Windows (New Architecture): production feedback on the WebView2 implementation + offer of hardening PRs
We ship a production react-native-windows 0.83.2 New-Architecture app (Win32 composition, WinAppSDK 1.8, ARM64) with an independently-built WebView2 Fabric component that converged on exactly the architecture #3973 landed: WinUI 3 WebView2 → Microsoft.UI.Xaml.XamlIsland → ContentIsland → RNW ContentIslandComponentView.Connect(). It renders real web content in the field today, so first: the XamlIsland/ContentIsland approach in #3973 is solid — we can confirm first pixel, load events, postMessage round-trips, and teardown behavior (including the "orphaned island breaks window-wide input" trap your comment in RCTWebView2ComponentView.cpp also documents) on independent code.
Reading windows/ReactNativeWebView/RCTWebView2ComponentView.cpp against our production component, we found a handful of places where our implementation had to harden the same code paths, and we'd like to upstream them as a small PR series.
- No
onErrorwhen the WebView2 Runtime is missing.EnsureCoreWebView2Async()is called without an environment and without failure handling (InitializeContentIsland). On machines without the Evergreen Runtime (stripped/managed images, Server SKUs) the view stays blank with no JS-visible signal. We create the environment explicitly (CoreWebView2Environment::CreateWithOptionsAsync) with a writable, packaged-safe user-data folder, catch failures, and emitonLoadingErrorwith the HRESULT — both when environment creation itself throws (Runtime entirely missing) and viaCoreWebView2InitializedEventArgs.Exception()(failure after an environment was created). - Failed navigations emit
onLoadingFinish.OnNavigationCompleteddoesn't consultargs.IsSuccess()/WebErrorStatus(), so DNS failures, TLS errors, and offline states look like successful loads to JS. We branch and emitonLoadingErrorwith theCoreWebView2WebErrorStatusinstead. While in there:title(DocumentTitle()) already exists as a field on every nav-event struct in the codegen (RCTWebView2.g.h) but is never populated on Windows — we fill it in ononLoadingStart/onLoadingFinish/onLoadingError/onSourceChanged. - Bridge timing:
window.ReactNativeWebViewis injected post-load inNavigationCompleted; page scripts that run during load (a synchronous inline<script>in<head>, for example) can't see it, and theWebMessageReceivedhandler is re-registered on everyNavigationStarting. Registering the bridge once viaAddScriptToExecuteOnDocumentCreatedAsyncwhenCoreWebView2becomes available makes it exist before any page script runs on every subsequent navigation — and the same mechanism wires upinjectedJavaScriptBeforeContentLoaded, which already exists in the codegen'd prop spec but isn't read on Windows yet. javaScriptEnabledset before core-init is lost (applied inUpdatePropsonly whenCoreWebView2()already exists;OnCoreWebView2Initializedre-applies onlyuserAgentand pending HTML, neverjavaScriptEnabled).UpdatePropsre-navigates on unrelated prop changes. It receives the full current prop set on every call (not a diff), and unconditionally re-setsm_webView.Source(uri)/ callsNavigateToString(html)whenever a source is present — so toggling an unrelated prop likejavaScriptEnabledcan re-navigate/reload the page as a side effect. We diff againstoldProps(already a parameter, currently unused) before navigating.
We've opened a 4-PR series against master, each independently revertible, in roughly ascending order of size:
- WV-1 — item 1 (environment + init-error
onError) - WV-2 — item 2 (
IsSuccess()honored +titlein nav events) - WV-3 — item 3 (document-start bridge +
injectedJavaScriptBeforeContentLoaded) - WV-4 — item 4 + 5 (
javaScriptEnabledlifecycle + source-diffing)
All four are marked draft: we have not compiled them against this repo (no Windows build was run this pass — the changes are hand-verified by side-by-side reading against your RCTWebView2ComponentView.cpp, not by bun run windows), and each PR description says so explicitly. Every fix mirrors a code path already running in production in a parallel Windows new-arch WebView2 Fabric component — see FacilitronWorks/react-native-webview-windows — but that component uses renamed symbols and isn't a drop-in diff, so the PRs are hand-ported to your codegen/class shape, not copy-pasted.
Two questions before we go further:
- Any in-flight work post-#3973 that would collide (especially around environment creation or the
codegen_manualheaders)? - Preference on user-data-folder policy: fixed
ApplicationData.LocalFoldersubfolder (what we did), or a new prop/static setter so apps can share a profile with their own WebView2 environments?
Context for anyone landing here from search: for apps pinned to react-native-webview versions before Windows New Architecture support landed, we also maintain a small companion package with these fixes (FacilitronWorks/react-native-webview-windows) — the plan is to upstream everything above and retire it.
Source: react-native-webview/react-native-webview