diff --git a/best-practices/README.md b/best-practices/README.md new file mode 100644 index 000000000..9e1490a6a --- /dev/null +++ b/best-practices/README.md @@ -0,0 +1,33 @@ +# WebView2 Best Practices + +This folder documents best practices that WebView2 developers should follow when +building applications that host the WebView2 control. The goal is to capture +practical, API‑level guidance — informed by real issues that developers have run +into — so that new and existing WebView2 apps can avoid common pitfalls around +lifecycle management, navigation, security, performance, and platform +integration. + +Each document focuses on a specific app model, platform, or scenario, and is +organized around two questions for every recommendation: + +- **Best practice** — what you should do. +- **Why?** — the reasoning, and what can go wrong if you don't. + +## Available guides + +- [UWP best practices](./uwp-best-practices.md) — guidance for hosting WebView2 + inside Universal Windows Platform (UWP) applications. + +## Contributing + +If you have hit a problem in your own app that you think other WebView2 +developers would benefit from knowing about, feel free to open an issue or a +pull request proposing a new best practice. Please keep entries: + +- **API‑level and product‑agnostic** — describe the WebView2 API behavior and + the recommended pattern, not implementation details of any specific app or + internal product. +- **Actionable** — readers should be able to apply the guidance directly in + their own code. +- **Justified** — always include the *Why?* so readers understand the trade‑off + and can decide how it applies to their scenario. diff --git a/best-practices/uwp-best-practices.md b/best-practices/uwp-best-practices.md new file mode 100644 index 000000000..d12cfbe54 --- /dev/null +++ b/best-practices/uwp-best-practices.md @@ -0,0 +1,145 @@ +# UWP Best Practices for WebView2 + +Guidance for hosting WebView2 inside Universal Windows Platform (UWP) apps. +Each entry uses the format: + +- **Best practice** — what to do (with a code snippet). +- **Why?** — the problem this prevents. + +--- + +## 1. Explicitly close the WebView2 controller on app shutdown + +### Best practice + +Call `Close()` on every `CoreWebView2Controller` you own from your UWP app's +suspend handler (for example `Application.Suspending`), and release your +references afterwards. Because browser process teardown is asynchronous, take +a suspend deferral and complete it from +`CoreWebView2Environment.BrowserProcessExited` so the OS does not tear the +app down before the runtime has finished shutting down. + +```cpp +// C++/WinRT — App.xaml.cpp +App::App() +{ + Suspending({ this, &App::OnSuspending }); +} + +void App::OnSuspending(IInspectable const&, SuspendingEventArgs const& e) +{ + auto deferral = e.SuspendingOperation().GetDeferral(); + + m_environment.BrowserProcessExited([deferral](auto&&, auto&&) + { + deferral.Complete(); + }); + + if (m_controller) + { + m_controller.Close(); + m_controller = nullptr; + m_webview = nullptr; + } +} +``` + +```csharp +// C# — App.xaml.cs +public App() +{ + this.Suspending += OnSuspending; +} + +private void OnSuspending(object sender, SuspendingEventArgs e) +{ + var deferral = e.SuspendingOperation.GetDeferral(); + + void OnExited(object s, CoreWebView2BrowserProcessExitedEventArgs args) + { + _environment.BrowserProcessExited -= OnExited; + deferral.Complete(); + } + + _environment.BrowserProcessExited += OnExited; + + if (_controller != null) + { + _controller.Close(); + _controller = null; + _webView = null; + } +} +``` + +### Why? + +Without an explicit `Close()`, the WebView2 instance is torn down without +going through the normal browser shutdown procedure. As a result, state such +as cookies is not flushed correctly, which can lead to data loss or +inconsistent state on the next launch. + +--- + +## 2. Handle external URI schemes via the OS launcher + +### Best practice + +Subscribe to `CoreWebView2.LaunchingExternalUriScheme`, cancel the default +launch, and forward the URI to `Windows.System.Launcher.LaunchUriAsync` so +that the OS performs the activation. Gate the hand‑off on +`IsUserInitiated` and, where appropriate, an allow‑list of schemes and +initiating origins so that web content cannot silently activate other apps. + +```csharp +// C# +private static readonly HashSet AllowedSchemes = + new(StringComparer.OrdinalIgnoreCase) { "mailto", "tel", "ms-settings" }; + +_webView.CoreWebView2.LaunchingExternalUriScheme += async (s, e) => +{ + e.Cancel = true; + + if (!e.IsUserInitiated) return; + if (!Uri.TryCreate(e.Uri, UriKind.Absolute, out var uri)) return; + if (!AllowedSchemes.Contains(uri.Scheme)) return; + + try + { + await Windows.System.Launcher.LaunchUriAsync(uri); + } + catch + { + // Optional: surface a fallback UI to the user. + } +}; +``` + +```cpp +// C++/WinRT +m_webview.LaunchingExternalUriScheme([](auto&&, auto const& args) +{ + args.Cancel(true); + + if (!args.IsUserInitiated()) return; + + Uri uri{ nullptr }; + try { uri = Uri{ args.Uri() }; } catch (...) { return; } + + auto scheme = uri.SchemeName(); + if (scheme != L"mailto" && scheme != L"tel" && scheme != L"ms-settings") + return; + + Windows::System::Launcher::LaunchUriAsync(uri); +}); +``` + +### Why? + +WebView2 inside a UWP app does not automatically activate external protocol +handlers (for example `mailto:`, `tel:`, `ms-settings:`, store, or custom +`myapp:` schemes). Without this handler, clicks on such links silently do +nothing, which appears as a broken link to the user. `LaunchUriAsync` is the +supported UWP activation path, and `LaunchingExternalUriScheme` is the +purpose‑built event for this hand‑off — it fires only for external schemes, +so the app does not need to filter `http`/`https` navigations itself.