Skip to content

[Android] Fix for RefreshView triggering pull-to-refresh when scrolling inside a WebView with internal scrollable content#34614

Open
BagavathiPerumal wants to merge 3 commits intodotnet:mainfrom
BagavathiPerumal:fix-33510
Open

[Android] Fix for RefreshView triggering pull-to-refresh when scrolling inside a WebView with internal scrollable content#34614
BagavathiPerumal wants to merge 3 commits intodotnet:mainfrom
BagavathiPerumal:fix-33510

Conversation

@BagavathiPerumal
Copy link
Copy Markdown
Contributor

Note

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

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:

  • If the internal web content is not yet at the top, the gesture remains with the WebView
  • Once the internal web content reaches the top, RefreshView is allowed to intercept and trigger refresh

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

  • Android
  • Windows
  • iOS
  • Mac

Issues Fixed

Fixes #33510

Output ScreenShot

Before After
BeforeFix-33510.mov
AfterFix-33510.mov

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 24, 2026

🚀 Dogfood this PR with:

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

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

Or

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

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Mar 24, 2026
@sheiksyedm sheiksyedm marked this pull request as ready for review March 26, 2026 10:58
Copilot AI review requested due to automatic review settings March 26, 2026 10:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR 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 WebView JavaScript bridge + injected observer script to report whether touched DOM content can still scroll up.
  • Update MauiSwipeRefreshLayout to consult the reported “can scroll up” state for WebView (and add intercept logic for gestures starting inside a WebView).
  • 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.

Comment thread src/Core/src/Platform/Android/MauiSwipeRefreshLayout.cs
Comment thread src/Core/src/Platform/Android/MauiWebViewClient.cs
Comment thread src/Core/src/Platform/Android/MauiWebView.cs
@MauiBot MauiBot added s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-approved AI agent recommends approval - PR fix is correct and optimal s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) s/agent-changes-requested AI agent recommends changes - found a better alternative or issues labels Mar 27, 2026
kubaflo
kubaflo previously approved these changes Mar 28, 2026
@sheiksyedm
Copy link
Copy Markdown
Contributor

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

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

@sheiksyedm
Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@dotnet dotnet deleted a comment from MauiBot Apr 21, 2026
@dotnet dotnet deleted a comment from MauiBot Apr 21, 2026
@sheiksyedm
Copy link
Copy Markdown
Contributor

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

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Apr 22, 2026

🤖 AI Summary

👋 @BagavathiPerumal — new AI review results are available. Please review the latest session below.

📊 Review Session9b974af · fix-33510-Simplied Testcase and test script. Additionally, updated the code changes based on AI summary. · 2026-04-22 18:28 UTC
🚦 Gate — Test Before & After Fix

Gate Result: ⚠️ ENV ERROR

Platform: ANDROID · Base: main · Merge base: 42d0dbfe

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue33510 Issue33510 ✅ FAIL — 760s ⚠️ ENV ERROR
🔴 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.cs
  • src/Core/src/Platform/Android/MauiWebView.cs
  • src/Core/src/Platform/Android/MauiWebViewClient.cs
  • src/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), causing SwipeRefreshLayout to incorrectly assume the content is at the top and intercept gestures too early
  • Fix approach: JS bridge (AddJavascriptInterface) injected on OnPageFinished, reporting DOM scroll state back to C# via volatile bool fields; MauiSwipeRefreshLayout.OnInterceptTouchEvent uses this info to defer gesture interception while WebView content is still scrollable
  • Bridge is only attached when MauiWebView detects it's inside a MauiSwipeRefreshLayout — 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,RefreshView on 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 via LoadHtml or 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 = false before _hasReportedState = false); reversing order is safer
  • 💡 Issue33510.cs test: return null! after Assert.Ignore is 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 WaitForTextToBePresentInElement with 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 #2

A 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 throws

Assert.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 TryGetCanScrollUp in CanScrollUpViewByType break a WebView that's genuinely at the top? No — the fallback to CanScrollVertically(-1) || ScrollY > 0 correctly returns false in that case, same as the original ScrollY > 0 (with the added benefit of also catching the CanScrollVertically edge case).
  • The IsInsideSwipeRefreshLayout() parent-walk only runs at OnAttachedToWindow, 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 bool for 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 ⚠️ Warning about the missing positive-direction test was addressed by attempts 1, 2, and 3, each adding 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

  1. Test root cause (gate failure): Issue33510.cs HostApp page uses UrlWebViewSource pointing to https://material.angular.io/components/sidenav/overview — a real third-party Angular docs site that is not reachable from Android CI agents. The VerifyInternetConnectivity() guard only protects against total network absence, not specific URL unavailability.

  2. Bug root cause (issue [Android] RefreshView triggers pull-to-refresh immediately when scrolling up inside a WebView #33510): Android's WebView.ScrollY and CanScrollVertically(-1) only reflect the native WebView scroll position, not scroll state of inner HTML elements with overflow-y: auto/scroll. SwipeRefreshLayout calls canChildScrollUp() and, seeing ScrollY == 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:

  1. Required: Replace external URL in Issue33510.cs HostApp with HtmlWebViewSource (self-contained scrollable HTML)
  2. Required: Remove isInternetRequired: true and VerifyInternetConnectivity() from test
  3. Required: Add PullToRefreshShouldTriggerWhenWebViewIsAtTop positive-direction test
  4. 💡 Optional: Fix Reset() write order in RefreshViewWebViewScrollCapture.cs (swap _hasReportedState = false before _canScrollUp = false)
  5. 💡 Optional: Remove dead return null! after Assert.Ignore in WaitForAndroidApp()

🧪 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

@MauiBot MauiBot added s/agent-fix-win AI found a better alternative fix than the PR and removed s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates labels Apr 22, 2026
@dotnet dotnet deleted a comment from BagavathiPerumal Apr 22, 2026
@dotnet dotnet deleted a comment from MauiBot Apr 22, 2026
Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

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

Could you please review the ai's summary?

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

Labels

community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/android s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] RefreshView triggers pull-to-refresh immediately when scrolling up inside a WebView

6 participants