Windows (New Architecture): production feedback on the WebView2 implementation + offer of hardening PRs

Author: FaithfulAudioCreated Jul 11, 2026Updated Aug 4, 2026

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 WebView2Microsoft.UI.Xaml.XamlIslandContentIsland → 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.

  1. No onError when 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 emit onLoadingError with the HRESULT — both when environment creation itself throws (Runtime entirely missing) and via CoreWebView2InitializedEventArgs.Exception() (failure after an environment was created).
  2. Failed navigations emit onLoadingFinish. OnNavigationCompleted doesn't consult args.IsSuccess()/WebErrorStatus(), so DNS failures, TLS errors, and offline states look like successful loads to JS. We branch and emit onLoadingError with the CoreWebView2WebErrorStatus instead. 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 on onLoadingStart/onLoadingFinish/onLoadingError/onSourceChanged.
  3. Bridge timing: window.ReactNativeWebView is injected post-load in NavigationCompleted; page scripts that run during load (a synchronous inline <script> in <head>, for example) can't see it, and the WebMessageReceived handler is re-registered on every NavigationStarting. Registering the bridge once via AddScriptToExecuteOnDocumentCreatedAsync when CoreWebView2 becomes available makes it exist before any page script runs on every subsequent navigation — and the same mechanism wires up injectedJavaScriptBeforeContentLoaded, which already exists in the codegen'd prop spec but isn't read on Windows yet.
  4. javaScriptEnabled set before core-init is lost (applied in UpdateProps only when CoreWebView2() already exists; OnCoreWebView2Initialized re-applies only userAgent and pending HTML, never javaScriptEnabled).
  5. UpdateProps re-navigates on unrelated prop changes. It receives the full current prop set on every call (not a diff), and unconditionally re-sets m_webView.Source(uri) / calls NavigateToString(html) whenever a source is present — so toggling an unrelated prop like javaScriptEnabled can re-navigate/reload the page as a side effect. We diff against oldProps (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 + title in nav events)
  • WV-3 — item 3 (document-start bridge + injectedJavaScriptBeforeContentLoaded)
  • WV-4 — item 4 + 5 (javaScriptEnabled lifecycle + 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_manual headers)?
  • Preference on user-data-folder policy: fixed ApplicationData.LocalFolder subfolder (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