Skip to content

[WIP] [Android]Fix for Android BlazorWebView crash when opening image links in a new window - #38195

Draft
BagavathiPerumal wants to merge 2 commits into
dotnet:mainfrom
BagavathiPerumal:fix-35157
Draft

[WIP] [Android]Fix for Android BlazorWebView crash when opening image links in a new window#38195
BagavathiPerumal wants to merge 2 commits into
dotnet:mainfrom
BagavathiPerumal:fix-35157

Conversation

@BagavathiPerumal

Copy link
Copy Markdown
Contributor

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Root cause

The issue occurs because the Android WebView returns the nested image's data: URI through HitTestResult.Extra when a target="_blank" link contains an image. MAUI then attempts to open this non-launchable URI using an Android activity, causing an ActivityNotFoundException and crashing the app.

Solution description

The fix involves using RequestFocusNodeHref to retrieve the enclosing anchor's actual URL for image links while preserving the existing behavior for normal text links. It also rejects non-launchable URL schemes and safely handles ActivityNotFoundException, preventing the app from crashing.

Windows Behavior

The test behaves differently on Windows because WebView2 receives new-window navigation through NewWindowRequestedEventArgs.Uri instead of Android's HitTestResult.Extra. Therefore, Windows does not use the nested image's data: URI when resolving the enclosing link. The https://0.0.0.1/ address is BlazorWebView's internal virtual origin and represents a separate WebView2 new-window routing behavior.

Test Restriction

The regression test is compiled only for Android because it specifically verifies the Android WebView's HitTestResult and RequestFocusNodeHref behavior. Windows uses WebView2, while iOS and Mac Catalyst use WKWebView. Therefore, these platforms follow different new-window navigation paths and do not exercise this Android-specific code.
Validated the behavior in the following platforms

  • iOS
  • Mac
  • Android
  • Windows

Issues Fixed

Fixes #35157

Output ScreenShot

Platform Before After
Android
35157-BeforeFix.mov
35157-AfterFix.mov

@github-actions

github-actions Bot commented Sep 4, 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 -- 38195

Or

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

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Sep 4, 2026
@BagavathiPerumal BagavathiPerumal added the community ✨ Community Contribution label Sep 4, 2026
@github-actions github-actions Bot added area-blazor Blazor Hybrid / Desktop, BlazorWebView platform/android labels Sep 4, 2026
@vishnumenon2684

Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests , maui-pr-devicetests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).

@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Sep 4, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 1 findings

See inline comments for details.

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review Summary

@BagavathiPerumal — new AI review results are available based on commit 29363f3.

Gate Passed Confidence Medium Platform Android


🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix

Gate Result: ✅ PASSED

Platform: ANDROID · Base: main · Merge base: 7d9efcdc

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue35157 Issue35157 ✅ FAIL — 2864s ✅ PASS — 610s
🔴 Without fix — 🖥️ Issue35157: FAIL ✅ · 2864s

Error-relevant lines (filtered from the build log):

