Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions best-practices/README.md
Original file line number Diff line number Diff line change
@@ -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.
145 changes: 145 additions & 0 deletions best-practices/uwp-best-practices.md
Original file line number Diff line number Diff line change
@@ -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<string> 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.
}
};
Comment on lines +94 to +115
```

```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.