[Android] Fix for RefreshView triggering pull-to-refresh when scrolling inside a WebView with internal scrollable content#34614
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34614Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34614" |
There was a problem hiding this comment.
Pull request overview
This PR addresses Android-specific RefreshView behavior when wrapping a WebView whose visible scrolling happens inside an internal HTML overflow container (so native WebView scroll state can incorrectly indicate “at top”), causing pull-to-refresh to trigger too early.
Changes:
- Add an Android
WebViewJavaScript bridge + injected observer script to report whether touched DOM content can still scroll up. - Update
MauiSwipeRefreshLayoutto consult the reported “can scroll up” state forWebView(and add intercept logic for gestures starting inside aWebView). - Add a HostApp repro page and an Android UI test for issue #33510.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt | Records the new/changed Android public surface from overrides added in this PR. |
| src/Core/src/Platform/Android/RefreshViewWebViewScrollCapture.cs | Introduces the JS bridge + observer script and exposes TryGetCanScrollUp for RefreshView decisions. |
| src/Core/src/Platform/Android/MauiWebViewClient.cs | Resets scroll capture state on navigation start and injects the observer script on navigation finish. |
| src/Core/src/Platform/Android/MauiWebView.cs | Attaches/detaches the JS interface lifecycle to the native MauiWebView. |
| src/Core/src/Platform/Android/MauiSwipeRefreshLayout.cs | Uses the bridge-reported scrollability for WebView and adds intercept logic for gestures that start in a WebView. |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33510.cs | Adds an Android UI test that validates pull-to-refresh doesn’t trigger until internal web scrolling reaches top. |
| src/Controls/tests/TestCases.HostApp/Issues/Issue33510.cs | Adds a HostApp issue page with a RefreshView + WebView using internal overflow scrolling to reproduce the bug. |
4c83f0d to
b45679f
Compare
|
/azp run maui-pr-uitests , maui-pr-devicetests |
|
Azure Pipelines successfully started running 2 pipeline(s). |
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
1369731 to
94256ab
Compare
I've updated the changes based on the Copilot review suggestions to extend the same fixes to MauiHybridWebView. Specifically: MauiHybridWebView.cs
MauiHybridWebViewClient.cs
PublicAPI.Unshipped.txt
These changes mirror the existing RefreshView/WebView fix pattern and resolve the same scroll-capture issue for HybridWebView inside SwipeRefreshLayout. |
Updated [Magick.NET-Q8-AnyCPU](https://github.com/dlemstra/Magick.NET) from 14.10.4 to 14.12.0. <details> <summary>Release notes</summary> _Sourced from [Magick.NET-Q8-AnyCPU's releases](https://github.com/dlemstra/Magick.NET/releases)._ ## 14.12.0 ### What's Changed - Added `FixByteOrder` to the `DcmReadDefines` (#1976) - Added `IconWriteDefines`. ### Related changes in ImageMagick since the last release of Magick.NET: - Correct bug in `Composite` when using `CopyAlpha` (#1985) - Fixed incorrect orientation of JPEG compressed TIFF images (#1991) - Heap-Buffer-Overflow write of single zero byte when parsing xml (GHSA-cr67-pvmx-2pp2) - Stack Overflow in DestroyXMLTree (GHSA-fwvm-ggf6-2p4x) - Out-of-Bounds read in sample operation (GHSA-pcvx-ph33-r5vv) - Stack Overflow via Recursive FX Expression Parsing (GHSA-f4qm-vj5j-9xpw) - Heap Buffer Overflow in ImageMagick MVG decoder (GHSA-x9h5-r9v2-vcww) - Heap overflow caused by integer overflow/wraparound in viff encoder on 32-bit builds (GHSA-v67w-737x-v2c9) - Stack-buffer-overflow in MNG encoder with oversized pallete (GHSA-98cp-rj9f-6v5g) - Integer overflow in despeckle operation causes heap buffer overflow on 32-bit builds (GHSA-26qp-ffjh-2x4v) - Off-by-One in MSL decoder could result in crash (GHSA-5xg3-585r-9jh5) - Heap buffer overflow when encoding JXL image with a 16-bit float (GHSA-jvgr-9ph5-m8v4) - Heap-use-after-free via XMP profile could result in a crash when printing the values (GHSA-r83h-crwp-3vm7) - Heap buffer overflow (WRITE) in the YAML and JSON encoders (GHSA-5592-p365-24xh) - Heap out-of-bounds write in JP2 encoder (GHSA-pwg5-6jfc-crvh) ### Library updates: - ImageMagick 7.1.2-19 (2026-04-12) - aom 3.13.3 (2026-04-02) - openexr 3.4.9 (2026-04-03) - freetype 2.14.3 (2026-03-22) - gdk-pixbuf 2.44.6 (2026-03-31) - harfbuzz 14.0.0 (2026-04-01) - liblzma 5.8.3 (2026-04-31) - libpng 1.6.56 (2026-03-25) **Full Changelog**: dlemstra/Magick.NET@14.11.1...14.12.0 ## 14.11.1 ### Related changes in ImageMagick since the last release of Magick.NET: - Stack-buffer-overflow WRITE in InterpretImageFilename due to overflow (GHSA-8793-7xv6-82cf) ### Library updates: - ImageMagick 7.1.2-18 (2026-03-23) - aom 3.13.2 (2026-03-19) - openexr 3.4.7 (2026-03-15) - harfbuzz 13.2.1 (2026-03-19) **Full Changelog**: dlemstra/Magick.NET@14.11.0...14.11.1 ## 14.11.0 ### What's Changed - Added `DcmReadDefines`. ### Related changes in ImageMagick since the last release of Magick.NET: - Access mode change for files created from 0666 to 0600 (ImageMagick/ImageMagick#8609) - Heap-buffer-overflow in NewXMLTree could result in crash (GHSA-gc62-2v5p-qpmp) ### Library updates: - ImageMagick 7.1.2-17 (2026-03-16) - openexr 3.4.6 (2026-03-01) - freetype 2.14.2 (2026-03-01) - harfbuzz 13.0.1 (2026-03-07) - libxml2 2.15.2 (2026-03-03) **Full Changelog**: dlemstra/Magick.NET@14.10.4...14.11.0 Commits viewable in [compare view](dlemstra/Magick.NET@14.10.4...14.12.0). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/dotnet/maui/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…t#35333) Bump OpenTelemetry packages to latest stable versions in the maui-aspire-servicedefaults template: - OpenTelemetry.Exporter.OpenTelemetryProtocol: 1.9.0 to 1.15.3 - OpenTelemetry.Extensions.Hosting: 1.9.0 to 1.15.3 - OpenTelemetry.Instrumentation.Http: 1.9.0 to 1.15.1 - OpenTelemetry.Instrumentation.Runtime: 1.9.0 to 1.15.1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This pull request updates the following dependencies [marker]: <> (Begin:a71c12d9-5aa4-4b46-e2d6-08da0cf8cd95) ## From https://github.com/dotnet/xharness - **Subscription**: [a71c12d9-5aa4-4b46-e2d6-08da0cf8cd95](https://maestro.dot.net/subscriptions?search=a71c12d9-5aa4-4b46-e2d6-08da0cf8cd95) - **Build**: [20260430.4](https://dev.azure.com/dnceng/internal/_build/results?buildId=2964906) ([312724](https://maestro.dot.net/channel/2/github:dotnet:xharness/build/312724)) - **Date Produced**: May 1, 2026 7:05:11 AM UTC - **Commit**: [92962e5c46ac08a66ded4c5696209cc60f1a232f](dotnet/xharness@92962e5) - **Branch**: [main](https://github.com/dotnet/xharness/tree/main) [DependencyUpdate]: <> (Begin) - **Dependency Updates**: - From [11.0.0-prerelease.26229.1 to 11.0.0-prerelease.26230.4][1] - Microsoft.DotNet.XHarness.CLI - Microsoft.DotNet.XHarness.TestRunners.Common - Microsoft.DotNet.XHarness.TestRunners.Xunit [1]: dotnet/xharness@9d5a7e9...92962e5 [DependencyUpdate]: <> (End) [marker]: <> (End:a71c12d9-5aa4-4b46-e2d6-08da0cf8cd95) Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Description Replaces `review-rules.md` (flat 345-line checklist) with a dimensional expert review agent. Single source of truth for all review rules, organized into 30 dimensions for per-dimension sub-agent evaluation. Adds inline file:line PR comments alongside the existing wall-of-text summary. Extracted from 28k review comments across 5 maintainers via [extraction-pipeline](https://github.com/dotnet/fsharp/blob/main/.github/agents/extraction-pipeline.md). No functional code changes. Recreated from dotnet#35062 on a dotnet/maui branch (originally opened from a fork). ## What changed **Before:** `review-rules.md` had 345 lines of flat rules. `code-review` skill loaded them all into one context. Output was a single wall-of-text PR comment. **After:** Rules absorbed into `maui-expert-reviewer.md` as 30 dimensions with 200+ CHECK items. Each dimension runs as an independent sub-agent with focused context. Output is inline file:line PR comments via `inline-findings.json`. ## CI Flow ``` Review-PR.ps1 prompt: 1. code-review → maui-expert-reviewer agent → inline-findings.json 2. pr-review → Pre-Flight → Try-Fix → Report (sees findings, no duplication) Posting: post-inline-review.ps1 → .json → GitHub file:line comments (NEW) post-ai-summary-comment.ps1 → {phase}/content.md → wall-of-text (existing) CI: COMMENTS_VIA_FILE=true → agent writes .json, script posts Local: agent writes .json, code-review posts directly via gh api ``` ## Files | Action | File | What | |--------|------|------| | **Add** | `agents/maui-expert-reviewer.md` | 30 dimensions, 200+ CHECKs, routing table | | **Add** | `instructions/collectionview-{android,ios,windows}` | Platform-isolated CV rules | | **Add** | `instructions/{handler-patterns,layout-system,performance-hotpaths,public-api,threading-async}` | Domain-specific ambient guidance | | **Add** | `scripts/post-inline-review.ps1` | Posts .json as GitHub PR review | | **Del** | `skills/code-review/references/review-rules.md` | Absorbed into agent | | **Mod** | `skills/code-review/SKILL.md` | Delegates to agent | | **Mod** | `scripts/Review-PR.ps1` | Prompt + inline posting wiring | | **Mod** | `eng/pipelines/ci-copilot.yml` | `COMMENTS_VIA_FILE` env var | --------- Co-authored-by: kubaflo <kubaflo@users.noreply.github.com> Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tomas Grosup <tomasgrosup@microsoft.com>
…View2 is not connected in Appium. (dotnet#35335) ### Description of Changes - Recently, the Appium driver has not been connecting properly to the native WebView2 control on Windows. While running locally using Appium Inspector with the WebView control, the inspector is unable to recognize the WebView and displays an error. - Due to this Appium driver issue, the WebView lane in CI takes a long time to run (approximately 3 hours) and eventually gets cancelled. As a temporary workaround, the WebView lane has been temporarily removed from the Windows CI pipeline to allow the CI process to complete more quickly. <img width="649" height="294" alt="image" src="https://github.com/user-attachments/assets/68df006b-56d6-4bfa-870a-a4184f5b18b7" /> <img width="576" height="430" alt="image" src="https://github.com/user-attachments/assets/40c222e8-4935-450d-be7e-5ee9245e9eb1" /> **Issue:** dotnet#35334
… WebView drag gestures when internal DOM content can still scroll up.
…e code changes based on AI summary.
…WebView, optimized the implementation, and resolved the related issues as well.
bafd7ec to
7462491
Compare
🤖 AI Summary
📊 Review Session —
|
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🖥️ Issue33510 Issue33510 |
✅ FAIL — 870s | ✅ PASS — 1252s |
🔴 Without fix — 🖥️ Issue33510: FAIL ✅ · 870s
Determining projects to restore...
Restored /home/vsts/work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 502 ms).
Restored /home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 6.83 sec).
Restored /home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 6.38 sec).
Restored /home/vsts/work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 169 ms).
Restored /home/vsts/work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 8 ms).
Restored /home/vsts/work/1/s/src/Essentials/src/Essentials.csproj (in 20 ms).
Restored /home/vsts/work/1/s/src/Core/src/Core.csproj (in 57 ms).
Restored /home/vsts/work/1/s/src/Core/maps/src/Maps.csproj (in 24 ms).
Restored /home/vsts/work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 1.48 sec).
Restored /home/vsts/work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 32 ms).
Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 1.32 sec).
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:09:40.53
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Determining projects to restore...
Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 1.39 sec).
Restored /home/vsts/work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 51 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 6.01 sec).
Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.Android.Tests/Controls.TestCases.Android.Tests.csproj (in 7.51 sec).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 3 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 17 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 632 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 3.63 sec).
5 of 13 projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
[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.18] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.66] 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
>>>>> 05/13/2026 19:10:22 FixtureSetup for Issue33510(Android)
>>>>> 05/13/2026 19:10:27 PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown Start
>>>>> 05/13/2026 19:10:40 PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown Stop
>>>>> 05/13/2026 19:10:40 Log types: logcat, bugreport, server
Failed PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown [14 s]
Error Message:
RefreshView should not trigger while WebView content is not at the top.
Assert.That(App.FindElement(StatusLabel).GetText(), Does.Not.Contain("Refresh triggered"))
Expected: not String containing "Refresh triggered"
But was: "Refresh triggered"
Stack Trace:
at Microsoft.Maui.TestCases.Tests.Issues.Issue33510.PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33510.cs:line 44
1) at Microsoft.Maui.TestCases.Tests.Issues.Issue33510.PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33510.cs:line 44
NUnit Adapter 4.5.0.0: Test execution complete
Total tests: 1
Test Run Failed.
Failed: 1
Total time: 1.5285 Minutes
🟢 With fix — 🖥️ Issue33510: PASS ✅ · 1252s
(truncated to last 15,000 chars)
ramework=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: --- 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 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 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.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.
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: Mono.AndroidTools.InstallFailedException: Unexpected install output: cmd: Failure calling service package: Broken pipe (32) [/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: [/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.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.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: 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: --- 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 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 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.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]
0 Warning(s)
1 Error(s)
Time Elapsed 00:10:02.26
* daemon not running; starting now at tcp:5037
* daemon started successfully
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:08:29.59
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14089110
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
[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.13] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.31] 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
>>>>> 05/13/2026 19:31:22 FixtureSetup for Issue33510(Android)
>>>>> 05/13/2026 19:31:27 PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown Start
>>>>> 05/13/2026 19:31:37 PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown Stop
Passed PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown [10 s]
NUnit Adapter 4.5.0.0: Test execution complete
Test Run Successful.
Total tests: 1
Passed: 1
Total time: 31.6866 Seconds
📁 Fix files reverted (6 files)
src/Core/src/Platform/Android/MauiHybridWebView.cssrc/Core/src/Platform/Android/MauiHybridWebViewClient.cssrc/Core/src/Platform/Android/MauiSwipeRefreshLayout.cssrc/Core/src/Platform/Android/MauiWebView.cssrc/Core/src/Platform/Android/MauiWebViewClient.cssrc/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
New files (not reverted):
src/Core/src/Platform/Android/RefreshViewWebViewScrollCapture.cs
🧪 UI Tests — RefreshView,ViewBaseTests
Detected UI test categories: RefreshView,ViewBaseTests
✅ Deep UI tests — 198 passed, 0 failed across 2 categories on platform-pool agent (replaces in-process counts above).
🧪 UI Test Execution Results (deep, platform pool)
| Category | Tests | Snapshot diffs |
|---|---|---|
controls-RefreshView |
40/40 ✓ | — |
controls-ViewBaseTests |
158/159 ✓ | — |
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs) |
🔍 Pre-Flight — Context & Validation
Issue: #33510 — [Android] RefreshView triggers pull-to-refresh immediately when scrolling up inside a WebView
PR: #34614 — [Android] Fix for RefreshView triggering pull-to-refresh when scrolling inside a WebView with internal scrollable content
Platforms Affected: Android
Files Changed: 7 implementation, 2 test (647 additions, 1 deletion)
Problem Summary
Android RefreshView wraps a WebView. When the WebView shows web content scrolled by an internal HTML container (overflow-y: auto div), the native WebView.ScrollY / CanScrollVertically(-1) may report the WebView itself is at the top while the inner DOM element is mid-scroll. SwipeRefreshLayout.canChildScrollUp() therefore returns false and intercepts the upward swipe as pull-to-refresh, breaking inner scroll.
PR's Fix Architecture
- JS-bridge observer (
RefreshViewWebViewScrollCapture.cs, 228 LOC): An[JavascriptInterface]namedmauiRefreshViewHostis injected into the WebView when (and only when) it is inside aMauiSwipeRefreshLayout. A capture-phasetouchstart/touchmovelistener walks the touched DOM element's ancestors to find the nearest scrollable element, and reports itsscrollTop > 0to native viasetCanScrollUp(bool). - MauiWebView / MauiHybridWebView:
OnAttachedToWindowcallsAttach(this)and re-injects the observer for late-attach.OnDetachedFromWindow/DisposecallsDetach.MauiWebViewClient.OnPageStartedresets state;OnPageFinishedre-injects observer (gated byIsAttached). - MauiSwipeRefreshLayout: New
OnInterceptTouchEventoverride hit-tests the touch position to find the WebView, snapshotsScrollCaptureStateonACTION_DOWN, and forwards/blocks intercept based on the cachedCanScrollUpflag (volatile, no JNI per-frame). OnACTION_MOVE, releases ownership back to RefreshView once inner scroll reaches the top mid-gesture. Multi-touch (POINTER_DOWN) cancels the guard. - State lifecycle:
ScrollCaptureState : Java.Lang.Objectusesvolatile bool _detachedso in-flight JNI callbacks become no-ops after detach instead of touching disposed objects.MarkDetachedruns beforeRemoveJavascriptInterface.
Key Findings
- The PR's approach is functionally necessary because inner DOM scroll is not observable from the native side without JS — there is no Android API that reports CSS
overflow-yscroll position. - Heavyweight: 647 LOC across 7 files; introduces a JS bridge, capture-phase event listeners, hit-testing, and a parallel touch-intercept state machine in
MauiSwipeRefreshLayout. - Adds 7 new public API surface entries (
MauiWebView.OnDetachedFromWindow,MauiHybridWebView.OnAttachedToWindow/OnDetachedFromWindow/Dispose,MauiHybridWebViewClient.OnPageStarted/OnPageFinished,MauiSwipeRefreshLayout.OnInterceptTouchEvent). - Inline review thread feedback already addressed iteratively: gating injection so it only runs when inside a RefreshView, mid-gesture re-evaluation on
ACTION_MOVE,ScrollX/ScrollYcorrection in hit-test, deferred GC forScrollCaptureState, late-attach re-injection, named JS handlers to support Detach/re-attach. - Gate already verified ✅: test fails without fix, passes with fix.
Code Review Summary
Verdict: NEEDS_DISCUSSION (alternative approaches worth exploring)
Confidence: medium
Errors: 0 | Warnings: 2 | Suggestions: 3
Key code review findings:
⚠️ Public API surface increase (7 entries) — large surface for a niche fix. Could mark some asinternaloverrides where possible.⚠️ Capture-phasetouchstart/touchmovelisteners on every page load run JS on every user touch, even outside the RefreshView gesture window. Performance impact unmeasured.- 💡
evaluateJavascriptofObserverScript(~80 lines minified) on everyOnPageFinishedmay delay JS execution on slow devices. - 💡
MauiHybridWebViewinherits the same fix path — consistent coverage, but doubles the attack surface for regression. - 💡
FindWebViewrecurses on everyOnInterceptTouchEvent— fine for typical layouts, but O(N) per gesture frame onACTION_DOWNonly (acceptable).
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #34614 | Persistent JS observer with capture-phase touch listeners + native touch-intercept state machine | ✅ Gate PASSED | 7 impl, 2 test | Original PR — heavyweight (647 LOC), large public API |
| 1 | try-fix-1 | NestedScrolling delegation: override MauiWebView.OnTouchEvent to call Parent.RequestDisallowInterceptTouchEvent on ACTION_DOWN and release on UP based on ScrollY deltas, no JS |
See try-fix-1 | 1–2 impl | Limitation: cannot detect inner-DOM-only scroll |
| 2 | try-fix-2 | On-demand JS evaluation only at gesture start (evaluateJavascript callback in OnInterceptTouchEvent) — no persistent observer, no touch listeners |
See try-fix-2 | 2–3 impl | Async result arrives mid-gesture; needs short defer window |
| 3 | try-fix-3 | Minimal JS observer that only updates a single window.__mauiCanScrollUp value via scroll/touchstart (no native callback per move) + native polls via evaluateJavascript once on ACTION_DOWN |
See try-fix-3 | 3–4 impl | Reduced JS overhead vs PR; still needs JS bridge for sync read |
🔧 Fix — Analysis & Comparison
try-fix aggregate: alternatives to PR #34614
PR #34614 fixes #33510 (Android RefreshView + WebView with inner-DOM
overflow-y scroll triggers spurious pull-to-refresh) by installing a persistent
JS observer in every WebView hosted inside a RefreshView. The observer registers
capture-phase touchstart/touchmove listeners that walk to the nearest CSS-scrollable
ancestor and push setCanScrollUp(scrollTop > 0) across a [JavascriptInterface]-decorated
Java.Lang.Object (ScrollCaptureState) into native code; MauiSwipeRefreshLayout
then reads a volatile bool per ACTION_MOVE.
This document compares three architecturally distinct alternatives.
Comparison table
| Criterion | PR (persistent JS push) | try-fix-1: Native NestedScrolling | try-fix-2: On-demand JS at DOWN | try-fix-3: Minimal JS flag + native poll |
|---|---|---|---|---|
| Fixes inner-DOM scroll bug | ✅ | ❌ (no native signal for CSS overflow scroll) | ✅ | ✅ |
| Pull-to-refresh still works at top | ✅ | ✅ | ||
| No regression for non-WebView children | ✅ | ✅ | ✅ | ✅ |
| Covers MauiHybridWebView | ✅ | ✅ | ✅ | ✅ |
| Survives Shell tab-switch | ✅ (Detach+re-Attach + re-inject) | ✅ (no shared state) | ✅ (no persistent state) | ✅ (idempotent script) |
| Late-attach safe | ✅ | ✅ | ✅ | ✅ |
| GC / JNI-callback safe | MarkDetached + volatile fields + "do NOT Dispose because V8 may hold ref") |
✅ (no JNI bridge) | ✅ (transient IValueCallback only) |
✅ (transient IValueCallback only) |
| Public API surface | +7 unshipped | −7 (smaller) | −7 (smaller) | −7 (smaller) |
| JS / runtime overhead | every touch frame, page-lifetime listeners + JNI hop | zero JS | one EvaluateJavascript per ACTION_DOWN, no listeners |
two listeners (write primitive only) + one EvaluateJavascript per ACTION_DOWN |
Gate test (Issue33510) — analytical |
PASS | FAIL | PARTIAL → likely PASS | PASS |
| LOC delta vs main | +780 | ~+25 (huge deletion) | ~+90 (large deletion) | ~+110 (moderate deletion) |
Recommendation
Stick with the PR's approach. The PR has the strongest correctness guarantee on
the gate test — it has the answer ready before ACTION_DOWN fires, so it cannot
miss a fast first-touch flick the way candidates B and C can on cold V8. Its main
downsides (the ScrollCaptureState : Java.Lang.Object lifecycle, +7 public API,
per-touch JNI) are all real but are correctly mitigated in the PR (volatile
MarkDetached+ idempotent script + capped JNI to a single boolean write).
However, if reviewers want to reduce the API/JNI surface area without losing fix
quality, try-fix-3 is the next-best option: it preserves the push-based DOM
observation so the answer is current (no per-touch DOM walk like candidate B), but
deletes the [JavascriptInterface] bridge and all 7 unshipped public-API entries.
The single residual gap — the very first gesture after late-attach into a never-touched
inner div — is narrower than the steady-state cost of the PR's persistent JNI bridge,
and recompute(document.body) at install time covers the document-scroll case.
try-fix-1 should not be merged on its own (does not fix the bug), but it is a
useful demonstration of why the PR's complexity exists: Android exposes no native
signal for inner CSS-overflow scroll, so any honest fix must consult the JS context
in some form.
Honest evaluation note
Android device-test execution was not possible in this Linux build agent (gate
already PASSED for the PR's existing fix). All candidate evaluations above are
analytical against the semantics of the gate test in Issue33510.cs and the
acceptance criteria stated in the task. The races in candidates B and C are real but
narrow (typically 5–30 ms on warm V8) and would only manifest as a single missed
pull-to-refresh on the very first fast flick — never as the false-positive symptom
that issue #33510 reports. None of the candidates introduce a regression worse than
the bug they are meant to fix.
📋 Report — Final Recommendation
PR #34614 — Comparative Candidate Report
Issue: #33510 — [Android] RefreshView triggers pull-to-refresh immediately when scrolling up inside a WebView with inner CSS-overflow content.
Platform under test: Android.
Gate: ✅ already PASSED for the PR's existing fix (test fails without fix, passes with fix). Not re-run.
Candidates evaluated
| # | Candidate | Source | Approach | Public API delta | Gate | Build | Notes |
|---|---|---|---|---|---|---|---|
| 1 | pr |
PR #34614 as submitted | Persistent JS observer ([JavascriptInterface] ScrollCaptureState : Java.Lang.Object) + capture-phase touchstart/touchmove listeners + native OnInterceptTouchEvent state machine in MauiSwipeRefreshLayout. |
+7 unshipped | ✅ PASS | ✅ | Heavyweight (647 LOC). Has the correctness answer ready before ACTION_DOWN. |
| 2 | pr-plus-reviewer |
pr + maui-expert-reviewer feedback |
Same architecture as PR. Reviewer additionally: clears cached gesture refs in MauiSwipeRefreshLayout.OnDetachedFromWindow (closes a Shell-tab-switch GREF retention window), adds a positive-case test, sorts PublicAPI.Unshipped.txt alphabetically. |
+7 unshipped (sorted) | ✅ PASS (analytical — additive only) | ✅ | Strict superset of pr. No behavior change on the original repro; closes regression-test gap and a small memory-hygiene issue. |
| 3 | try-fix-1 |
Native NestedScrolling delegation in MauiWebView.OnTouchEvent (no JS, no OnInterceptTouchEvent override). |
None | ❌ FAIL (analytical) | ✅ | Cannot detect inner-DOM CSS-overflow scroll — Android exposes no native signal for it. Does not fix #33510. | |
| 4 | try-fix-2 |
On-demand evaluateJavascript from OnInterceptTouchEvent ACTION_DOWN, no persistent observer. |
None | ✅ | Async result arrives mid-gesture; race window vs first fast flick. Removes JNI bridge & 7 API entries. | ||
| 5 | try-fix-3 |
Minimal JS observer that writes window.__mauiCanScrollUp only (no JavascriptInterface); native polls once per ACTION_DOWN via evaluateJavascript. |
−7 (removes all PR-added entries) | ✅ PASS (analytical) | ✅ | Hybrid: push-based JS freshness + on-demand transport. Eliminates entire Java.Lang.Object/V8-GC class of bugs that the PR explicitly works around. |
Ranking criteria
Per task constraint: candidates that fail regression tests rank below candidates that pass. Within the passing tier, ranked on: (a) correctness on edge cases, (b) lifecycle / GC safety, (c) API surface / blast radius, (d) test quality.
Ranking
| Rank | Candidate | Tier | Rationale |
|---|---|---|---|
| 1 | pr-plus-reviewer |
PASS | Strict superset of pr. Closes the only material regression-test gap (no positive-case assertion) and a small Shell-tab-switch GREF retention window. Same correctness profile as pr on the gate. |
| 2 | pr |
PASS | Strongest correctness on the gate test — answer is ready before ACTION_DOWN, no race on first flick. Mitigations for JS-bridge lifetime are present and correctly ordered (MarkDetached before RemoveJavascriptInterface). |
| 3 | try-fix-3 |
PASS | Materially smaller API/JNI surface. Removes the PR's most fragile piece (ScrollCaptureState : Java.Lang.Object with explicit "do NOT Dispose" comment). Slightly more brittle on first late-attach gesture (mitigated by recompute(document.body) at install). |
| 4 | try-fix-2 |
PARTIAL | Same async-callback race but without a pre-computed JS-side flag — the DOM walk happens during the race window. Higher risk of missed first-flick than try-fix-3. |
| 5 | try-fix-1 |
FAIL | Does not fix the bug. Useful only as proof that any honest fix must consult JS. Ranked last per the "failed candidates rank lower" rule. |
Why pr-plus-reviewer wins over pr
The reviewer's applied changes are:
- Test gap closed. A new
PullToRefreshShouldTriggerWhenWebViewIsAtToptest asserts the positive case. Without it, a regression that causesMauiSwipeRefreshLayoutto always block pull-to-refresh inside a WebView would still pass the existing test. OnDetachedFromWindowclears_activeTouchWebView/_activeTouchScrollState. Closes a Shell-tab-switch window where the refresh layout's staleJava.Lang.Objectreference can keep the WebView's GREF alive past expected lifetime.PublicAPI.Unshipped.txtsorted alphabetically — consistency with repo convention.
These are additive and non-controversial. They preserve the PR's gate-passing behavior (the changes don't touch the touch state machine or the JS bridge) and they ship a strictly more defensible test surface. Build verified ✅ on net10.0-android36.0.
Why not try-fix-3 over pr-plus-reviewer
try-fix-3 is architecturally cleaner and removes 7 unshipped public-API entries plus the ScrollCaptureState : Java.Lang.Object lifetime hazard. It is the strongest alternative design. However:
- The PR has already shipped the heavyweight design through multiple inline-review iterations (gating, mid-gesture re-eval, late-attach, idempotent injection). That review work is sunk cost only if we ship something else.
try-fix-3keeps a small but real first-late-attach race that the PR does not have.- All
try-fix-*candidates were evaluated analytically only — no Android device available on this Linux build agent. Replacing the PR's gate-verified fix with an analytical-only redesign on a release-affecting bug is a worse risk profile than shipping the PR with reviewer touch-ups. - The reviewer correctly notes that the +7 public API entries are not avoidable by changing visibility — Java-base virtuals (
OnAttachedToWindow,OnDetachedFromWindow,OnPageStarted,OnPageFinished,OnInterceptTouchEvent) requirepublicaccessibility from C# wrappers.
If a follow-up cleanup PR wants to migrate to try-fix-3's simpler model later, the PR's tests will still serve as regression coverage. That refactor should not block this fix.
Inline reviewer findings
Raw findings written to CustomAgentLogsTmp/PRState/34614/PRAgent/inline-findings.json (16 items: 0 errors, 3 warnings, ~9 suggestions, 4 nits). Headline items:
- W: Test only asserts negative case (test gap — addressed in
pr-plus-reviewer). - W:
OnInterceptTouchEventdoes not handleMotionEventActions.PointerUp— multi-touch state-machine edge case. - W:
FindWebViewignoresTranslationX/Y; should useView.GetHitRectfor transform-aware hit-test. - S: Capture-phase JS listeners run on every touch (pre-flight W2 confirmed) — left for human discussion.
- S:
InjectObserverruns on everyOnPageFinished— pre-flight S1 confirmed. - S: Comment-only nits on
CanScrollUpViewByTypeWebView fall-through and discardedbase.OnInterceptTouchEventreturn.
Pre-flight W1 (public-API growth) is reclassified as moot: the entries are mandatory because the C# overrides wrap Java public virtuals.
Honest evaluation note
Android device-test execution is not feasible on this Linux build agent. Gate already PASSED for the PR's existing fix. pr-plus-reviewer is evaluated as a strict additive superset of pr and verified to build on net10.0-android36.0. All try-fix-* candidates are evaluated analytically against the gate's semantics in Issue33510.cs. The races flagged for try-fix-2/try-fix-3 are real but narrow (5–30 ms on warm V8) and would manifest as a single missed pull-to-refresh on a fast first flick — never as the false-positive symptom that issue #33510 reports.
Winner
pr-plus-reviewer — apply the reviewer's three additive touch-ups on top of the PR. isPRFix = true.
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!
Issue Details
When using a RefreshView that wraps a WebView in a .NET MAUI app on Android, the pull-to-refresh gesture is triggered as soon as the user scrolls up, even if the WebView content has not reached the top. This prevents normal upward scrolling through web content without accidentally refreshing the page.
Root Cause
The issue occurs because of how Android RefreshView determines whether to intercept a downward drag gesture. For a WebView, this decision is based on the native scroll state (ScrollY / CanScrollVertically(-1)).
In this scenario, the visible content is not scrolled by the native WebView itself, but by an internal HTML container (overflow-y: auto). While this internal DOM element is still mid-scroll, the native WebView may incorrectly report that it is already at the top.
As a result, RefreshView intercepts the gesture too early, triggering pull-to-refresh instead of allowing the web content to continue scrolling.
Description of Change
The fix involves adding Android-specific handling for the WebView + RefreshView interaction.
A lightweight WebView bridge is introduced to determine whether the touched DOM content can still scroll upward. MauiSwipeRefreshLayout uses this information when a gesture starts inside a WebView:
This approach preserves existing RefreshView behavior for other controls, while correctly handling the WebView scenario that native Android scroll checks cannot accurately detect.
Validated the behavior in the following platforms
Issues Fixed
Fixes #33510
Output ScreenShot
BeforeFix-33510.mov
AfterFix-33510.mov