/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Mono.AndroidTools.Internal.AdbOutputParsing.CheckInstallSuccess(String output, String packageName) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Mono.AndroidTools.AndroidDevice.<>c__DisplayClass105_0.<InstallPackage>b__0(Task`1 t) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
Build FAILED.
     at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.Issues.Issue35157.TargetBlankLinkWithDataImageDoesNotCrash() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35157.cs:line 23
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
🟢 With fix — 🖥️ Issue35157: PASS ✅ · 610s

(no coded error found; showing last 1200 chars)

(x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.12]   Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.37]   Discovered:  Controls.TestCases.Android.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 09/04/2026 15:45:23 FixtureSetup for Issue35157(Android)
>>>>> 09/04/2026 15:45:25 TargetBlankLinkWithDataImageDoesNotCrash Start
>>>>> 09/04/2026 15:45:34 TargetBlankLinkWithDataImageDoesNotCrash Stop
  Passed TargetBlankLinkWithDataImageDoesNotCrash [9 s]
NUnit Adapter 4.5.0.0: Test execution complete
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue35157.trx
Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 24.6046 Seconds
>>> TRX_RESULT_FILE: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue35157.trx
📁 Fix files reverted (1 files)
  • src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs

📋 Pre-Flight — Context & Validation

PR #38195 Pre-Flight

Scope

  • PR: [WIP] [Android]Fix for Android BlazorWebView crash when opening image links in a new window
  • Base / head: main / fix-35157
  • Materialized review commit: 2b53d62a3928b36eb39dfb2bf3d7058e8a1b400f
  • Linked issue: #35157
  • Platform: Android
  • Gate: Passed previously. Issue35157 failed without the production fix and passed with it. Do not rerun gate verification and do not modify gate/content.md.

Problem

For a BlazorWebView link such as <a href="https://www.google.com" target="_blank"><img src="data:image/..."></a>, Android reports the image source in WebView.GetHitTestResult().Extra. The current implementation passes that data: URI to an ACTION_VIEW intent. Android has no matching activity and throws ActivityNotFoundException, crashing the app instead of opening the enclosing anchor URL.

Existing PR Approach

The PR changes src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs to:

  1. Detect HitTestResult.SrcImageAnchorType.
  2. Call RequestFocusNodeHref with a custom main-looper Handler to retrieve the enclosing anchor's URL asynchronously.
  3. Route both image-anchor and normal-link URLs through TryOpenInExternalBrowser.
  4. Reject data:, blob:, javascript:, and about: URLs and catch ActivityNotFoundException.

The alternative candidates must use a materially different root-cause mechanism, not merely relocate the same checks.

Diff Boundary

The PR changes six files:

  • Production: src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs (+59/-4)
  • Test host project: src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj
  • Test host page: src/Controls/tests/TestCases.HostApp/Issues/Issue35157.cs (new)
  • Test host startup: src/Controls/tests/TestCases.HostApp/MauiProgram.cs
  • Test host HTML: src/Controls/tests/TestCases.HostApp/wwwroot/index.html (new)
  • UI test: src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35157.cs (new)

Only the production file is a gate-detected fix file and eligible for a try-fix implementation. The test additions remain in place to verify alternatives.

Bounded Validation

Run only:

pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -TestFilter "Issue35157"

This is the detected primary regression test and the only mandatory regression test supplied for STEP 5a. Do not run a full suite, category-wide suite, gate verification, or unrelated tests.

Attempt Limits

Each candidate receives one implementation/test pass and at most one focused correction/retest. It must perform the try-fix skill's inline expert self-review, capture all required artifacts, and restore using only:

pwsh .github/scripts/EstablishBrokenBaseline.ps1 -Restore

The repository contains unrelated pre-existing .github and eng worktree changes. They are out of scope and must remain untouched.


🔬 Code Review — Deep Analysis

Expert PR Evaluation — PR #38195

Verdict: NEEDS_DISCUSSION

Confidence: medium

Independent assessment

The Android production change addresses the crash at its source: SrcImageAnchorType no longer sends the nested image's data: URI directly to an ACTION_VIEW intent. Instead, it asks Android WebView for the focused anchor's href, routes both image-anchor and ordinary-link targets through one launch helper, rejects schemes that should not be dispatched externally, and converts a missing activity handler from an application crash into an unhandled-new-window result.

The implementation is localized to Android BlazorWebChromeClient behavior and introduces no static state or startup-path changes. The main-looper callback keeps Android WebView and activity operations on the UI thread. The trusted Gate establishes that the supplied regression scenario fails without the production fix and passes with it.

Actionable finding

⚠️ The UI test does not prove that external navigation occurred. In src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35157.cs:23, the test taps the image, foregrounds the HostApp, and asserts a survival label that was already present before the tap. This detects the original process-crash failure, but a future implementation that simply suppresses image-anchor navigation would also pass. The test should establish an observable post-tap precondition showing that the external browser/navigation path was reached before it verifies that the HostApp survived.

The raw file:line finding is persisted in inline-findings.json.

Failure-mode probing

  • Ordinary text links: They continue through the same external-launch path, now with scheme filtering and ActivityNotFoundException handling.
  • Image anchors whose focused-node URL is absent or non-launchable: The callback does not dispatch an intent and the new-window request remains unhandled, avoiding the crash.
  • No activity can handle an otherwise launchable URL: ActivityNotFoundException is contained and reported as an unhandled launch rather than escaping through the WebChromeClient callback.
  • Lifecycle/static-state risk: No listener, subscription, or shared state is retained across handler reconnects; the callback and handler are per request.
  • Regression-test false green: If image-anchor intent dispatch stops entirely, the existing label remains visible and the current final assertion can still pass.

Recommendation

Keep the production fix, but harden the focused regression test in one consolidated candidate patch so it demonstrates that the external navigation was triggered rather than only that the HostApp remained alive.


🛠️ Try-Fix — Analysis & Comparison

STEP 5a Try-Fix Aggregate — PR #38195

Candidate 1 — Hit-Test Type Gating

  • Model: gpt-5.3-codex
  • Result: Blocked
  • Approach: Treat Android's hit-test classification as authoritative. Launch an external intent only for a real link-target hit and skip SrcImageAnchorType, preventing the nested image's data: payload from entering the intent path.
  • Difference from PR: The PR asynchronously recovers the enclosing anchor with RequestFocusNodeHref, filters schemes, and catches ActivityNotFoundException. Candidate 1 would avoid URL recovery and block image-anchor intent dispatch at the hit-test boundary.
  • Files changed: None.
  • Test: pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -TestFilter "Issue35157" was not executed.
  • Failure analysis: Mandatory baseline establishment failed because EstablishBrokenBaseline.ps1 rejected the pre-existing dirty .github and eng worktree. Skill constraints therefore prohibited implementation and testing. The required restore command ran and reported No baseline state found / Restored=False, which is expected because baseline state was never created and no candidate edits were made.
  • Inline self-review: 0 findings; there was no candidate diff.
  • Details: ../try-fix-1/content.md
  • Artifacts: attempt-1/

Candidate 2 — Actual New-Window Navigation Interception

  • Model: gpt-5.6-sol
  • Result: Blocked
  • Approach: Accept Android WebView's WebViewTransport from resultMsg, attach a short-lived WebView/WebViewClient, consume Chromium's first real new-window navigation request, and open that request URL externally. This would obtain the enclosing anchor's resolved href from the navigation pipeline rather than from touch metadata.
  • Prior approaches avoided: Unlike the PR, it would not use GetHitTestResult().Extra, SrcImageAnchorType, RequestFocusNodeHref, a URL-scheme blacklist, or a custom Handler. Unlike Candidate 1, it would not suppress image-anchor launches based on hit-test type.
  • Mechanism-level difference: The faulty value originates in hit testing, which identifies the touched image rather than its enclosing anchor. Candidate 2 would stop using hit testing for URL selection and let Chromium expose the actual _blank navigation target through the transported WebView.
  • Files changed: None.
  • Captured diff: Empty.
  • Test: pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -TestFilter "Issue35157" was not executed because it would only have exercised the unchanged PR implementation.
  • Failure analysis: Mandatory baseline establishment again failed with DIRTY WORKING DIRECTORY due to the same pre-existing .github and eng changes. No .github/.baseline-state.json or RevertedFiles allow-list was created, so implementation and testing were prohibited. The exact restore command ran and reported No baseline state found / Restored=False; no candidate edits existed.
  • Inline self-review: 0 findings; there was no candidate diff.
  • Details: ../try-fix-2/content.md
  • Artifacts: attempt-2/

Final Aggregate Status

The bounded maximum of two sequential candidates was reached:

Candidate Mechanism Result Empirical Test
1 (gpt-5.3-codex) Gate intent launch by hit-test type and skip image-anchor payloads Blocked Not run
2 (gpt-5.6-sol) Capture Chromium's actual target through WebViewTransport Blocked Not run

Both candidates are design-only and unvalidated. The mandatory try-fix baseline guard rejected unrelated pre-existing tracked changes before either candidate could modify the sole production target. No candidate code remains applied, both required restore commands completed with the expected verified no-state outcome, and no full or unrelated test suite was run.


📝 PR Finalize — Recommended Title & Description

Assessment: ✏️ Recommend updating — the description is detailed and accurate, but the title retains a [WIP] marker, omits the component separator, and does not follow the [Platform] Component: What changed format.

Recommended title

[Android] BlazorWebView: Fix crash when image links open a new window

Recommended description

### Root cause

When a `target="_blank"` anchor contains an image, Android WebView returns the nested image's `data:` URI through `HitTestResult.Extra`. `BlazorWebChromeClient.OnCreateWindow` then passes that non-launchable URI to an `ACTION_VIEW` intent. Because Android has no matching activity, `StartActivity` throws `ActivityNotFoundException` and crashes the app instead of opening the enclosing anchor URL.

### Description of Change

For `HitTestResult.SrcImageAnchorType`, `BlazorWebChromeClient` now uses `RequestFocusNodeHref` to retrieve the enclosing anchor's actual URL while preserving the existing path for ordinary text links. Both paths use a shared external-launch helper that:

- ignores empty URLs and the clearly non-launchable `data:`, `blob:`, `javascript:`, and `about:` schemes;
- opens launchable URLs with an Android `ACTION_VIEW` intent; and
- handles `ActivityNotFoundException` so a missing activity does not crash the app.

The PR also adds an Android HostApp page and UI regression test for the image-anchor scenario.

### Windows behavior

The scenario behaves differently on Windows because WebView2 receives new-window navigation through `NewWindowRequestedEventArgs.Uri` instead of Android's `HitTestResult.Extra`. Windows therefore does not use the nested image's `data:` URI when resolving the enclosing link. The `https://0.0.0.1/` address is BlazorWebView's internal virtual origin and represents a separate WebView2 new-window routing behavior.

### Test restriction

The regression test is compiled only for Android because it specifically verifies Android WebView's `HitTestResult` and `RequestFocusNodeHref` behavior. Windows uses WebView2, while iOS and Mac Catalyst use WKWebView, so those platforms follow different new-window navigation paths and do not exercise this Android-specific code.

### Platforms validated

- [x] iOS
- [x] Mac Catalyst
- [x] Android
- [x] Windows

### Issues Fixed

Fixes https://github.com/dotnet/maui/issues/35157

### Output recording

| Platform | Before | After |
|---|---|---|
| Android | <video src="https://github.com/user-attachments/assets/be2fa02a-90bf-4413-9c53-7d4a1b12991f"> | <video src="https://github.com/user-attachments/assets/1e46f601-5b66-486f-a7aa-9526e4e67299"> |

🏁 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Winner: pr-plus-reviewer

pr-plus-reviewer retains the submitted Android production fix and closes the expert reviewer's only actionable gap: the regression test now proves that tapping the image actually launches an external activity before checking that the HostApp survived. It passed the required focused Android validation in the prescribed candidate sandbox.

Comparative ranking

Rank Candidate Implementation status Regression evidence Assessment
1 pr-plus-reviewer Implemented in the required sandbox PASSIssue35157, 1/1 test Best balance of correctness and evidence. Preserves the sound PR fix while preventing a no-op navigation implementation from producing a false-green test.
2 pr Submitted implementation PASS — trusted Gate fails without the fix and passes with it Production approach is localized and sound, but the final assertion observes a label that exists before the tap. It detects the original crash but does not independently establish that external navigation occurred.
3 try-fix-2 Design only; blocked before editing Not run Obtaining the URL from a transported WebView's real navigation avoids hit-test metadata, but no diff or empirical evidence exists. It also adds a temporary WebView, transport setup, interception, and lifecycle cleanup for a problem the submitted focused-node API solves more directly.
4 try-fix-1 Design only; blocked before editing Not run Skipping SrcImageAnchorType would avoid the crash by suppressing image-anchor launches rather than opening the enclosing link. The strengthened winning test is specifically designed to reject this false-success behavior.

Expert review reconciliation

The one expert-review pass found no blocking production-code defect. BlazorWebChromeClient.OnCreateWindow detects SrcImageAnchorType, resolves the enclosing anchor through RequestFocusNodeHref, dispatches launchable URLs through one helper, filters clearly non-launchable schemes, and contains ActivityNotFoundException. The change is Android-local, has no startup or shared-state impact, and disposes its per-request Handler.

The review did identify one moderate regression-coverage weakness on the submitted test line. The single consolidated reviewer patch resolves it without altering production behavior or broadening scope.

Validation and uncertainty

  • Trusted Gate for pr: PASS — test fails without the production fix and passes with it.
  • Required validation for pr-plus-reviewer: PASS — exact Android Issue35157 command, 1/1 test.
  • try-fix-1 and try-fix-2: BLOCKED by their baseline guard; neither produced code or ran the regression test.
  • No candidate failed an executed regression test. The two blocked design-only alternatives rank below both empirically passing implementations.

Because the winning test hardening is not present in the submitted PR HEAD, the submitted PR should receive the pr-plus-reviewer change before approval.


📱 UI Tests — WebView

Detected UI test categories: WebView

Deep UI tests — 57 passed, 0 failed across 1 category on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
WebView 57/57 ✓
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)

🧭 Next Steps — reviewer changes required

The reviewer-enhanced candidate identified changes that are not yet in the submitted PR.

Why: The submitted Android production fix is sound, and pr-plus-reviewer preserves it while hardening the regression test to prove an external activity was actually launched. The focused Issue35157 validation passed in the required candidate sandbox, while both try-fix alternatives remained unimplemented and unvalidated.

Address the actionable findings in this review before merging.

@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) and removed s/agent-review-in-progress AI review is currently running for this PR labels Sep 4, 2026
@BagavathiPerumal

Copy link
Copy Markdown
Contributor Author

AI Review Summary

@BagavathiPerumal — new AI review results are available based on commit 29363f3.

Gate Passed Confidence Medium Platform Android

🗂️ Review Sessions — click to expand

🚦 Gate — Test Before & After Fix

Gate Result: ✅ PASSED

Platform: ANDROID · Base: main · Merge base: 7d9efcdc

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue35157 Issue35157 ✅ FAIL — 2864s ✅ PASS — 610s
🔴 Without fix — 🖥️ Issue35157: FAIL ✅ · 2864s
Error-relevant lines (filtered from the build log):

/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Mono.AndroidTools.Internal.AdbOutputParsing.CheckInstallSuccess(String output, String packageName) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Mono.AndroidTools.AndroidDevice.<>c__DisplayClass105_0.<InstallPackage>b__0(Task`1 t) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
Build FAILED.
     at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.Issues.Issue35157.TargetBlankLinkWithDataImageDoesNotCrash() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35157.cs:line 23
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

🟢 With fix — 🖥️ Issue35157: PASS ✅ · 610s
(no coded error found; showing last 1200 chars)

(x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.12]   Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.37]   Discovered:  Controls.TestCases.Android.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 09/04/2026 15:45:23 FixtureSetup for Issue35157(Android)
>>>>> 09/04/2026 15:45:25 TargetBlankLinkWithDataImageDoesNotCrash Start
>>>>> 09/04/2026 15:45:34 TargetBlankLinkWithDataImageDoesNotCrash Stop
  Passed TargetBlankLinkWithDataImageDoesNotCrash [9 s]
NUnit Adapter 4.5.0.0: Test execution complete
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue35157.trx
Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 24.6046 Seconds
>>> TRX_RESULT_FILE: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue35157.trx

📁 Fix files reverted (1 files)

  • src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs

📋 Pre-Flight — Context & Validation

PR #38195 Pre-Flight

Scope

Problem

For a BlazorWebView link such as <a href="https://www.google.com" target="_blank"><img src="data:image/..."></a>, Android reports the image source in WebView.GetHitTestResult().Extra. The current implementation passes that data: URI to an ACTION_VIEW intent. Android has no matching activity and throws ActivityNotFoundException, crashing the app instead of opening the enclosing anchor URL.

Existing PR Approach

The PR changes src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs to:

  1. Detect HitTestResult.SrcImageAnchorType.
  2. Call RequestFocusNodeHref with a custom main-looper Handler to retrieve the enclosing anchor's URL asynchronously.
  3. Route both image-anchor and normal-link URLs through TryOpenInExternalBrowser.
  4. Reject data:, blob:, javascript:, and about: URLs and catch ActivityNotFoundException.

The alternative candidates must use a materially different root-cause mechanism, not merely relocate the same checks.

Diff Boundary

The PR changes six files:

  • Production: src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs (+59/-4)
  • Test host project: src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj
  • Test host page: src/Controls/tests/TestCases.HostApp/Issues/Issue35157.cs (new)
  • Test host startup: src/Controls/tests/TestCases.HostApp/MauiProgram.cs
  • Test host HTML: src/Controls/tests/TestCases.HostApp/wwwroot/index.html (new)
  • UI test: src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35157.cs (new)

Only the production file is a gate-detected fix file and eligible for a try-fix implementation. The test additions remain in place to verify alternatives.

Bounded Validation

Run only:

pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -TestFilter "Issue35157"

This is the detected primary regression test and the only mandatory regression test supplied for STEP 5a. Do not run a full suite, category-wide suite, gate verification, or unrelated tests.

Attempt Limits

Each candidate receives one implementation/test pass and at most one focused correction/retest. It must perform the try-fix skill's inline expert self-review, capture all required artifacts, and restore using only:

pwsh .github/scripts/EstablishBrokenBaseline.ps1 -Restore

The repository contains unrelated pre-existing .github and eng worktree changes. They are out of scope and must remain untouched.

🔬 Code Review — Deep Analysis

Expert PR Evaluation — PR #38195

Verdict: NEEDS_DISCUSSION

Confidence: medium

Independent assessment

The Android production change addresses the crash at its source: SrcImageAnchorType no longer sends the nested image's data: URI directly to an ACTION_VIEW intent. Instead, it asks Android WebView for the focused anchor's href, routes both image-anchor and ordinary-link targets through one launch helper, rejects schemes that should not be dispatched externally, and converts a missing activity handler from an application crash into an unhandled-new-window result.

The implementation is localized to Android BlazorWebChromeClient behavior and introduces no static state or startup-path changes. The main-looper callback keeps Android WebView and activity operations on the UI thread. The trusted Gate establishes that the supplied regression scenario fails without the production fix and passes with it.

Actionable finding

⚠️ The UI test does not prove that external navigation occurred. In src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35157.cs:23, the test taps the image, foregrounds the HostApp, and asserts a survival label that was already present before the tap. This detects the original process-crash failure, but a future implementation that simply suppresses image-anchor navigation would also pass. The test should establish an observable post-tap precondition showing that the external browser/navigation path was reached before it verifies that the HostApp survived.

The raw file:line finding is persisted in inline-findings.json.

Failure-mode probing

  • Ordinary text links: They continue through the same external-launch path, now with scheme filtering and ActivityNotFoundException handling.
  • Image anchors whose focused-node URL is absent or non-launchable: The callback does not dispatch an intent and the new-window request remains unhandled, avoiding the crash.
  • No activity can handle an otherwise launchable URL: ActivityNotFoundException is contained and reported as an unhandled launch rather than escaping through the WebChromeClient callback.
  • Lifecycle/static-state risk: No listener, subscription, or shared state is retained across handler reconnects; the callback and handler are per request.
  • Regression-test false green: If image-anchor intent dispatch stops entirely, the existing label remains visible and the current final assertion can still pass.

Recommendation

Keep the production fix, but harden the focused regression test in one consolidated candidate patch so it demonstrates that the external navigation was triggered rather than only that the HostApp remained alive.

🛠️ Try-Fix — Analysis & Comparison

STEP 5a Try-Fix Aggregate — PR #38195

Candidate 1 — Hit-Test Type Gating

  • Model: gpt-5.3-codex
  • Result: Blocked
  • Approach: Treat Android's hit-test classification as authoritative. Launch an external intent only for a real link-target hit and skip SrcImageAnchorType, preventing the nested image's data: payload from entering the intent path.
  • Difference from PR: The PR asynchronously recovers the enclosing anchor with RequestFocusNodeHref, filters schemes, and catches ActivityNotFoundException. Candidate 1 would avoid URL recovery and block image-anchor intent dispatch at the hit-test boundary.
  • Files changed: None.
  • Test: pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -TestFilter "Issue35157" was not executed.
  • Failure analysis: Mandatory baseline establishment failed because EstablishBrokenBaseline.ps1 rejected the pre-existing dirty .github and eng worktree. Skill constraints therefore prohibited implementation and testing. The required restore command ran and reported No baseline state found / Restored=False, which is expected because baseline state was never created and no candidate edits were made.
  • Inline self-review: 0 findings; there was no candidate diff.
  • Details: ../try-fix-1/content.md
  • Artifacts: attempt-1/

Candidate 2 — Actual New-Window Navigation Interception

  • Model: gpt-5.6-sol
  • Result: Blocked
  • Approach: Accept Android WebView's WebViewTransport from resultMsg, attach a short-lived WebView/WebViewClient, consume Chromium's first real new-window navigation request, and open that request URL externally. This would obtain the enclosing anchor's resolved href from the navigation pipeline rather than from touch metadata.
  • Prior approaches avoided: Unlike the PR, it would not use GetHitTestResult().Extra, SrcImageAnchorType, RequestFocusNodeHref, a URL-scheme blacklist, or a custom Handler. Unlike Candidate 1, it would not suppress image-anchor launches based on hit-test type.
  • Mechanism-level difference: The faulty value originates in hit testing, which identifies the touched image rather than its enclosing anchor. Candidate 2 would stop using hit testing for URL selection and let Chromium expose the actual _blank navigation target through the transported WebView.
  • Files changed: None.
  • Captured diff: Empty.
  • Test: pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -TestFilter "Issue35157" was not executed because it would only have exercised the unchanged PR implementation.
  • Failure analysis: Mandatory baseline establishment again failed with DIRTY WORKING DIRECTORY due to the same pre-existing .github and eng changes. No .github/.baseline-state.json or RevertedFiles allow-list was created, so implementation and testing were prohibited. The exact restore command ran and reported No baseline state found / Restored=False; no candidate edits existed.
  • Inline self-review: 0 findings; there was no candidate diff.
  • Details: ../try-fix-2/content.md
  • Artifacts: attempt-2/

Final Aggregate Status

The bounded maximum of two sequential candidates was reached:

Candidate Mechanism Result Empirical Test
1 (gpt-5.3-codex) Gate intent launch by hit-test type and skip image-anchor payloads Blocked Not run
2 (gpt-5.6-sol) Capture Chromium's actual target through WebViewTransport Blocked Not run
Both candidates are design-only and unvalidated. The mandatory try-fix baseline guard rejected unrelated pre-existing tracked changes before either candidate could modify the sole production target. No candidate code remains applied, both required restore commands completed with the expected verified no-state outcome, and no full or unrelated test suite was run.

📝 PR Finalize — Recommended Title & Description

Assessment: ✏️ Recommend updating — the description is detailed and accurate, but the title retains a [WIP] marker, omits the component separator, and does not follow the [Platform] Component: What changed format.

Recommended title

[Android] BlazorWebView: Fix crash when image links open a new window

Recommended description

### Root cause

When a `target="_blank"` anchor contains an image, Android WebView returns the nested image's `data:` URI through `HitTestResult.Extra`. `BlazorWebChromeClient.OnCreateWindow` then passes that non-launchable URI to an `ACTION_VIEW` intent. Because Android has no matching activity, `StartActivity` throws `ActivityNotFoundException` and crashes the app instead of opening the enclosing anchor URL.

### Description of Change

For `HitTestResult.SrcImageAnchorType`, `BlazorWebChromeClient` now uses `RequestFocusNodeHref` to retrieve the enclosing anchor's actual URL while preserving the existing path for ordinary text links. Both paths use a shared external-launch helper that:

- ignores empty URLs and the clearly non-launchable `data:`, `blob:`, `javascript:`, and `about:` schemes;
- opens launchable URLs with an Android `ACTION_VIEW` intent; and
- handles `ActivityNotFoundException` so a missing activity does not crash the app.

The PR also adds an Android HostApp page and UI regression test for the image-anchor scenario.

### Windows behavior

The scenario behaves differently on Windows because WebView2 receives new-window navigation through `NewWindowRequestedEventArgs.Uri` instead of Android's `HitTestResult.Extra`. Windows therefore does not use the nested image's `data:` URI when resolving the enclosing link. The `https://0.0.0.1/` address is BlazorWebView's internal virtual origin and represents a separate WebView2 new-window routing behavior.

### Test restriction

The regression test is compiled only for Android because it specifically verifies Android WebView's `HitTestResult` and `RequestFocusNodeHref` behavior. Windows uses WebView2, while iOS and Mac Catalyst use WKWebView, so those platforms follow different new-window navigation paths and do not exercise this Android-specific code.

### Platforms validated

- [x] iOS
- [x] Mac Catalyst
- [x] Android
- [x] Windows

### Issues Fixed

Fixes https://github.com/dotnet/maui/issues/35157

### Output recording

| Platform | Before | After |
|---|---|---|
| Android | <video src="https://github.com/user-attachments/assets/be2fa02a-90bf-4413-9c53-7d4a1b12991f"> | <video src="https://github.com/user-attachments/assets/1e46f601-5b66-486f-a7aa-9526e4e67299"> |

🏁 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Winner: pr-plus-reviewer

pr-plus-reviewer retains the submitted Android production fix and closes the expert reviewer's only actionable gap: the regression test now proves that tapping the image actually launches an external activity before checking that the HostApp survived. It passed the required focused Android validation in the prescribed candidate sandbox.

Comparative ranking

Rank Candidate Implementation status Regression evidence Assessment
1 pr-plus-reviewer Implemented in the required sandbox PASSIssue35157, 1/1 test Best balance of correctness and evidence. Preserves the sound PR fix while preventing a no-op navigation implementation from producing a false-green test.
2 pr Submitted implementation PASS — trusted Gate fails without the fix and passes with it Production approach is localized and sound, but the final assertion observes a label that exists before the tap. It detects the original crash but does not independently establish that external navigation occurred.
3 try-fix-2 Design only; blocked before editing Not run Obtaining the URL from a transported WebView's real navigation avoids hit-test metadata, but no diff or empirical evidence exists. It also adds a temporary WebView, transport setup, interception, and lifecycle cleanup for a problem the submitted focused-node API solves more directly.
4 try-fix-1 Design only; blocked before editing Not run Skipping SrcImageAnchorType would avoid the crash by suppressing image-anchor launches rather than opening the enclosing link. The strengthened winning test is specifically designed to reject this false-success behavior.

Expert review reconciliation

The one expert-review pass found no blocking production-code defect. BlazorWebChromeClient.OnCreateWindow detects SrcImageAnchorType, resolves the enclosing anchor through RequestFocusNodeHref, dispatches launchable URLs through one helper, filters clearly non-launchable schemes, and contains ActivityNotFoundException. The change is Android-local, has no startup or shared-state impact, and disposes its per-request Handler.

The review did identify one moderate regression-coverage weakness on the submitted test line. The single consolidated reviewer patch resolves it without altering production behavior or broadening scope.

Validation and uncertainty

  • Trusted Gate for pr: PASS — test fails without the production fix and passes with it.
  • Required validation for pr-plus-reviewer: PASS — exact Android Issue35157 command, 1/1 test.
  • try-fix-1 and try-fix-2: BLOCKED by their baseline guard; neither produced code or ran the regression test.
  • No candidate failed an executed regression test. The two blocked design-only alternatives rank below both empirically passing implementations.

Because the winning test hardening is not present in the submitted PR HEAD, the submitted PR should receive the pr-plus-reviewer change before approval.

📱 UI Tests — WebView

Detected UI test categories: WebView

Deep UI tests — 57 passed, 0 failed across 1 category on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
WebView 57/57 ✓ —
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)
🧭 Next Steps — reviewer changes required

I have addressed mentioned concerns following:

  • Test assertion gap — Added App.WaitForNoElement("Issue35157SurvivalLabel", ...) right after the tap, proving the browser actually opened before re-checking survival. A silent "tap does nothing" regression will now fail the test instead of passing unnoticed.
  • CI build failure (CA1416) — AddMauiBlazorWebView() in MauiProgram.cs wasn't fully scoped to #if ANDROID , breaking iOS/MacCatalyst builds. Fixed by wrapping the full block in the directive.

@vishnumenon2684

Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests , maui-pr-devicetests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).

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

Labels

area-blazor Blazor Hybrid / Desktop, BlazorWebView community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/android s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-win AI found a better alternative fix than the PR s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

<a target="_blank"> with image child <img src="data:image"/> causes app crash on Android in MAUI Blazor Hybrid

5 participants