Skip to content

Feature/blazorwebview wpf composition control opt in#34869

Open
mobiletonster wants to merge 5 commits into
dotnet:net11.0from
mobiletonster:feature/blazorwebview-wpf-composition-control-opt-in
Open

Feature/blazorwebview wpf composition control opt in#34869
mobiletonster wants to merge 5 commits into
dotnet:net11.0from
mobiletonster:feature/blazorwebview-wpf-composition-control-opt-in

Conversation

@mobiletonster

Copy link
Copy Markdown

Description of Change

This change introduces an opt‑out mechanism for using WebView2CompositionControl in WPF BlazorWebView scenarios.
While WebView2CompositionControl resolves the WPF airspace issue, it introduces measurable rendering overhead that
negatively impacts Razor component performance.

For apps that do not require XAML‑over‑WebView layering, developers can now disable the composition control and fall
back to the standard WebView2, resulting in significantly improved performance on WPF.

This feature also addresses feedback from the original WebView2CompositionControl introduction
(PR #31777: #31777)
and community requests asking for an opt‑in/opt‑out toggle
(Issue #28063: #28063).


Issues Fixed / Related

This PR enables opting out of WebView2CompositionControl (added in .NET 10.0 to address WPF airspace issues) in
favor of the standard WebView2 when airspace layering is unnecessary and lower rendering overhead is preferred.


Summary of Changes

  • Added UseCompositionControl dependency property (default: true) on BlazorWebView, preserving existing
    behavior.
  • Updated _webview and the WebView property to use IWebView2, implemented by both WebView2 and
    WebView2CompositionControl.
  • CreateWebViewTemplate() now selects the correct control type at initialization; the property‑changed callback
    updates the template if changed before the control is added to the visual tree.
  • Added an InvalidOperationException if UseCompositionControl is modified after the underlying WebView has been
    created.
  • Updated WebView2WebViewManager and BlazorWebViewInitializedEventArgs shared source to use the WPF IWebView2
    alias.
  • Updated PublicAPI.Unshipped.txt with *REMOVED* entries for the old WebView2CompositionControl return types
    and the new IWebView2 and UseCompositionControl public API surface.

PR Review Feedback Addressed

  • Missing *REMOVED* entries in PublicAPI.Unshipped.txt — Added *REMOVED* entries for both
    BlazorWebViewInitializedEventArgs.WebView and BlazorWebView.WebView to satisfy the API analyzer.
  • Unsafe cast in OnApplyTemplate() — Replaced the direct (IWebView2) cast with an is not pattern match that
    throws a descriptive InvalidOperationException if the template child is missing or the wrong type.
  • Unit tests — No WPF-specific test project exists in this repository; the existing DeviceTests project targets
    the MAUI handler layer and is not appropriate for standalone WPF control tests. A dedicated WPF test project would be
    a disproportionate investment for this change and has been deferred.
  • WebView return type / source compatibility — The change from WebView2CompositionControl! to IWebView2! is
    intentional and documented. The proposed alternative (keeping WebView as WebView2CompositionControl? and adding a
    WebViewBase property) would itself be a breaking change (nullable return) and would produce a confusing two-property
    API. Callers relying on WebView2CompositionControl-specific members can cast explicitly.

Notes

This is not a bug fix; it is a feature/performance enhancement.

Copilot AI review requested due to automatic review settings April 8, 2026 00:06
@github-actions

github-actions Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34869

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34869"

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a WPF-specific toggle to allow opting out of WebView2CompositionControl for BlazorWebView, enabling better performance in scenarios that don’t need XAML-over-WebView layering, while preserving the current default behavior.

Changes:

  • Added UseCompositionControl (default true) to select between WebView2CompositionControl and standard WebView2.
  • Updated the exposed WebView surface (and related initialization plumbing) to use a common WPF IWebView2 type.
  • Updated WPF PublicAPI tracking with new entries and *REMOVED* markers for the prior return types.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/BlazorWebView/src/Wpf/BlazorWebView.cs Adds UseCompositionControl DP, selects the appropriate WPF WebView2 control via template creation, and updates WebView to return IWebView2.
src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs Switches the WPF WebView2 alias to IWebView2 to support either underlying control.
src/BlazorWebView/src/SharedSource/BlazorWebViewInitializedEventArgs.cs Updates the WPF WebView event arg type alias to IWebView2.
src/BlazorWebView/src/Wpf/PublicAPI.Unshipped.txt Records new API surface and removals for the changed WebView-returning members.

@kubaflo

kubaflo commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Code Review — PR #34869

Multi-model review (Claude Opus, GPT-5.4, Claude Sonnet 4.5)

Independent Assessment

What this changes: Adds a UseCompositionControl WPF dependency property (default: true) to BlazorWebView that lets developers opt out of WebView2CompositionControl in favor of the standard WebView2 for better rendering performance when WPF airspace layering is not needed. Internally, the _webview field and public WebView property are widened from the concrete WebView2CompositionControl type to the IWebView2 interface (which both controls implement).

Inferred motivation: WebView2CompositionControl was introduced in .NET 10 GA (PR #31777) to fix the WPF airspace issue, but it introduces measurable rendering overhead. Users who don't need XAML-over-WebView layering want to opt out.

Reconciliation with PR Narrative

Author claims: Feature enhancement, not a bug fix. Breaking change to WebView return type is intentional. No tests due to lack of WPF test infrastructure.

Agreement: The code matches the description accurately. The approach is sound — using IWebView2 as the common type is the cleanest path. The PR description is thorough and addresses prior review feedback.

Findings

⚠️ Warning — Branch target may be incorrect (needs maintainer guidance)

This PR targets net10.0, but .NET 10 GA shipped in November 2025 (commit e101f0d9ac marked the API as shipped Nov 3, 2025). The WebView2CompositionControl return types on BlazorWebView.WebView and BlazorWebViewInitializedEventArgs.WebView are in PublicAPI.Shipped.txt, making this a breaking change to shipped GA API.

Per repository conventions, new features with API changes should target the highest netN.0 feature branch (net11.0). This PR adds new public API surface (UseCompositionControl, UseCompositionControlProperty) and changes shipped return types — both are feature-branch work, not servicing.

Recommendation: A MAUI maintainer should confirm whether this should retarget to net11.0.

⚠️ Warning — Breaking API change to WebView property (binary + source)

BlazorWebView.WebView changes from WebView2CompositionControl!IWebView2! and BlazorWebViewInitializedEventArgs.WebView from WebView2CompositionControlIWebView2.

  • Binary breaking: Callers compiled against .NET 10 GA will get MissingMethodException at runtime
  • Source breaking: Code explicitly typed as WebView2CompositionControl (e.g., WebView2CompositionControl ctrl = blazorWebView.WebView;) won't compile

The *REMOVED* entries in PublicAPI.Unshipped.txt correctly track this against the shipped API — the Public API analyzer approach is sound. However, since these were shipped in .NET 10 GA, this needs explicit maintainer approval for the breaking change, with migration guidance in release notes.

Multi-model agreement: all 3 models flagged this. All agreed it's acceptable for a pre-GA change, but the shipped status in PublicAPI.Shipped.txt raises the bar.

💡 Suggestion — Missing newline at end of PublicAPI.Unshipped.txt

src/BlazorWebView/src/Wpf/PublicAPI.Unshipped.txt doesn't end with a newline. This will trigger git diff warnings and may cause analyzer issues.

Multi-model agreement: all 3 models flagged this.

💡 Suggestion — (_webview as IDisposable)?.Dispose() cast — verify necessity

In DisposeAsyncCore(), the dispose changed from _webview?.Dispose() to (_webview as IDisposable)?.Dispose(). If IWebView2 extends IDisposable, the cast is unnecessary. If it doesn't, the cast is correct and defensive. Either way it works, but a brief comment explaining the cast would help future readers.

💡 Suggestion — Binding to UseCompositionControl could throw unexpectedly

If a consumer data-binds UseCompositionControl and the bound value changes after the WebView is created, the property-changed callback throws InvalidOperationException. The XML docs correctly warn about this, but consider logging a warning instead of throwing for the "value didn't actually change" case (i.e., setting true when it's already true after initialization).

Devil's Advocate

Challenge to my findings: The branch target concern may be outdated if the team is still accepting feature work on net10.0 for a point release. MAUI has historically included features in servicing releases. A maintainer should weigh in.

Challenge to my approval: I can't verify runtime behavior of the WPF template swapping or IWebView2 interface contract without running the code on Windows. The WebView2 SDK's IWebView2 interface surface is an external dependency — if it changes or lacks expected members, this will break at runtime.

What I didn't verify: No WPF test infrastructure exists in this repo, so I can't empirically verify the UseCompositionControl=false path works correctly. The approach is sound in theory, but manual testing on WPF is essential before merge.

CI Status

CI checks show fail for maui-pr, maui-pr-devicetests, and maui-pr-uitests, but the failure links reference PR #34764 (a different PR), suggesting these may be base-branch failures rather than failures caused by this PR. Build Analysis is pending.

Verdict: NEEDS_DISCUSSION

Confidence: medium
Summary: The implementation is clean, well-documented, and follows correct patterns for WPF dependency properties. The core concern is that this makes a breaking change to shipped .NET 10 GA public API and targets a servicing branch (net10.0) rather than the feature branch (net11.0). A MAUI maintainer should confirm the branch target and approve the breaking API change. The code itself is well-structured — if the branch and breaking change are approved, the implementation is ready.

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please review the comment?

@mobiletonster

mobiletonster commented Apr 27, 2026 via email

Copy link
Copy Markdown
Author

@kubaflo

kubaflo commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

@mobiletonster no worries, it might be a bit confusing at first :) So the main branch is the latest maui release (currently net10), net10 branch is an old branch we had for net10 pre-releases. MauiBot asked you to target this pr to net11 branch. I've just done it;

@kubaflo kubaflo changed the base branch from net10.0 to net11.0 April 27, 2026 18:21
@kubaflo

kubaflo commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

@mobiletonster I've also resolved conflicts :)

@kubaflo

kubaflo commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Code Review — PR #34869 (Re-review post-retarget)

Previous Finding Status

# Finding Status
1 ⚠️ Branch target net10.0 — breaking change to shipped GA API Fixed — retargeted to net11.0 (feature branch). API changes are now for .NET 11, not servicing.
2 ⚠️ Breaking API change (WebView return type) Acceptable — in net11.0 this is a new-major-version API change, not a servicing break. *REMOVED* entries properly track the .NET 10 → 11 transition.
3 💡 Missing newline at EOF in PublicAPI.Unshipped.txt ⚠️ Still present\ No newline at end of file
4 💡 (_webview as IDisposable)?.Dispose() cast ℹ️ Non-blocking
5 💡 Binding to UseCompositionControl edge case ℹ️ Non-blocking, documented

Merge Conflict Resolution

Conflicts resolved by accepting net11.0 versions for 10 infrastructure files (NuGet.config, global.json, Version.Details.xml, etc.). None of the PR's BlazorWebView changes were affected — the core diff is identical to the pre-merge version.

CI Status

CI just started after the merge push. Build Analysis pending, maui-pr not yet triggered. No results to assess yet.

Verdict: LGTM

Confidence: high
Summary: The retarget from net10.0net11.0 resolves the primary concern from the initial review. The API change is now properly in the feature branch for .NET 11. Implementation is clean, well-documented, and follows correct WPF patterns. Only remaining nit is the missing EOF newline in PublicAPI.Unshipped.txt. Ready for maintainer approval.

mobiletonster and others added 5 commits April 27, 2026 20:29
Allows opting out of the WebView2CompositionControl (introduced in
net10.0 to fix WPF airspace issues) in favor of the standard WebView2
control when airspace layering is not required and lower rendering
overhead is preferred.

- Add UseCompositionControl dependency property (default: true) to
  BlazorWebView, preserving existing behavior by default
- Type _webview and the WebView property as IWebView2, the interface
  implemented by both WebView2 and WebView2CompositionControl
- CreateWebViewTemplate() selects the correct control type at init time;
  the property-changed callback swaps the template if set before the
  control is added to the visual tree
- Throw InvalidOperationException if UseCompositionControl is changed
  after the underlying WebView2 has already been created
- Update WebView2WebViewManager and BlazorWebViewInitializedEventArgs
  shared source to use IWebView2 for the WPF type alias
- Update PublicAPI.Shipped.txt to reflect the IWebView2 return types
  and new UseCompositionControl public API
…Unshipped.txt

I misunderstood how this was used. This should correct it.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Enhanced error reporting when WebView2 template child is missing
or of the wrong type. Updated public API to return IWebView2
instead of WebView2CompositionControl for greater abstraction.
@kubaflo kubaflo force-pushed the feature/blazorwebview-wpf-composition-control-opt-in branch from c612109 to 543def2 Compare April 27, 2026 18:30
@kubaflo

kubaflo commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

@jfversluis the pr looks good! The only question is if we want it in net 11

@mobiletonster

mobiletonster commented Apr 27, 2026 via email

Copy link
Copy Markdown
Author

@kubaflo

kubaflo commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

NEEDS_DISCUSSION (confidence: medium) — multi-model review (2 NEEDS_DISCUSSION, 2 LGTM; no code defects found).

This PR adds an opt-in WPF BlazorWebView composition control (WebView2CompositionControl) gated by UseCompositionControl (default true, preserving current behavior). Verified against HEAD: both WebView2 and WebView2CompositionControl implement IWebView2 and are valid for the FrameworkElementFactory path; the PublicAPI *REMOVED*/new nullability entries match; WinForms/MAUI branches are unaffected.

The item for maintainer discussion is the deliberate binary-breaking return-type change of a .NET 10 GA API — it is intentional and already extensively discussed on the PR, and warrants explicit sign-off plus a green net11.0 CI run before merge. (A trailing-newline nit on PublicAPI.Unshipped.txt is left to the formatter.)

CI: confirm a green net11.0 run before merge.

Multi-model review (gpt-5.5 · opus-4.8 · opus-4.6 · gemini-3.1-pro). Comments only — not a formal approval.

@kubaflo

kubaflo commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 3 pipeline(s).

@kubaflo

kubaflo commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

/review tests

@github-actions

Copy link
Copy Markdown
Contributor

Tests Failure Analysis

@mobiletonster — test-failure review results are available based on commit 543def2.
To request a fresh review after new comments, commits, or CI runs, comment /review tests.

Overall Likely unrelated Failures 25 Platform Multiple

Test Failure Review: Likely unrelated - click to expand

Overall verdict: Likely unrelated

All three CI pipelines (maui-pr, maui-pr-devicetests, maui-pr-uitests) have consistently failed on the base branch (net11.0) across the last five builds each, which is strong evidence these failures are pre-existing and not caused by this PR. The PR changes only four files in src/BlazorWebView/src/Wpf/ and src/BlazorWebView/src/SharedSource/; the vast majority of failures occur on iOS, macOS, Android, and MacCatalyst platforms that are entirely outside the PR scope.

Failure Verdict Evidence
RunOniOS_MauiReleaseTrimFull / RunOniOS_MauiReleaseTrimFull_CoreCLR (maui-pr) Likely unrelated iOS Apple template build failures; PR modifies WPF/BlazorWebView only. Base branch builds 1455686, 1455594, 1455188, 1454301, 1448182 all failed on net11.0.
Run Integration Tests AOT macOS (maui-pr) Likely unrelated macOS AOT job; PR does not change macOS or AOT-related code. Base branch consistently fails on this pipeline.
net11.0 Windows Helix Tests Run DeviceTests Windows (maui-pr-devicetests) Needs human investigation Windows platform overlaps with the PR's WPF changes; however, Helix work-item data returned 404 and could not be verified. Base branch builds 1455984, 1455596, 1455190, 1454304, 1448184 all failed on the same definition, suggesting a pre-existing issue.
ShellBadgeCanBeCleared, ShellBadgeCanBeSetAtRuntime, ShellBadgeInitialBadgeIsVisible, ShellBadgeMultipleTabsCanHaveBadges (Android UITests Shell) Likely unrelated VisualTestFailedException on Android Shell badge visual tests; PR does not touch Shell or Android. Pattern consistent with missing/stale visual baselines. Base branch consistently fails on uitests pipeline.
VerifyHamburgerIcon, VerifyFlyoutBackgroundColor, AFlyoutTests, VerifyShellFlyoutContentAlignedInRTL, TabBarShouldNotHaveEmptySpaceAfterNavigatingBack, FlyoutContentTests (Android/iOS Shell) Likely unrelated TimeoutException navigating to Shell Flyout and VisualTestFailedException on Shell/TabBar visual tests; PR does not modify Shell or Flyout code. Base branch consistently fails.
WebView_* (10 tests, macOS UITests Controls WebView) Likely unrelated All macOS Controls WebView tests time out with Appium TimeoutException. These test the MAUI native WebView control, not BlazorWebView; the changed file WebView2WebViewManager.cs is a BlazorWebView-only component. Base branch consistently fails on uitests pipeline.
MacCatalyst UITests CoreCLR Controls (Cells, CheckBox, etc.) Likely unrelated Job completed in ~3 minutes suggesting an infrastructure or setup failure; PR does not change MacCatalyst Controls code. Base branch consistently fails.
iOS UITests CoreCLR / Mono Controls Shell Likely unrelated iOS Shell tests; PR does not modify iOS or Shell code. Base branch consistently fails on uitests pipeline.

Recommended action

No action is needed on the test failures — all failures except Windows Helix are clearly unrelated based on platform mismatch and consistent base-branch failures. For the Windows Helix device-test failure, a reviewer should verify whether this failure is also present on recent base-branch builds once Helix data becomes accessible.

Evidence details

PR scope: 4 files changed, all in src/BlazorWebView/ (WPF and SharedSource). No test files changed. Inferred area: Blazor only.

Base branch failures (net11.0):

  • maui-pr (302): builds 1455686, 1455594, 1455188, 1454301, 1448182 all failed / completed
  • maui-pr-devicetests (314): builds 1455984, 1455596, 1455190, 1454304, 1448184 all failed / completed
  • maui-pr-uitests (313): builds 1456236, 1455595, 1455189, 1454302, 1448183 all failed / completed

This PR builds:

  • maui-pr build 1464556: 13 failed timeline records, 20 distinct log/test failures
  • maui-pr-devicetests build 1464558: 8 failed timeline records; Helix job 3af78527-642f-44ab-9875-483205e2a7d2 returned 404
  • maui-pr-uitests build 1464557: 30 failed timeline records, 87 distinct log/test failures (still in progress at review time)

Limitations: No AzDO token was available; authenticated test-run APIs were skipped. Build metadata, timelines, and public logs were queried. Helix work-item details for the Windows device-test failure returned 404 and could not be inspected. maui-pr-uitests build 1464557 was still in progress when context was gathered; additional failures may appear.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community ✨ Community Contribution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants