[WIP] [Android]Fix for Android BlazorWebView crash when opening image links in a new window - #38195
[WIP] [Android]Fix for Android BlazorWebView crash when opening image links in a new window#38195BagavathiPerumal wants to merge 2 commits into
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 38195Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 38195" |
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
|
/azp run maui-pr-uitests , maui-pr-devicetests |
|
Azure Pipelines: Successfully started running 2 pipeline(s). |
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 1 findings
See inline comments for details.
MauiBot
left a comment
There was a problem hiding this comment.
AI Review Summary
@BagavathiPerumal — new AI review results are available based on commit
29363f3.
🗂️ 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.
Issue35157failed without the production fix and passed with it. Do not rerun gate verification and do not modifygate/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:
- Detect
HitTestResult.SrcImageAnchorType. - Call
RequestFocusNodeHrefwith a custom main-looperHandlerto retrieve the enclosing anchor's URL asynchronously. - Route both image-anchor and normal-link URLs through
TryOpenInExternalBrowser. - Reject
data:,blob:,javascript:, andabout:URLs and catchActivityNotFoundException.
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 -RestoreThe 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
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
ActivityNotFoundExceptionhandling. - 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:
ActivityNotFoundExceptionis 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'sdata:payload from entering the intent path. - Difference from PR: The PR asynchronously recovers the enclosing anchor with
RequestFocusNodeHref, filters schemes, and catchesActivityNotFoundException. 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.ps1rejected the pre-existing dirty.githubandengworktree. Skill constraints therefore prohibited implementation and testing. The required restore command ran and reportedNo 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
WebViewTransportfromresultMsg, 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 resolvedhreffrom 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 customHandler. 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
_blanknavigation 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 DIRECTORYdue to the same pre-existing.githubandengchanges. No.github/.baseline-state.jsonorRevertedFilesallow-list was created, so implementation and testing were prohibited. The exact restore command ran and reportedNo 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 | PASS — Issue35157, 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 AndroidIssue35157command, 1/1 test. try-fix-1andtry-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.
I have addressed mentioned concerns following:
|
|
/azp run maui-pr-uitests , maui-pr-devicetests |
|
Azure Pipelines: Successfully started running 2 pipeline(s). |
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
Issues Fixed
Fixes #35157
Output ScreenShot
35157-BeforeFix.mov
35157-AfterFix.mov