[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). |
… WebView drag gestures when internal DOM content can still scroll up.
e6c0b52 to
b0384b3
Compare
…e code changes based on AI summary.
|
/azp run maui-pr-uitests , maui-pr-devicetests |
|
Azure Pipelines successfully started running 2 pipeline(s). |
🤖 AI Summary
📊 Review Session —
|
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🖥️ Issue33510 Issue33510 |
✅ FAIL — 760s |
🔴 Without fix — 🖥️ Issue33510: FAIL ✅ · 760s
Determining projects to restore...
Restored /home/vsts/work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 6.23 sec).
Restored /home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 6.25 sec).
Restored /home/vsts/work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 180 ms).
Restored /home/vsts/work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 11 ms).
Restored /home/vsts/work/1/s/src/Essentials/src/Essentials.csproj (in 15 ms).
Restored /home/vsts/work/1/s/src/Core/src/Core.csproj (in 38 ms).
Restored /home/vsts/work/1/s/src/Core/maps/src/Maps.csproj (in 376 ms).
Restored /home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 686 ms).
Restored /home/vsts/work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 33 ms).
Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 316 ms).
1 of 11 projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
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.70-ci+azdo.13907303
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
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.70-ci+azdo.13907303
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
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.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.70-ci+azdo.13907303
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
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.70-ci+azdo.13907303
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
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.70-ci+azdo.13907303
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.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
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
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:06:59.99
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 923 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 833 ms).
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 1.84 sec).
Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 4.77 sec).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 2.92 sec).
Restored /home/vsts/work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 5 ms).
Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.Android.Tests/Controls.TestCases.Android.Tests.csproj (in 2.88 sec).
5 of 13 projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
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.70-ci+azdo.13907303
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
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.70-ci+azdo.13907303
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
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
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.47] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.88] 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
>>>>> 04/22/2026 13:56:05 FixtureSetup for Issue33510(Android)
>>>>> 04/22/2026 13:56:09 PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown Start
>>>>> 04/22/2026 13:56:45 PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown Stop
>>>>> 04/22/2026 13:56:45 Log types: logcat, bugreport, server
Failed PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown [37 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 45
1) at Microsoft.Maui.TestCases.Tests.Issues.Issue33510.PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33510.cs:line 45
NUnit Adapter 4.5.0.0: Test execution complete
Total tests: 1
Failed: 1
Test Run Failed.
Total time: 2.4898 Minutes
🟢 With fix — 🖥️ Issue33510: ⚠️ ENV ERROR · 1341s
(truncated to last 15,000 chars)
/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:26.03
* 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.70-ci+azdo.13907303
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
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.70-ci+azdo.13907303
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
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.70-ci+azdo.13907303
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
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.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.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.70-ci+azdo.13907303
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.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.70-ci+azdo.13907303
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.70-ci+azdo.13907303
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
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
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
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
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:08:34.09
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.70-ci+azdo.13907303
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13907303
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.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.70-ci+azdo.13907303
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.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
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.42] 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
>>>>> 04/22/2026 14:18:03 FixtureSetup for Issue33510(Android)
>>>>> 04/22/2026 14:18:06 PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown Start
>>>>> 04/22/2026 14:18:42 PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown Stop
>>>>> 04/22/2026 14:18:42 Log types: logcat, bugreport, server
Failed PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown [35 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 45
1) at Microsoft.Maui.TestCases.Tests.Issues.Issue33510.PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33510.cs:line 45
NUnit Adapter 4.5.0.0: Test execution complete
Test Run Failed.
Total tests: 1
Failed: 1
Total time: 56.1635 Seconds
⚠️ Issues found
⚠️ Issue33510 with fix:Exception calling "Matches" with "2" argument(s): "Value cannot be null. (Parameter 'input')"
📁 Fix files reverted (4 files)
src/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
🔍 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 (fix), All (tests validate Android only)
Files Changed: 4 implementation, 3 test (2 UI test files + 1 HostApp page)
Key Findings
- Root cause: Android's native
WebView.ScrollY/CanScrollVertically(-1)don't reflect inner HTML overflow containers (overflow-y: auto), causingSwipeRefreshLayoutto incorrectly assume the content is at the top and intercept gestures too early - Fix approach: JS bridge (
AddJavascriptInterface) injected onOnPageFinished, reporting DOM scroll state back to C# viavolatile boolfields;MauiSwipeRefreshLayout.OnInterceptTouchEventuses this info to defer gesture interception while WebView content is still scrollable - Bridge is only attached when
MauiWebViewdetects it's inside aMauiSwipeRefreshLayout— avoids polluting standalone WebViews - Three previous Copilot review comments (unconditional injection, unconditional JS interface attachment, no mid-gesture re-evaluation) have all been addressed
- CI is actively failing for Android UITests
RadioButton,RefreshViewon both Mono and CoreClr — the most likely cause is the external URL dependency in the test - Prior maintainer review (kubaflo, dismissed) and prior Copilot automated review (fixed suggestions)
Code Review Summary
Verdict: NEEDS_CHANGES
Confidence: high
Errors: 1 | Warnings: 2 | Suggestions: 2
Key code review findings:
- ❌ CI failures in Android UITests RadioButton,RefreshView (both Mono + CoreClr jobs, fast failure ~24 min consistent with test failure not infra timeout)
⚠️ src/Controls/tests/TestCases.HostApp/Issues/Issue33510.cs: External URL (https://material.angular.io/...) creates fragile internet dependency — should use self-contained HTML viaLoadHtmlor a stable minimal URL⚠️ src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33510.cs: No positive-direction test — verifies refresh does NOT trigger, but no test that pull-to-refresh DOES trigger once web content reaches the top (risk of over-correction regression)- 💡
RefreshViewWebViewScrollCapture.cs:Reset()write order is theoretically racy (_canScrollUp = falsebefore_hasReportedState = false); reversing order is safer - 💡
Issue33510.cstest:return null!afterAssert.Ignoreis dead code (Assert.Ignore throws, never returns)
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #34614 | JS bridge via AddJavascriptInterface reports DOM scroll state; OnInterceptTouchEvent defers gesture when WebView content is still scrollable | ❌ FAILED (Gate) | 4 impl + 3 test | CI failing Android RefreshView tests |
🔬 Code Review — Deep Analysis
Code Review — PR #34614
Independent Assessment
What this changes: Adds Android-specific gesture interception to MauiSwipeRefreshLayout so that a downward drag inside a WebView child is not consumed as pull-to-refresh when the web page content hasn't yet scrolled to the top. The mechanism bridges native Android into JavaScript via AddJavascriptInterface, injecting a scroll-position observer that reports DOM scroll state back to the C# side through volatile fields. MauiWebView auto-attaches/detaches this bridge based on whether it is a descendant of a MauiSwipeRefreshLayout.
Inferred motivation: Android's native WebView reports ScrollY == 0 and CanScrollVertically(-1) == false for pages that scroll via an internal CSS overflow container (e.g., overflow-y: auto) rather than the WebView's own native scroll position. SwipeRefreshLayout, seeing "not scrolled", eagerly intercepts the gesture and triggers refresh even while the user is still scrolling through inner page content.
Reconciliation with PR Narrative
Author claims: The fix introduces a "lightweight WebView bridge" to detect whether the touched DOM content can scroll up, only when the WebView is inside a RefreshView. Validated on Android, Windows, iOS, Mac.
Agreement: The root-cause analysis is correct and the approach is sound. The three previous Copilot review comments (unconditional injection, unconditional JS interface attachment, no mid-gesture re-evaluation) were all addressed in the current diff. The code is well-structured for an Android-only patch.
Disagreement: CI is failing for the exact category this test covers. The PR is not yet ready to merge.
Findings
❌ Error — CI failures in Android UITests ... RadioButton,RefreshView
Both the Mono-runtime and CoreClr jobs for Android UITests Controls (API 30) RadioButton,RefreshView are failed. This is the CI batch that includes the new Issue33510 test. The failures completed in ~24 minutes — much faster than adjacent tests (~90 min), which is consistent with an early test failure rather than an infrastructure timeout.
The most likely cause: Issue33510 loads https://material.angular.io/... and depends on external network access. If CI lacks outbound internet, WaitForTextToBePresentInElement("StatusLabel", "WebView ready", timeout: 30s) will time out and fail the assertion (not skip it — VerifyInternetConnectivity() only skips when the internet check itself returns false). Even if the test properly skips, any other pre-existing RefreshView test in this batch could be regressing due to the OnInterceptTouchEvent behavioral change.
This must be investigated and resolved before merge.
⚠️ Warning — External URL creates fragile internet dependency
src/Controls/tests/TestCases.HostApp/Issues/Issue33510.cs loads https://material.angular.io/components/sidenav/overview — a real third-party Angular docs site. This is high-risk for CI reliability:
- The URL could go down, redirect, or change layout
- CI agents often have restricted/no outbound internet for third-party domains
- DNS resolution or TLS handshake failures will time-out the 30-second
WaitForTextToBePresentInElementwith a hard failure, not a skip
A minimal stable replacement such as a LoadHtml string with an overflow-y: scroll inner div would decouple the test from third-party availability. The isInternetRequired: true attribute and VerifyInternetConnectivity() call are good habits, but they guard against the host having no internet — not against a specific URL being unreachable.
⚠️ Warning — No positive-direction test (pull-to-refresh when at top)
PullToRefreshShouldNotTriggerWhenWebViewIsScrolledDown verifies the fix doesn't over-trigger. There is no complementary test that verifies pull-to-refresh does still trigger correctly once the WebView content is at the top. This is exactly the class of regression the adjacent-scenario rule warns about: "fix one scenario, break the neighboring case." Without a positive test, a future change that sets _webViewOwnsGesture to true unconditionally and never clears it would pass all existing tests.
💡 Suggestion — Reset() write-ordering is theoretically racy
In ScrollCaptureState.Reset():
_canScrollUp = false; // volatile store #1
_hasReportedState = false; // volatile store #2A SetCanScrollUp(true) on the JavaBridge thread between these two stores would leave _canScrollUp = true and _hasReportedState = false — the JS-reported state is silently discarded. In practice the window is tiny (the JS for a new page fires well after OnPageStarted), and the native fallback in TryGetCanScrollUp partially compensates. Still, reversing the order (_hasReportedState = false first, then _canScrollUp = false) ensures that if the race fires, the consumer will see consistent state: HasReportedState = false will make TryGetCanScrollUp fall back to the native check.
💡 Suggestion — return null! after Assert.Ignore is dead code
In WaitForAndroidApp():
Assert.Ignore("Issue #33510 is Android-specific.");
return null!; // unreachable — Assert.Ignore always throwsAssert.Ignore throws IgnoreException and never returns, so return null! is unreachable. The ! suppressor is unnecessary. Consider marking the block with // unreachable or restructuring to avoid the dead return. Minor, but confusing on first read.
Devil's Advocate
Challenging the errors:
- The CI failure might be pre-existing infrastructure flakiness unrelated to this PR. However, the timing (two jobs, both
RefreshView, both failing fast) and the presence of an internet-dependent test make this more than a coincidence. The safe position is to require green CI.
Challenging the design:
- Could
TryGetCanScrollUpinCanScrollUpViewByTypebreak a WebView that's genuinely at the top? No — the fallback toCanScrollVertically(-1) || ScrollY > 0correctly returnsfalsein that case, same as the originalScrollY > 0(with the added benefit of also catching theCanScrollVerticallyedge case). - The
IsInsideSwipeRefreshLayout()parent-walk only runs atOnAttachedToWindow, so dynamically reparenting a WebView between a plain layout and a RefreshView won't update the state — but this is an edge case that MAUI's handler model doesn't support in practice. volatile boolfor cross-thread communication between JavaBridge and UI thread is the correct pattern on Android/JVM for single-field reads/writes, and is well-established in the MAUI codebase.
Challenging the approach overall:
- Injecting a JS interface only when inside a RefreshView (gated by
IsAttached) is a clean opt-in model that avoids polluting all WebViews. This was correctly addressed after the initial Copilot review. The approach is sound.
Verdict: NEEDS_CHANGES
Confidence: high
Summary: The core approach is well-designed and correctly scoped to the RefreshView+WebView interaction. All three prior review issues have been addressed. However, CI is actively failing for the RefreshView Android test batch (Mono + CoreClr), which must be resolved before merge. The use of an external URL in the test is the most likely culprit and should be replaced with a self-contained HTML page. A complementary positive-direction test for the refresh-triggers-correctly-at-top scenario should also be added to guard against over-correction regressions.
🔧 Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix (claude-opus-4.6) | HtmlWebViewSource test fix + kept JS bridge + added positive test | ✅ PASS | 2 (test files only) | Minimal change; JS bridge correct; no sleep needed |
| 2 | try-fix (claude-sonnet-4.6) | Pull-based evaluateJavascript polling from OnTouchEvent (no AddJavascriptInterface) + HtmlWebViewSource + positive test | ✅ PASS | 3 (test + MauiWebView.cs) | Avoids AddJavascriptInterface; requires 600ms sleep in positive test |
| 3 | try-fix (gpt-5.3-codex) | Native gesture-origin gating by touch start Y coordinate + HtmlWebViewSource + positive test | ✅ PASS | 3 (test + MauiSwipeRefreshLayout.cs) | No JS; heuristic approach; doesn't detect HTML scroll state directly |
| 4 | try-fix (gpt-4.1) | Thread-safety fix in Reset() + HtmlWebViewSource + positive test | ❌ FAIL (Blocked) | N/A | PowerShell script invocation error; code not exercised |
| PR | PR #34614 | JS bridge (AddJavascriptInterface + touchstart/touchmove observer) + external URL test | ❌ FAILED (Gate) | 4 impl + 3 test | External URL causes CI timeouts; no positive-direction test |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-4.6 | 2 | NEW IDEA | Override CanChildScrollUp with CanScrollVertically(-1) — NOT VIABLE (root cause is precisely that CanScrollVertically(-1) doesn't reflect inner HTML overflow state) |
| claude-sonnet-4.6 | 2 | NO NEW IDEAS | Confirmed solution space exhausted; JS bridge, JS polling, and gesture heuristics cover all viable approaches |
Exhausted: Yes
Selected Fix: Candidate #1 (claude-opus-4.6) — Reason: Minimal change (only test files) with highest reliability (no timing sleeps). The PR's JS bridge is sound and correct; only the test needed fixing. Replacing the external URL with self-contained HtmlWebViewSource eliminates the CI-blocking internet dependency, and adding the positive-direction test guards against over-correction regressions. This is the simplest path to making the PR mergeable.
📋 Report — Final Recommendation
⚠️ Final Recommendation: REQUEST CHANGES
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight | ✅ COMPLETE | Issue #33510 context gathered; 7 files classified (4 impl + 3 test) |
| Code Review | NEEDS_CHANGES (high) | 1 error, 2 warnings, 2 suggestions |
| Gate | ❌ FAILED | Android — tests did NOT behave as expected |
| Try-Fix | ✅ COMPLETE | 4 attempts, 3 passing (attempt 4 blocked by script error) |
| Report | ✅ COMPLETE |
Code Review Impact on Try-Fix
The primary code-review ❌ Error (CI failures caused by external URL https://material.angular.io/ in the HostApp test) directly guided all try-fix models: every passing attempt replaced the external URL with a self-contained HtmlWebViewSource. The code-review PullToRefreshShouldTriggerWhenWebViewIsAtTop. Attempt 4 (which failed due to script error) was specifically addressing the thread-safety warning in Reset(). The failure-mode probe ("URL unavailable in CI → hard FAIL") was confirmed as the root cause of the gate failure.
Summary
PR #34614 fixes a real Android bug where RefreshView wrapping a WebView triggers pull-to-refresh too early when the page scrolls via an inner HTML overflow-y container rather than the native WebView scroll position. The JS bridge approach is architecturally sound and the implementation is correct. However, the gate failed because the HostApp test page loads https://material.angular.io/ — a third-party URL that is unavailable in Android CI environments. This causes WaitForTextToBePresentInElement to time out and hard-fail. Additionally, the test only verifies the negative case (no false refresh) without a complementary positive case (refresh does trigger at top).
Try-Fix found a passing alternative (Candidate #1) that fixes both issues with minimal change: replace the external URL with a self-contained HtmlWebViewSource HTML page and add the positive-direction test. The JS bridge platform implementation does not need to change.
Root Cause
-
Test root cause (gate failure):
Issue33510.csHostApp page usesUrlWebViewSourcepointing tohttps://material.angular.io/components/sidenav/overview— a real third-party Angular docs site that is not reachable from Android CI agents. TheVerifyInternetConnectivity()guard only protects against total network absence, not specific URL unavailability. -
Bug root cause (issue [Android] RefreshView triggers pull-to-refresh immediately when scrolling up inside a WebView #33510): Android's
WebView.ScrollYandCanScrollVertically(-1)only reflect the native WebView scroll position, not scroll state of inner HTML elements withoverflow-y: auto/scroll. SwipeRefreshLayout callscanChildScrollUp()and, seeingScrollY == 0, incorrectly concludes the content is at the top and intercepts the drag gesture.
Fix Quality
Platform implementation (MauiSwipeRefreshLayout, RefreshViewWebViewScrollCapture, MauiWebView, MauiWebViewClient): The JS bridge is the correct approach for this problem. The three prior Copilot review suggestions have all been addressed. The volatile bool fields correctly handle cross-thread communication between the JavaBridge thread and the UI thread. The IsInsideSwipeRefreshLayout() parent-walk correctly scopes the bridge to only WebViews inside a RefreshView. The OnInterceptTouchEvent mid-gesture re-evaluation (MotionEventActions.Move) is correctly implemented. Minor: the Reset() write ordering race (code review 💡 suggestion) is not blocking but worth fixing for correctness.
Test (blocking): Must replace UrlWebViewSource { Url = "https://material.angular.io/..." } with a self-contained HtmlWebViewSource containing an inner overflow-y: scroll div. Remove isInternetRequired: true and VerifyInternetConnectivity(). Add PullToRefreshShouldTriggerWhenWebViewIsAtTop positive test.
Recommended changes for the author:
- ❌ Required: Replace external URL in
Issue33510.csHostApp withHtmlWebViewSource(self-contained scrollable HTML) - ❌ Required: Remove
isInternetRequired: trueandVerifyInternetConnectivity()from test - ❌ Required: Add
PullToRefreshShouldTriggerWhenWebViewIsAtToppositive-direction test - 💡 Optional: Fix
Reset()write order inRefreshViewWebViewScrollCapture.cs(swap_hasReportedState = falsebefore_canScrollUp = false) - 💡 Optional: Remove dead
return null!afterAssert.IgnoreinWaitForAndroidApp()
🧪 UI Tests — Category Detection & Results
Build #1391106 | ❌ failed | 1102/1203 passed (91.6%) | 99 failed
🎯 Detected categories: RefreshView,ViewBaseTests — ran 13 of 143 matrix cells (skipped 130)
Results by Platform
| Platform | Passed | Failed | Total |
|---|---|---|---|
| ✅ Android (API 30) | 39 | 0 | 39 |
| ✅ Android (API 30) | 118 | 0 | 119 |
| ✅ Android (API 30) | 39 | 0 | 39 |
| ✅ Android (API 30) | 118 | 0 | 119 |
| ✅ iOS Mono (18.5) | 39 | 0 | 39 |
| ✅ iOS Mono (18.5) | 112 | 0 | 112 |
| ❌ iOS Mono (latest) | 37 | 2 | 39 |
| ❌ iOS Mono (latest) | 15 | 97 | 112 |
| ✅ macOS | 20 | 0 | 20 |
| ✅ macOS | 112 | 0 | 112 |
| ✅ WinUI | 19 | 0 | 19 |
| ✅ WinUI | 115 | 0 | 115 |
| ✅ Material3 Android (API 36) | 319 | 0 | 319 |
Failures (99)
🖼️ snapshot: 99
❌ iOS Mono (latest) — 97 failed, 15/112 passed (13%)
| Test | Detail | |
|---|---|---|
| 🖼️ | Border_ClipNull_NoCrash |
0.56% diff |
| 🖼️ | Border_ClipWithNestedContent |
0.56% diff |
| 🖼️ | Border_ClipWithRotation |
0.56% diff |
| 🖼️ | Border_ClipWithRotationAndScale |
0.56% diff |
| 🖼️ | Border_ClipWithShadow |
0.56% diff |
| 🖼️ | Border_ClipWithStrokeColorBlue |
0.56% diff |
| 🖼️ | Border_ClipWithStrokeColorGreen |
0.56% diff |
| 🖼️ | Border_ClipWithStrokeShapeRoundRectangle |
0.56% diff |
| 🖼️ | Border_ClipWithStrokeThickness |
0.56% diff |
| 🖼️ | BoxView_ClipWithColorGreen |
0.56% diff |
| 🖼️ | BoxView_ClipWithCornerRadius |
0.56% diff |
| 🖼️ | BoxView_ClipWithRotation |
0.56% diff |
| 🖼️ | BoxView_ClipWithShadow |
0.56% diff |
| 🖼️ | Button_ClipNull_NoCrash |
0.56% diff |
| 🖼️ | Button_ClipWithImageSource |
0.56% diff |
| 🖼️ | Button_ClipWithScale |
0.56% diff |
| 🖼️ | Button_ClipWithShadow |
0.56% diff |
| 🖼️ | Button_ClipWithText |
0.56% diff |
| 🖼️ | ContentView_ClipNull_NoCrash |
0.56% diff |
| 🖼️ | ContentView_ClipWithEllipseGeometry |
0.56% diff |
| 🖼️ | ContentView_ClipWithNestedClippedContent |
0.56% diff |
| 🖼️ | ContentView_ClipWithRectangleGeometry |
0.56% diff |
| 🖼️ | ContentView_ClipWithRoundRectangleGeometry |
0.56% diff |
| 🖼️ | ContentView_ClipWithShadow |
0.56% diff |
| 🖼️ | DarkTheme_CheckBox_VerifyVisualState |
2.01% diff |
| ...and 74 more |
❌ iOS Mono (latest) — 2 failed, 37/39 passed (95%)
| Test | Detail | |
|---|---|---|
| 🖼️ | Border_ClipNull_NoCrash |
0.56% diff |
| 🖼️ | Border_ClipWithNestedContent |
0.56% diff |
| 🖼️ | Border_ClipWithRotation |
0.56% diff |
| 🖼️ | Border_ClipWithRotationAndScale |
0.56% diff |
| 🖼️ | Border_ClipWithShadow |
0.56% diff |
| 🖼️ | Border_ClipWithStrokeColorBlue |
0.56% diff |
| 🖼️ | Border_ClipWithStrokeColorGreen |
0.56% diff |
| 🖼️ | Border_ClipWithStrokeShapeRoundRectangle |
0.56% diff |
| 🖼️ | Border_ClipWithStrokeThickness |
0.56% diff |
| 🖼️ | BoxView_ClipWithColorGreen |
0.56% diff |
| 🖼️ | BoxView_ClipWithCornerRadius |
0.56% diff |
| 🖼️ | BoxView_ClipWithRotation |
0.56% diff |
| 🖼️ | BoxView_ClipWithShadow |
0.56% diff |
| 🖼️ | Button_ClipNull_NoCrash |
0.56% diff |
| 🖼️ | Button_ClipWithImageSource |
0.56% diff |
| 🖼️ | Button_ClipWithScale |
0.56% diff |
| 🖼️ | Button_ClipWithShadow |
0.56% diff |
| 🖼️ | Button_ClipWithText |
0.56% diff |
| 🖼️ | ContentView_ClipNull_NoCrash |
0.56% diff |
| 🖼️ | ContentView_ClipWithEllipseGeometry |
0.56% diff |
| 🖼️ | ContentView_ClipWithNestedClippedContent |
0.56% diff |
| 🖼️ | ContentView_ClipWithRectangleGeometry |
0.56% diff |
| 🖼️ | ContentView_ClipWithRoundRectangleGeometry |
0.56% diff |
| 🖼️ | ContentView_ClipWithShadow |
0.56% diff |
| 🖼️ | DarkTheme_CheckBox_VerifyVisualState |
2.01% diff |
| ...and 74 more |
🔴 Failed stages (1) of 13 total
- ❌ iOS UITests Mono
kubaflo
left a comment
There was a problem hiding this comment.
Could you please review the ai's summary?
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