Repository navigation
Fixed HideSoftInputOnTapped="True" on one page permanently affects all other pages if NavigatedFrom is never fired - #36238
Conversation
Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36238Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36238" |
|
Hey there @@KarthikRajaKalaimani! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 3 findings
See inline comments for details.
| // that was cached when the view became focused. This preserves the | ||
| // original behavior where the feature remains enabled until the view | ||
| // explicitly loses focus. | ||
| var page = GetEnclosingPage(FocusedView) ?? FocusedEnclosingPage; |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[major] Logic and Correctness / Regression Prevention — FeatureEnabled falls back to FocusedEnclosingPage (the page cached when the view became focused) whenever the live Parent walk from FocusedView returns null. This reintroduces a variant of the exact bug this PR fixes: if the focused input on the outgoing page is detached (e.g. ShellContent.IsVisible = false + absolute GoToAsync, per the Issue35890 repro) before its native IsFocused=false update runs, SendNavigatedFrom never fires, so the cached page's HasNavigatedTo stays true forever. Until some other input view anywhere in the app receives focus (which clears _focusedView/_focusedViewEnclosingPage), a tap on the new destination page is evaluated against the stale cached page's HideSoftInputOnTapped/HasNavigatedTo, not the destination page's. Consider dropping the fallback (accepting that a genuinely-detached view disables the feature) or gating the fallback on the cached page still being the currently active page/window, rather than only on the view's Parent chain being non-null.
| // hiding the ShellContent via IsVisible=false and navigating with an absolute path | ||
| // does NOT fire NavigatedFrom on the outgoing page. | ||
| _loginShellContent.IsVisible = false; | ||
| _ = Current.GoToAsync("///HomePage35890"); |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[moderate] Regression Prevention and Test Coverage — This repro (and the paired UI test) logs in by tapping LoginButton directly, without ever focusing UsernameEntry first. That means NavigateToHome() never exercises the scenario where the manager's _focusedViewEnclosingPage fallback is actually populated with the outgoing (Login) page at the moment of navigation — the exact path this PR's fix (HideSoftInputOnTappedChangedManager.Platform.cs) added to handle. Add a variant that taps UsernameEntry (to give it focus and populate the cache) immediately before calling NavigateToHome(), then assert HomePage35890's HideSoftInputOnTapped=false is still honored on the very first tap after arriving — this is the scenario most likely to reveal a stale-cache regression.
| // Step 1: Log in from the LoginPage (HideSoftInputOnTapped = True), unless a previous | ||
| // test in this fixture has already done so. | ||
| // This triggers NavigatedTo on the LoginPage, which registers it in | ||
| // HideSoftInputOnTappedChangedManager._contentPages. |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[minor] Stale comment — This comment says logging in "registers it in HideSoftInputOnTappedChangedManager._contentPages", but this same PR removes the _contentPages field entirely (see HideSoftInputOnTappedChangedManager.cs diff) in favor of the focused-view/_focusedViewEnclosingPage model. The comment describes the pre-fix implementation detail and will confuse future maintainers reading the post-fix code; please update it to describe the current (focused-view-based) mechanism or drop the implementation-detail reference.
kubaflo
left a comment
There was a problem hiding this comment.
Could you please check the ai's suggestions?
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 1 findings
See inline comments for details.
| // that was cached when the view became focused. This preserves the | ||
| // original behavior where the feature remains enabled until the view | ||
| // explicitly loses focus. | ||
| var page = GetEnclosingPage(FocusedView) ?? FocusedEnclosingPage; |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[major] Handler lifecycle — Falling back to the cached focused page keeps FeatureEnabled true after the focused input has been detached from the live tree. In the issue scenario, if an Entry on the HideSoftInputOnTapped=true page is focused before Shell hides/navigates away without LostFocus/NavigatedFrom, the old tap watcher can remain active until another input receives focus, so tapping a non-input control on a HideSoftInputOnTapped=false destination can still be processed using the stale page. Please clear the tracked focus when the focused view is detached/windowless, or gate dispatch on the current visible page instead of the cached page.
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 2 findings
See inline comments for details.
| // that was cached when the view became focused. This preserves the | ||
| // original behavior where the feature remains enabled until the view | ||
| // explicitly loses focus. | ||
| var page = GetEnclosingPage(FocusedView) ?? FocusedEnclosingPage; |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[major] Logic and Correctness — Falling back to the cached FocusedEnclosingPage keeps using the old page after the focused input is detached without a focus-lost event. In the issue scenario, if the login page keeps focus and Shell hides/navigates it without NavigatedFrom, HasNavigatedTo remains true, so FeatureEnabled stays true and Android can still dispatch taps through the stale page until another input gets focus. This reintroduces the cross-page persistence the PR is trying to remove; prefer requiring the focused view to resolve to a live enclosing page (or otherwise clearing this cache on detach/navigation).
| // The login action hides the ShellContent (IsVisible=false) and navigates | ||
| // via absolute GoToAsync — which does NOT fire NavigatedFrom on the LoginPage. | ||
| // Step 2: Verify we are on the HomePage (HideSoftInputOnTapped = False). | ||
| EnsureOnHomePage(); |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[major] Regression Prevention and Test Coverage — This test navigates away from the login page without first focusing UsernameEntry, so it does not cover the stale focused-input path introduced by the cached-page fallback. The old global-list bug is exercised after HomeEntry receives focus, but a login entry that remains focused while NavigatedFrom is skipped can still leave the previous page controlling tap dismissal. Please focus the login entry/show the keyboard before tapping LoginButton (or add a separate test) so the regression test fails for that case.
MauiBot
left a comment
There was a problem hiding this comment.
AI Review Summary
@KarthikRajaKalaimani — new AI review results are available based on this last commit:
91a3d6b. To request a fresh review after new comments or commits, comment/review rerun.
🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix
Gate Result: ✅ PASSED
Platform: ANDROID · Base: main · Merge base: 0395a53b
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🖥️ Issue35890 Issue35890 |
✅ FAIL — 2339s | ✅ PASS — 1708s |
🔴 Without fix — 🖥️ Issue35890: FAIL ✅ · 2339s
(truncated to last 15,000 chars)
ome/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.100-ci+azdo.14656745
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14656745
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.100-ci+azdo.14656745
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14656745
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14656745
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
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
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14656745
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:08:17.46
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
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Starting: Intent { act=android.settings.SETTINGS }
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/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 1.28 sec).
Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 4 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 4.5 sec).
Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.Android.Tests/Controls.TestCases.Android.Tests.csproj (in 5.95 sec).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 2 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 3 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 237 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 6.2 sec).
5 of 13 projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14656745
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14656745
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14656745
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.100-ci+azdo.14656745
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.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.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.
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 2 of 2 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 07/14/2026 21:41:17 FixtureSetup for Issue35890(Android)
>>>>> 07/14/2026 21:41:21 HideSoftInputOnTappedDoesNotPersistAfterShellContentHiddenWithoutNavigatedFrom Start
>>>>> 07/14/2026 21:41:34 HideSoftInputOnTappedDoesNotPersistAfterShellContentHiddenWithoutNavigatedFrom Stop
>>>>> 07/14/2026 21:41:35 Log types: logcat, bugreport, server
Failed HideSoftInputOnTappedDoesNotPersistAfterShellContentHiddenWithoutNavigatedFrom [14 s]
Error Message:
Keyboard should remain visible when HideSoftInputOnTapped=False is set on the current page. The stale LoginPage registration in HideSoftInputOnTappedChangedManager must have been cleaned up.
Assert.That(App.IsKeyboardShown(), Is.True)
Expected: True
But was: False
Stack Trace:
at Microsoft.Maui.TestCases.Tests.Issues.Issue35890.HideSoftInputOnTappedDoesNotPersistAfterShellContentHiddenWithoutNavigatedFrom() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35890.cs:line 68
1) at Microsoft.Maui.TestCases.Tests.Issues.Issue35890.HideSoftInputOnTappedDoesNotPersistAfterShellContentHiddenWithoutNavigatedFrom() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35890.cs:line 68
>>>>> 07/14/2026 21:41:36 HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation Start
>>>>> 07/14/2026 21:41:45 HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation Stop
>>>>> 07/14/2026 21:41:45 Log types: logcat, bugreport, server
WebDriverException: OpenQA.Selenium.StaleElementReferenceException: Cached elements 'By.accessibilityId: Navigate up' do not exist in DOM anymore; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#stale-element-reference-exception
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebDriver.InternalExecute(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Execute(String commandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Click()
at UITest.Appium.AppiumMouseActions.ClickElement(AppiumElement element) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 106
Failed HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation [10 s]
Error Message:
OpenQA.Selenium.StaleElementReferenceException : Cached elements 'By.accessibilityId: Navigate up' do not exist in DOM anymore; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#stale-element-reference-exception
1) Keyboard should remain visible when tapping outside the Entry on a page with HideSoftInputOnTapped = False, even though the previous page in the navigation stack had HideSoftInputOnTapped = True.
Assert.That(App.IsKeyboardShown(), Is.True)
Expected: True
But was: False
Stack Trace:
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebDriver.InternalExecute(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Execute(String commandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.Appium.AppiumElement.Execute(String commandName, Dictionary`2 parameters)
at OpenQA.Selenium.Appium.AppiumElement._GetAttribute(String attributeName)
at OpenQA.Selenium.Appium.AppiumElement.<>c__DisplayClass21_0.<GetAttribute>b__0()
at OpenQA.Selenium.Appium.AppiumElement.CacheValue(String key, Func`1 getter)
at OpenQA.Selenium.Appium.AppiumElement.GetAttribute(String attributeName)
at UITest.Appium.AppiumMouseActions.ElementToClickablePoint(AppiumElement element) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 230
at UITest.Appium.AppiumMouseActions.<ClickElement>g__ProcessException|13_0(<>c__DisplayClass13_0&) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 136
at UITest.Appium.AppiumMouseActions.ClickElement(AppiumElement element) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 117
at UITest.Appium.AppiumMouseActions.Click(IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 63
at UITest.Appium.AppiumMouseActions.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 45
at UITest.Appium.AppiumCommandExecutor.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumCommandExecutor.cs:line 20
at UITest.Appium.HelperExtensions.Click(IUIElement element) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 412
at Microsoft.Maui.TestCases.Tests.UtilExtensions.Back(UITestContextBase testBase) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 18
at Microsoft.Maui.TestCases.Tests.Issues.Issue35890.HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35890.cs:line 121
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
1) at Microsoft.Maui.TestCases.Tests.Issues.Issue35890.HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35890.cs:line 110
2) at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebDriver.InternalExecute(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Execute(String commandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.Appium.AppiumElement.Execute(String commandName, Dictionary`2 parameters)
at OpenQA.Selenium.Appium.AppiumElement._GetAttribute(String attributeName)
at OpenQA.Selenium.Appium.AppiumElement.<>c__DisplayClass21_0.<GetAttribute>b__0()
at OpenQA.Selenium.Appium.AppiumElement.CacheValue(String key, Func`1 getter)
at OpenQA.Selenium.Appium.AppiumElement.GetAttribute(String attributeName)
at UITest.Appium.AppiumMouseActions.ElementToClickablePoint(AppiumElement element) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 230
at UITest.Appium.AppiumMouseActions.<ClickElement>g__ProcessException|13_0(<>c__DisplayClass13_0&) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 136
at UITest.Appium.AppiumMouseActions.ClickElement(AppiumElement element) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 117
at UITest.Appium.AppiumMouseActions.Click(IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 63
at UITest.Appium.AppiumMouseActions.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 45
at UITest.Appium.AppiumCommandExecutor.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumCommandExecutor.cs:line 20
at UITest.Appium.HelperExtensions.Click(IUIElement element) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 412
at Microsoft.Maui.TestCases.Tests.UtilExtensions.Back(UITestContextBase testBase) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 18
at Microsoft.Maui.TestCases.Tests.Issues.Issue35890.HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35890.cs:line 121
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
Standard Output Messages:
WebDriverException: OpenQA.Selenium.StaleElementReferenceException: Cached elements 'By.accessibilityId: Navigate up' do not exist in DOM anymore; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#stale-element-reference-exception
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebDriver.InternalExecute(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Execute(String commandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Click()
at UITest.Appium.AppiumMouseActions.ClickElement(AppiumElement element) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 106
NUnit Adapter 4.5.0.0: Test execution complete
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.12] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.53] Discovered: Controls.TestCases.Android.Tests
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue35890.trx
Test Run Failed.
Total tests: 2
Failed: 2
Total time: 50.9928 Seconds
>>> TRX_RESULT_FILE: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue35890.trx
🟢 With fix — 🖥️ Issue35890: PASS ✅ · 1708s
(truncated to last 15,000 chars)
completed: result=0
Starting: Intent { act=android.settings.SETTINGS }
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.
Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14656745
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14656745
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14656745
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.100-ci+azdo.14656745
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.
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 2 of 2 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 07/14/2026 22:00:15 FixtureSetup for Issue35890(Android)
>>>>> 07/14/2026 22:00:17 HideSoftInputOnTappedDoesNotPersistAfterShellContentHiddenWithoutNavigatedFrom Start
>>>>> 07/14/2026 22:00:28 HideSoftInputOnTappedDoesNotPersistAfterShellContentHiddenWithoutNavigatedFrom Stop
Passed HideSoftInputOnTappedDoesNotPersistAfterShellContentHiddenWithoutNavigatedFrom [11 s]
>>>>> 07/14/2026 22:00:29 HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation Start
>>>>> 07/14/2026 22:00:37 HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation Stop
>>>>> 07/14/2026 22:00:37 Log types: logcat, bugreport, server
WebDriverException: OpenQA.Selenium.StaleElementReferenceException: Cached elements 'By.accessibilityId: Navigate up' do not exist in DOM anymore; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#stale-element-reference-exception
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebDriver.InternalExecute(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Execute(String commandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Click()
at UITest.Appium.AppiumMouseActions.ClickElement(AppiumElement element) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 106
Failed HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation [9 s]
Error Message:
OpenQA.Selenium.StaleElementReferenceException : Cached elements 'By.accessibilityId: Navigate up' do not exist in DOM anymore; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#stale-element-reference-exception
Stack Trace:
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebDriver.InternalExecute(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Execute(String commandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.Appium.AppiumElement.Execute(String commandName, Dictionary`2 parameters)
at OpenQA.Selenium.Appium.AppiumElement._GetAttribute(String attributeName)
at OpenQA.Selenium.Appium.AppiumElement.<>c__DisplayClass21_0.<GetAttribute>b__0()
at OpenQA.Selenium.Appium.AppiumElement.CacheValue(String key, Func`1 getter)
at OpenQA.Selenium.Appium.AppiumElement.GetAttribute(String attributeName)
at UITest.Appium.AppiumMouseActions.ElementToClickablePoint(AppiumElement element) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 230
at UITest.Appium.AppiumMouseActions.<ClickElement>g__ProcessException|13_0(<>c__DisplayClass13_0&) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 136
at UITest.Appium.AppiumMouseActions.ClickElement(AppiumElement element) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 117
at UITest.Appium.AppiumMouseActions.Click(IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 63
at UITest.Appium.AppiumMouseActions.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 45
at UITest.Appium.AppiumCommandExecutor.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumCommandExecutor.cs:line 20
at UITest.Appium.HelperExtensions.Click(IUIElement element) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 412
at Microsoft.Maui.TestCases.Tests.UtilExtensions.Back(UITestContextBase testBase) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 18
at Microsoft.Maui.TestCases.Tests.Issues.Issue35890.HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35890.cs:line 121
at Microsoft.Maui.TestCases.Tests.Issues.Issue35890.HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35890.cs:line 82
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
Standard Output Messages:
WebDriverException: OpenQA.Selenium.StaleElementReferenceException: Cached elements 'By.accessibilityId: Navigate up' do not exist in DOM anymore; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#stale-element-reference-exception
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebDriver.InternalExecute(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Execute(String commandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Click()
at UITest.Appium.AppiumMouseActions.ClickElement(AppiumElement element) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 106
NUnit Adapter 4.5.0.0: Test execution complete
[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.53] Discovered: Controls.TestCases.Android.Tests
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue35890.trx
Total tests: 2
Passed: 1
Failed: 1
Test Run Failed.
Total time: 34.0839 Seconds
>>> TRX_RESULT_FILE: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue35890.trx
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.
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
>>>>> 07/14/2026 22:00:52 FixtureSetup for Issue35890(Android)
>>>>> 07/14/2026 22:00:53 HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation Start
>>>>> 07/14/2026 22:01:10 HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation Stop
>>>>> 07/14/2026 22:01:10 Log types: logcat, bugreport, server
WebDriverException: OpenQA.Selenium.StaleElementReferenceException: Cached elements 'By.accessibilityId: Navigate up' do not exist in DOM anymore; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#stale-element-reference-exception
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebDriver.InternalExecute(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Execute(String commandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Click()
at UITest.Appium.AppiumMouseActions.ClickElement(AppiumElement element) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 106
Failed HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation [16 s]
Error Message:
OpenQA.Selenium.StaleElementReferenceException : Cached elements 'By.accessibilityId: Navigate up' do not exist in DOM anymore; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#stale-element-reference-exception
Stack Trace:
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebDriver.InternalExecute(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Execute(String commandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.Appium.AppiumElement.Execute(String commandName, Dictionary`2 parameters)
at OpenQA.Selenium.Appium.AppiumElement._GetAttribute(String attributeName)
at OpenQA.Selenium.Appium.AppiumElement.<>c__DisplayClass21_0.<GetAttribute>b__0()
at OpenQA.Selenium.Appium.AppiumElement.CacheValue(String key, Func`1 getter)
at OpenQA.Selenium.Appium.AppiumElement.GetAttribute(String attributeName)
at UITest.Appium.AppiumMouseActions.ElementToClickablePoint(AppiumElement element) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 230
at UITest.Appium.AppiumMouseActions.<ClickElement>g__ProcessException|13_0(<>c__DisplayClass13_0&) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 136
at UITest.Appium.AppiumMouseActions.ClickElement(AppiumElement element) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 117
at UITest.Appium.AppiumMouseActions.Click(IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 63
at UITest.Appium.AppiumMouseActions.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 45
at UITest.Appium.AppiumCommandExecutor.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumCommandExecutor.cs:line 20
at UITest.Appium.HelperExtensions.Click(IUIElement element) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 412
at Microsoft.Maui.TestCases.Tests.UtilExtensions.Back(UITestContextBase testBase) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 18
at Microsoft.Maui.TestCases.Tests.Issues.Issue35890.HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35890.cs:line 121
at Microsoft.Maui.TestCases.Tests.Issues.Issue35890.HideSoftInputOnTappedRespectsPerPageSemanticsWithClassicPushNavigation() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35890.cs:line 82
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
Standard Output Messages:
WebDriverException: OpenQA.Selenium.StaleElementReferenceException: Cached elements 'By.accessibilityId: Navigate up' do not exist in DOM anymore; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#stale-element-reference-exception
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebDriver.InternalExecute(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Execute(String commandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.WebElement.Click()
at UITest.Appium.AppiumMouseActions.ClickElement(AppiumElement element) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:line 106
NUnit Adapter 4.5.0.0: Test execution complete
[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.14] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.59] Discovered: Controls.TestCases.Android.Tests
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/retry-Issue35890.trx
Test Run Failed.
Total tests: 1
Failed: 1
Total time: 29.2577 Seconds
📁 Fix files reverted (3 files)
src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Android.cssrc/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Platform.cssrc/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.cs
📱 UI Tests — Focus,Navigation,Page,Shell,SoftInput
Detected UI test categories: Focus,Navigation,Page,Shell,SoftInput
❌ Deep UI tests — 435 passed, 3 failed across 5 categories on platform-pool agent (replaces in-process counts above).
🧪 UI Test Execution Results (deep, platform pool)
| Category | Tests | Snapshot diffs |
|---|---|---|
Focus |
2/2 ✓ | — |
Navigation |
88/89 ✓ | — |
Page |
24/26 (2 ❌) | — |
Shell |
308/309 (1 ❌) | — |
SoftInput |
13/13 ✓ | — |
❌ Page — 2 failed tests
WindowSoftInputModeAdjustSetToResizeForModalPage
Assert.That(beforeEntryRectY, Is.Not.EqualTo(afterEntryRectY))
Expected: not equal to 863
But was: 863
at Microsoft.Maui.TestCases.Tests.Issues.Issue27242.WindowSoftInputModeAdjustSetToResizeForModalPage() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue27242.cs:line 29
1) at Microsoft.Maui.TestCases.Tests.Issues.Issue27242.WindowSoftInputModeAdjustSetToResizeForModalPage() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue27242.cs:line 29
WindowSoftInputModeAdjustSetToPanForModalpage
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.Issues.Issue27242.WindowSoftInputModeAdjustSetToPanForModalpage() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue27242.cs:line 37
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at
...
❌ Shell — 1 failed test
ClearAndRecreateShellElements
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.Issues.ShellItemIsVisible.ClearAndRecreateShellElements() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/XFIssue/ShellItemIsVisible.cs:line 66
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* arg
...
🔍 AI analysis of failures — PR-related vs unrelated
🔍 AI-generated triage (GitHub Copilot CLI) — a heuristic judgement of whether each deep UI test failure is connected to this PR's changes. Verify before relying on it.
Mixed / uncertain: see the grouped assessment below.
- ✗ PR-related — Android soft-input modal page behavior (~2 tests): the failures in Issue27242 exercise Android keyboard/window soft-input adjustment on modal pages, and this PR changes Android/shared
HideSoftInputOnTappedChangedManagerlogic that controls when tap/keyboard handling is enabled for the focused page, making the unchanged Y position and missing element plausibly connected. - ● Unrelated — Shell element recreation timeout (~1 test):
ClearAndRecreateShellElementsis a Shell visibility/recreation wait timeout with no stack reference to the changed soft-input manager, and the PR only adds a separate Shell-hosted regression test while changing ContentPage soft-input tap behavior.
Strongest signal: the two Page failures are in the same Android keyboard/input behavior neighborhood as the PR, while the Shell timeout looks like the common “timed out waiting for element” UI-test pattern outside the modified code path.
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)
📋 Pre-Flight — Context & Validation
Issue: #35890 - HideSoftInputOnTapped="True" on one page permanently affects all other pages if NavigatedFrom is never fired
PR: #36238 - Fixed HideSoftInputOnTapped="True" on one page permanently affects all other pages if NavigatedFrom is never fired
Platforms Affected: Android primary; PR also touches shared Android/iOS code and marks iOS affected
Files Changed: 3 implementation, 2 test
Key Findings
- The issue is a stale global state bug in
HideSoftInputOnTappedChangedManager: a page withHideSoftInputOnTapped=truecan remain registered if Shell hides itsShellContentand absolute navigation skipsNavigatedFrom. - The PR's current fix removes
_contentPagesand computes from the focused view's enclosing page, but retains a cachedFocusedEnclosingPagefallback. - Android UI tests were added under
Issue35890with categorySoftInput; impacted UI categories:SoftInput,Shell,Navigation,Focus,Page. - GitHub CLI was unauthenticated in this environment, so public GitHub API and local branch state were used for context. Required-check status could not be authoritatively queried with
gh pr checks.
Code Review Summary
Verdict: NEEDS_CHANGES
Confidence: low
Errors: 1 | Warnings: 1 | Suggestions: 1
Key code review findings:
- x Cached focused page can reintroduce stale page behavior at
HideSoftInputOnTappedChangedManager.Platform.cs:36andHideSoftInputOnTappedChangedManager.Android.cs:21. - ! Test does not focus the login entry before navigating away, so it may not exercise the cached-page stale fallback.
- i Test comments still reference removed
_contentPagesimplementation details.
Failure-mode probes:
- Focused input detaches before focus-lost: cached page can remain available and keep the feature enabled.
- Destination page has
HideSoftInputOnTapped=false: Android can still consult the stale cached page before another focus update clears it. - No input focused before navigation: the current PR likely fixes the original global-list leak, but not the hardest focused-detach scenario.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #36238 | Remove global page list and resolve the focused view's enclosing page on demand, with a cached enclosing-page fallback | PASSED (Gate provided by caller) | HideSoftInputOnTappedChangedManager*.cs, Issue35890.cs tests |
Original PR |
🔬 Code Review — Deep Analysis
Code Review -- PR #36238
Independent Assessment
What this changes: Replaces the global _contentPages tracking list for HideSoftInputOnTapped with focused-view lookup: the manager walks FocusedView.Parent to find the enclosing ContentPage, and caches the page when focus is set as a fallback if the view later detaches.
Inferred motivation: Avoid stale page registrations when NavigatedFrom does not fire, so one page's HideSoftInputOnTapped=true does not affect later pages.
Reconciliation with PR Narrative
Author claims: The PR says behavior is now computed from the live visual tree with no stale cache; if the page is gone, FocusedView becomes null and FeatureEnabled returns false.
Agreement/disagreement: The high-level direction is sound, but the implementation still has a cache: GetEnclosingPage(FocusedView) ?? FocusedEnclosingPage. That cached page can preserve the same stale-page class of bug when an input was focused before navigation/hiding.
Prior Review Reconciliation
| Prior Error Finding | Source | Status | Evidence |
|---|---|---|---|
| Cached focused page can keep stale dismiss behavior active | MauiBot inline/review summary | Unresolved | Current PR head still has GetEnclosingPage(FocusedView) ?? FocusedEnclosingPage in HideSoftInputOnTappedChangedManager.Platform.cs:36 and Android uses the same fallback at HideSoftInputOnTappedChangedManager.Android.cs:21. |
| Agent gate failed with fix | MauiBot AI summary | Unresolved | Review body reports Gate Result: FAILED and Issue35890 FAILED with fix; no later commit is present on PR head 91a3d6b. |
Blast Radius Assessment
- Runs for all instances: Yes -- this is shared Android/iOS
HideSoftInputOnTappedfocus/tap plumbing. - Startup impact: No direct startup path.
- Static/shared state: No static state, but the scoped manager keeps cross-page focused-view state and Android window dispatch subscriptions.
CI Status
- Required-check result:
gh pr checks --requiredunavailable becauseghis unauthenticated. - Classification: undetermined from required-check API. Public check-runs for head SHA show
maui-prsuccess; commit status API reports pending/no statuses. Prior MauiBot gate reports PR-specific Issue35890 failure. - Action taken: Used public GitHub API/curl fallback; confidence capped low by unresolved prior gate/finding.
Findings
Error -- Cached focused page can reintroduce stale page behavior
HideSoftInputOnTappedChangedManager.Platform.cs:36 falls back from the live parent walk to FocusedEnclosingPage. Android dispatch then uses the same fallback at HideSoftInputOnTappedChangedManager.Android.cs:21.
If a HideSoftInputOnTapped=true page has a focused Entry, then Shell hides/navigates away without NavigatedFrom and before a reliable focus-lost update, the focused view may be detached while _focusedViewEnclosingPage still points to the old page. The next tap can still be evaluated against the cached old page's HideSoftInputOnTapped/HasNavigatedTo, which is the stale-state problem this PR is meant to remove.
Warning -- Test does not cover the cached-page failure path
The new repro logs in via LoginButton without first focusing UsernameEntry (src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35890.cs:35-37). That means _focusedViewEnclosingPage may never be populated with the outgoing login page before navigation, so the test can pass while the added fallback remains unsafe.
Suggestion -- Update stale test comment
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35890.cs:46-47 still says the login page registers in _contentPages, but this PR removes _contentPages.
Failure-Mode Probing
- Focused input detaches before focus-lost: cached page remains available and can keep feature enabled.
- Destination page has
HideSoftInputOnTapped=false: Android can still consult stale cached page before another input focus clears it. - No input focused before navigation: new approach likely fixes the original global-list leak, but this is not the hardest case.
- Handler/window lifecycle: Android subscription cleanup depends on later focus updates; stale manager state can survive navigation.
Verdict: NEEDS_CHANGES
Confidence: low
Summary: The PR removes the global page list, but the cached focused-page fallback leaves an unresolved stale-state path and prior MauiBot findings/gate failure report the same issue. The tests should cover focusing the login entry before hiding/navigating, and the fallback should be removed or gated on the currently active page/window.
🛠️ Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | maui-expert-reviewer / try-fix | Live focused page only: remove cached enclosing-page fallback and require a live focused-view parent chain | PASS | 2 files | Passed Android Issue35890 runner after script-managed retry; addresses stale-cache review error |
| PR | PR #36238 | Remove global _contentPages; compute from focused view parent chain with cached focused-page fallback |
PASSED (Gate provided) | 5 files | Original PR; gate was pre-run by caller |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| maui-expert-reviewer | 1 | Yes | Proposed live-tree-only candidate, removing the stale cached page fallback. |
Exhausted: No
Selected Fix: Candidate #1 -- It passed the Android regression test command and is better than the PR's current fix because it removes the stale cached-page fallback identified by code review while preserving the PR's core live-tree approach.
📝 Recommended PR Title & Description
Assessment: ✏️ Recommend updating — the current title lacks the required platform/component prefix and the description claims there is no cache/stale state, which is inaccurate for the submitted PR and should describe the pr-plus-reviewer winning fix instead.
Recommended title
[Android/iOS] ContentPage: Scope HideSoftInputOnTapped to the live focused page
Recommended description
### Issue Details
`HideSoftInputOnTapped="True"` on one page can permanently affect later pages when Shell hides the outgoing `ShellContent` and navigates with an absolute route without firing `NavigatedFrom`.
### Root Cause
`HideSoftInputOnTappedChangedManager` maintained an internal list of `ContentPage` instances whose `HideSoftInputOnTapped` value was enabled. Pages were added when `NavigatedTo` fired and removed when `NavigatedFrom` fired. In the #35890 scenario, a login `ShellContent` is hidden by setting `IsVisible=false` and then navigating with `GoToAsync("///Route")`; Shell can skip `NavigatedFrom` for the outgoing page, leaving that page registered with `HideSoftInputOnTapped=true`.
Because `FeatureEnabled` was evaluated from the tracked pages, the stale login page could keep tap-to-dismiss enabled globally. Subsequent pages with `HideSoftInputOnTapped=false` could still dismiss the keyboard on tap because the old page remained in the manager state.
### Description of Change
The fix removes the global `_contentPages` tracking list and the `NavigatedFrom` cleanup dependency. Instead, `FeatureEnabled` is computed from the currently focused input view by walking its live `Element.Parent` chain to find the enclosing `ContentPage`.
Android dispatch now resolves the focused view's live enclosing page once and only dispatches the tap-to-dismiss behavior when that page:
- exists in the live parent chain;
- has `HasNavigatedTo=true`;
- has `HideSoftInputOnTapped=true`;
- has a valid platform handler and Maui context;
- contains the tap point.
The winning fix intentionally does not retain a cached enclosing page fallback. If the old focused input is detached from the logical tree before a focus-lost event arrives, it no longer has a live enclosing page and therefore cannot keep the previous page's `HideSoftInputOnTapped=true` behavior active.
**Previous behavior:** `FeatureEnabled` returned `true` if any registered page had `HideSoftInputOnTapped=true`, even if that page was no longer the active or visible page.
**New behavior:** `FeatureEnabled` only reflects the `HideSoftInputOnTapped` value of the page enclosing the currently focused view. Detached focused views do not keep stale page state alive.
### Test Coverage
Added Android/iOS UI coverage for issue #35890:
- Shell `IsVisible=false` + absolute `GoToAsync("///HomePage35890")` navigation where `NavigatedFrom` is skipped.
- A focused outgoing login `Entry` before navigation, to catch stale focused-view/page state.
- A destination page with `HideSoftInputOnTapped=false`, verifying keyboard dismissal does not leak from the login page.
- A classic push-navigation scenario confirming per-page semantics still work when a previous page has `HideSoftInputOnTapped=true` and the current page has it set to `false`.
### What NOT to Do
- Do not reintroduce a global list of pages whose cleanup depends only on `NavigatedFrom`; Shell can skip that event in this scenario.
- Do not use a cached focused enclosing page as a fallback after the focused view detaches; it can preserve the same stale page behavior this fix removes.
- Do not rely on a test that navigates away from the login page without focusing the login input; that misses the stale focused-input path.
### Platforms Tested
- [x] Android
- [ ] Windows
- [x] iOS
- [ ] Mac
### Issues Fixed
Fixes #35890
🏁 Report — Final Recommendation
Comparative Candidate Report
Candidates
| Rank | Candidate | Regression evidence | Assessment |
|---|---|---|---|
| 1 | pr-plus-reviewer |
Not separately gate-run in this phase; based on the reviewer-applied sandbox candidate and the STEP 5a passing implementation direction. | Best candidate. It keeps the PR's intended live focused-view model, removes the stale cached page fallback found by expert review, and adds coverage for the focused outgoing login entry path. |
| 2 | try-fix-1 |
PASS. Android Issue35890 filtered UI run passed under the repository retry policy. |
Strong alternative. It removes the cached FocusedEnclosingPage fallback and only enables the feature when the focused view still resolves to a live enclosing page. It does not include the additional focused-login test/comment cleanup applied in pr-plus-reviewer. |
| 3 | pr |
Gate was provided as passed by the caller, but expert review found an unresolved stale-cache path in the current diff. Prior local review artifacts also recorded a failing gate result for the same stale-cache concern. | Directionally correct because it removes _contentPages, but not acceptable as submitted: GetEnclosingPage(FocusedView) ?? FocusedEnclosingPage can preserve old page state after detach. |
Candidate details
pr
The raw PR removes the global page list and computes behavior from the currently focused input view's page. However, it keeps _focusedViewEnclosingPage as a weak cached fallback. That fallback contradicts the PR description's claim that there is no cache/stale state and can preserve the old login page when Shell hides/navigates away without NavigatedFrom.
try-fix-1
try-fix-1 removes the cached fallback and uses only the live focused-view parent chain. It passed the Android Issue35890 runner after script-managed retry:
pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -TestFilter "FullyQualifiedName~Issue35890"
This candidate directly addresses the expert review error while changing only the focused-page source of truth.
pr-plus-reviewer
pr-plus-reviewer applies the expert review feedback to the PR approach: no cached focused page fallback, Android dispatch uses only the live focused page, and the regression test focuses UsernameEntry before navigating away so it covers stale focused-input/page state. It is the strict improvement over both the raw PR and try-fix-1 because it combines the passing core approach with the reviewer's requested coverage fix.
Winning candidate
pr-plus-reviewer wins. It keeps the PR's simplest correct model and removes the stale state path identified by expert review, while adding the missing focused-input regression coverage. Candidates with known or unresolved regression failures are ranked below candidates that passed or directly incorporate the passing STEP 5a fix direction.
🧭 Next Steps — review latest findings
No alternative fix was selected for this run. Review the session findings and CI results before merging.
kubaflo
left a comment
There was a problem hiding this comment.
Looks like some failing tests are related - could you please verify?
This comment has been minimized.
This comment has been minimized.
Tests Failure Analysis
Test Failure Review: No failures found - click to expandOverall verdict: No failures found — all 31 GitHub status checks completed as passing, neutral, or skipped, with no failing, pending, or inconclusive checks in the rollup. The Coverage: 31 checks · 31 passing · 0 failing · 0 pending · 0 inaccessible · 0 unmapped · 0 unexplained build legs · 0 unaccounted failing checks · 0 aborted failing checks · 0 canceled-build checks · 0 device-test unverified · 0 unattributed · 0 regressed-vs-base. Deterministic ceiling: No failures found. Builds (this PR): maui-pr buildId 1491599. Base sampling (main, 0 recent builds): none required — no PR failures to compare. Recommended actionNo test-failure action is needed; CI is clean for this commit. |
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
…l other pages if NavigatedFrom is never fired (#36238) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details: HideSoftInputOnTapped="True" on one page permanently affects all other pages ### Root Cause: HideSoftInputOnTappedChangedManager maintained an internal List<ContentPage> _contentPages to track which pages had HideSoftInputOnTapped="True" enabled. Pages were added to this list when NavigatedTo fired on them, and removed when NavigatedFrom fired. The FeatureEnabled property iterated this list to decide whether the global tap-to-dismiss keyboard behavior should be active. The critical flaw was that this entire cleanup mechanism depended solely on NavigatedFrom firing. In the specific scenario described in issue #35890 — where a ShellContent is hidden by setting IsVisible=false followed by an absolute GoToAsync("///Route") call — the Shell framework skips firing NavigatedFrom on the outgoing page entirely. As a result, the login page remained permanently registered in _contentPages with HideSoftInputOnTapped=true, keeping FeatureEnabled=true globally. Every subsequent page in the app — even pages with HideSoftInputOnTapped=false — had the keyboard dismissed on tap, because the stale login page entry in the list kept overriding the correct behavior. ### Description of Change: The fix eliminates _contentPages and the event-subscription model entirely. Instead of maintaining a tracked list of pages, FeatureEnabled is now computed on-demand by walking the Element.Parent chain upward from the currently focused input view (FocusedView). A new static helper GetEnclosingPage(IView?) traverses the parent chain until it finds the enclosing ContentPage, then reads HideSoftInputOnTapped and HasNavigatedTo directly from that live page instance. Since the result is computed from the actual visual tree at the moment of the query, there is no cache and no stale state possible — if the page is gone from the tree, FocusedView becomes null and FeatureEnabled returns false automatically. This also resolves a secondary regression that existed in an earlier Unloaded-based fix: if a page was re-shown after being hidden, the old approach would permanently break for that page instance because HasNavigatedTo was never reset. With the parent-chain walk, the live property values are always used, so re-show scenarios work correctly without any special handling. The UpdatePage method, which previously managed list mutations and event subscriptions, is now a single line that re-evaluates the tap watcher setup. The Android-specific OnWindowDispatchedTouch handler is similarly simplified — instead of looping over the list, it calls GetEnclosingPage(FocusedView) once and acts on the result directly. **Previous behavior:** `FeatureEnabled` returned `true` if *any* registered page had `HideSoftInputOnTapped` set to `true` — even if that page was no longer the active/visible page (this was the root cause of #35890, the "sticky" keyboard-dismissal bug). In effect, one page's setting could leak and affect the keyboard behavior on unrelated pages. **New behavior:** `FeatureEnabled` now only checks the page enclosing the currently focused view. Only the active page's `HideSoftInputOnTapped` value applies. **Tested the behavior in the following platforms: ** - [x] Android - [ ] Windows - [x] iOS - [ ] Mac ### Reference: N/A ### Issues Fixed: Fixes #35890 ### Screenshots | Before | After | |---------|--------| | <Video src="https://github.com/user-attachments/assets/7365d6d3-2cef-40ee-94b9-9ce2acc1b718" Width="600" Height="300"/> | <Video src="https://github.com/user-attachments/assets/10e59ac3-a381-4053-95c8-061eb8b3eaa4" Width="600" Height="300"/> | --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…l other pages if NavigatedFrom is never fired (#36238) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details: HideSoftInputOnTapped="True" on one page permanently affects all other pages ### Root Cause: HideSoftInputOnTappedChangedManager maintained an internal List<ContentPage> _contentPages to track which pages had HideSoftInputOnTapped="True" enabled. Pages were added to this list when NavigatedTo fired on them, and removed when NavigatedFrom fired. The FeatureEnabled property iterated this list to decide whether the global tap-to-dismiss keyboard behavior should be active. The critical flaw was that this entire cleanup mechanism depended solely on NavigatedFrom firing. In the specific scenario described in issue #35890 — where a ShellContent is hidden by setting IsVisible=false followed by an absolute GoToAsync("///Route") call — the Shell framework skips firing NavigatedFrom on the outgoing page entirely. As a result, the login page remained permanently registered in _contentPages with HideSoftInputOnTapped=true, keeping FeatureEnabled=true globally. Every subsequent page in the app — even pages with HideSoftInputOnTapped=false — had the keyboard dismissed on tap, because the stale login page entry in the list kept overriding the correct behavior. ### Description of Change: The fix eliminates _contentPages and the event-subscription model entirely. Instead of maintaining a tracked list of pages, FeatureEnabled is now computed on-demand by walking the Element.Parent chain upward from the currently focused input view (FocusedView). A new static helper GetEnclosingPage(IView?) traverses the parent chain until it finds the enclosing ContentPage, then reads HideSoftInputOnTapped and HasNavigatedTo directly from that live page instance. Since the result is computed from the actual visual tree at the moment of the query, there is no cache and no stale state possible — if the page is gone from the tree, FocusedView becomes null and FeatureEnabled returns false automatically. This also resolves a secondary regression that existed in an earlier Unloaded-based fix: if a page was re-shown after being hidden, the old approach would permanently break for that page instance because HasNavigatedTo was never reset. With the parent-chain walk, the live property values are always used, so re-show scenarios work correctly without any special handling. The UpdatePage method, which previously managed list mutations and event subscriptions, is now a single line that re-evaluates the tap watcher setup. The Android-specific OnWindowDispatchedTouch handler is similarly simplified — instead of looping over the list, it calls GetEnclosingPage(FocusedView) once and acts on the result directly. **Previous behavior:** `FeatureEnabled` returned `true` if *any* registered page had `HideSoftInputOnTapped` set to `true` — even if that page was no longer the active/visible page (this was the root cause of #35890, the "sticky" keyboard-dismissal bug). In effect, one page's setting could leak and affect the keyboard behavior on unrelated pages. **New behavior:** `FeatureEnabled` now only checks the page enclosing the currently focused view. Only the active page's `HideSoftInputOnTapped` value applies. **Tested the behavior in the following platforms: ** - [x] Android - [ ] Windows - [x] iOS - [ ] Mac ### Reference: N/A ### Issues Fixed: Fixes #35890 ### Screenshots | Before | After | |---------|--------| | <Video src="https://github.com/user-attachments/assets/7365d6d3-2cef-40ee-94b9-9ce2acc1b718" Width="600" Height="300"/> | <Video src="https://github.com/user-attachments/assets/10e59ac3-a381-4053-95c8-061eb8b3eaa4" Width="600" Height="300"/> | --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…l other pages if NavigatedFrom is never fired (#36238) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details: HideSoftInputOnTapped="True" on one page permanently affects all other pages ### Root Cause: HideSoftInputOnTappedChangedManager maintained an internal List<ContentPage> _contentPages to track which pages had HideSoftInputOnTapped="True" enabled. Pages were added to this list when NavigatedTo fired on them, and removed when NavigatedFrom fired. The FeatureEnabled property iterated this list to decide whether the global tap-to-dismiss keyboard behavior should be active. The critical flaw was that this entire cleanup mechanism depended solely on NavigatedFrom firing. In the specific scenario described in issue #35890 — where a ShellContent is hidden by setting IsVisible=false followed by an absolute GoToAsync("///Route") call — the Shell framework skips firing NavigatedFrom on the outgoing page entirely. As a result, the login page remained permanently registered in _contentPages with HideSoftInputOnTapped=true, keeping FeatureEnabled=true globally. Every subsequent page in the app — even pages with HideSoftInputOnTapped=false — had the keyboard dismissed on tap, because the stale login page entry in the list kept overriding the correct behavior. ### Description of Change: The fix eliminates _contentPages and the event-subscription model entirely. Instead of maintaining a tracked list of pages, FeatureEnabled is now computed on-demand by walking the Element.Parent chain upward from the currently focused input view (FocusedView). A new static helper GetEnclosingPage(IView?) traverses the parent chain until it finds the enclosing ContentPage, then reads HideSoftInputOnTapped and HasNavigatedTo directly from that live page instance. Since the result is computed from the actual visual tree at the moment of the query, there is no cache and no stale state possible — if the page is gone from the tree, FocusedView becomes null and FeatureEnabled returns false automatically. This also resolves a secondary regression that existed in an earlier Unloaded-based fix: if a page was re-shown after being hidden, the old approach would permanently break for that page instance because HasNavigatedTo was never reset. With the parent-chain walk, the live property values are always used, so re-show scenarios work correctly without any special handling. The UpdatePage method, which previously managed list mutations and event subscriptions, is now a single line that re-evaluates the tap watcher setup. The Android-specific OnWindowDispatchedTouch handler is similarly simplified — instead of looping over the list, it calls GetEnclosingPage(FocusedView) once and acts on the result directly. **Previous behavior:** `FeatureEnabled` returned `true` if *any* registered page had `HideSoftInputOnTapped` set to `true` — even if that page was no longer the active/visible page (this was the root cause of #35890, the "sticky" keyboard-dismissal bug). In effect, one page's setting could leak and affect the keyboard behavior on unrelated pages. **New behavior:** `FeatureEnabled` now only checks the page enclosing the currently focused view. Only the active page's `HideSoftInputOnTapped` value applies. **Tested the behavior in the following platforms: ** - [x] Android - [ ] Windows - [x] iOS - [ ] Mac ### Reference: N/A ### Issues Fixed: Fixes #35890 ### Screenshots | Before | After | |---------|--------| | <Video src="https://github.com/user-attachments/assets/7365d6d3-2cef-40ee-94b9-9ce2acc1b718" Width="600" Height="300"/> | <Video src="https://github.com/user-attachments/assets/10e59ac3-a381-4053-95c8-061eb8b3eaa4" Width="600" Height="300"/> | --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…l other pages if NavigatedFrom is never fired (#36238) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details: HideSoftInputOnTapped="True" on one page permanently affects all other pages ### Root Cause: HideSoftInputOnTappedChangedManager maintained an internal List<ContentPage> _contentPages to track which pages had HideSoftInputOnTapped="True" enabled. Pages were added to this list when NavigatedTo fired on them, and removed when NavigatedFrom fired. The FeatureEnabled property iterated this list to decide whether the global tap-to-dismiss keyboard behavior should be active. The critical flaw was that this entire cleanup mechanism depended solely on NavigatedFrom firing. In the specific scenario described in issue #35890 — where a ShellContent is hidden by setting IsVisible=false followed by an absolute GoToAsync("///Route") call — the Shell framework skips firing NavigatedFrom on the outgoing page entirely. As a result, the login page remained permanently registered in _contentPages with HideSoftInputOnTapped=true, keeping FeatureEnabled=true globally. Every subsequent page in the app — even pages with HideSoftInputOnTapped=false — had the keyboard dismissed on tap, because the stale login page entry in the list kept overriding the correct behavior. ### Description of Change: The fix eliminates _contentPages and the event-subscription model entirely. Instead of maintaining a tracked list of pages, FeatureEnabled is now computed on-demand by walking the Element.Parent chain upward from the currently focused input view (FocusedView). A new static helper GetEnclosingPage(IView?) traverses the parent chain until it finds the enclosing ContentPage, then reads HideSoftInputOnTapped and HasNavigatedTo directly from that live page instance. Since the result is computed from the actual visual tree at the moment of the query, there is no cache and no stale state possible — if the page is gone from the tree, FocusedView becomes null and FeatureEnabled returns false automatically. This also resolves a secondary regression that existed in an earlier Unloaded-based fix: if a page was re-shown after being hidden, the old approach would permanently break for that page instance because HasNavigatedTo was never reset. With the parent-chain walk, the live property values are always used, so re-show scenarios work correctly without any special handling. The UpdatePage method, which previously managed list mutations and event subscriptions, is now a single line that re-evaluates the tap watcher setup. The Android-specific OnWindowDispatchedTouch handler is similarly simplified — instead of looping over the list, it calls GetEnclosingPage(FocusedView) once and acts on the result directly. **Previous behavior:** `FeatureEnabled` returned `true` if *any* registered page had `HideSoftInputOnTapped` set to `true` — even if that page was no longer the active/visible page (this was the root cause of #35890, the "sticky" keyboard-dismissal bug). In effect, one page's setting could leak and affect the keyboard behavior on unrelated pages. **New behavior:** `FeatureEnabled` now only checks the page enclosing the currently focused view. Only the active page's `HideSoftInputOnTapped` value applies. **Tested the behavior in the following platforms: ** - [x] Android - [ ] Windows - [x] iOS - [ ] Mac ### Reference: N/A ### Issues Fixed: Fixes #35890 ### Screenshots | Before | After | |---------|--------| | <Video src="https://github.com/user-attachments/assets/7365d6d3-2cef-40ee-94b9-9ce2acc1b718" Width="600" Height="300"/> | <Video src="https://github.com/user-attachments/assets/10e59ac3-a381-4053-95c8-061eb8b3eaa4" Width="600" Height="300"/> | --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…l other pages if NavigatedFrom is never fired (#36238) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details: HideSoftInputOnTapped="True" on one page permanently affects all other pages ### Root Cause: HideSoftInputOnTappedChangedManager maintained an internal List<ContentPage> _contentPages to track which pages had HideSoftInputOnTapped="True" enabled. Pages were added to this list when NavigatedTo fired on them, and removed when NavigatedFrom fired. The FeatureEnabled property iterated this list to decide whether the global tap-to-dismiss keyboard behavior should be active. The critical flaw was that this entire cleanup mechanism depended solely on NavigatedFrom firing. In the specific scenario described in issue #35890 — where a ShellContent is hidden by setting IsVisible=false followed by an absolute GoToAsync("///Route") call — the Shell framework skips firing NavigatedFrom on the outgoing page entirely. As a result, the login page remained permanently registered in _contentPages with HideSoftInputOnTapped=true, keeping FeatureEnabled=true globally. Every subsequent page in the app — even pages with HideSoftInputOnTapped=false — had the keyboard dismissed on tap, because the stale login page entry in the list kept overriding the correct behavior. ### Description of Change: The fix eliminates _contentPages and the event-subscription model entirely. Instead of maintaining a tracked list of pages, FeatureEnabled is now computed on-demand by walking the Element.Parent chain upward from the currently focused input view (FocusedView). A new static helper GetEnclosingPage(IView?) traverses the parent chain until it finds the enclosing ContentPage, then reads HideSoftInputOnTapped and HasNavigatedTo directly from that live page instance. Since the result is computed from the actual visual tree at the moment of the query, there is no cache and no stale state possible — if the page is gone from the tree, FocusedView becomes null and FeatureEnabled returns false automatically. This also resolves a secondary regression that existed in an earlier Unloaded-based fix: if a page was re-shown after being hidden, the old approach would permanently break for that page instance because HasNavigatedTo was never reset. With the parent-chain walk, the live property values are always used, so re-show scenarios work correctly without any special handling. The UpdatePage method, which previously managed list mutations and event subscriptions, is now a single line that re-evaluates the tap watcher setup. The Android-specific OnWindowDispatchedTouch handler is similarly simplified — instead of looping over the list, it calls GetEnclosingPage(FocusedView) once and acts on the result directly. **Previous behavior:** `FeatureEnabled` returned `true` if *any* registered page had `HideSoftInputOnTapped` set to `true` — even if that page was no longer the active/visible page (this was the root cause of #35890, the "sticky" keyboard-dismissal bug). In effect, one page's setting could leak and affect the keyboard behavior on unrelated pages. **New behavior:** `FeatureEnabled` now only checks the page enclosing the currently focused view. Only the active page's `HideSoftInputOnTapped` value applies. **Tested the behavior in the following platforms: ** - [x] Android - [ ] Windows - [x] iOS - [ ] Mac ### Reference: N/A ### Issues Fixed: Fixes #35890 ### Screenshots | Before | After | |---------|--------| | <Video src="https://github.com/user-attachments/assets/7365d6d3-2cef-40ee-94b9-9ce2acc1b718" Width="600" Height="300"/> | <Video src="https://github.com/user-attachments/assets/10e59ac3-a381-4053-95c8-061eb8b3eaa4" Width="600" Height="300"/> | --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Issue Details:
HideSoftInputOnTapped="True" on one page permanently affects all other pages
Root Cause:
HideSoftInputOnTappedChangedManager maintained an internal List _contentPages to track which pages had HideSoftInputOnTapped="True" enabled. Pages were added to this list when NavigatedTo fired on them, and removed when NavigatedFrom fired. The FeatureEnabled property iterated this list to decide whether the global tap-to-dismiss keyboard behavior should be active. The critical flaw was that this entire cleanup mechanism depended solely on NavigatedFrom firing. In the specific scenario described in issue #35890 — where a ShellContent is hidden by setting IsVisible=false followed by an absolute GoToAsync("///Route") call — the Shell framework skips firing NavigatedFrom on the outgoing page entirely. As a result, the login page remained permanently registered in _contentPages with HideSoftInputOnTapped=true, keeping FeatureEnabled=true globally. Every subsequent page in the app — even pages with HideSoftInputOnTapped=false — had the keyboard dismissed on tap, because the stale login page entry in the list kept overriding the correct behavior.
Description of Change:
The fix eliminates _contentPages and the event-subscription model entirely. Instead of maintaining a tracked list of pages, FeatureEnabled is now computed on-demand by walking the Element.Parent chain upward from the currently focused input view (FocusedView). A new static helper GetEnclosingPage(IView?) traverses the parent chain until it finds the enclosing ContentPage, then reads HideSoftInputOnTapped and HasNavigatedTo directly from that live page instance. Since the result is computed from the actual visual tree at the moment of the query, there is no cache and no stale state possible — if the page is gone from the tree, FocusedView becomes null and FeatureEnabled returns false automatically. This also resolves a secondary regression that existed in an earlier Unloaded-based fix: if a page was re-shown after being hidden, the old approach would permanently break for that page instance because HasNavigatedTo was never reset. With the parent-chain walk, the live property values are always used, so re-show scenarios work correctly without any special handling. The UpdatePage method, which previously managed list mutations and event subscriptions, is now a single line that re-evaluates the tap watcher setup. The Android-specific OnWindowDispatchedTouch handler is similarly simplified — instead of looping over the list, it calls GetEnclosingPage(FocusedView) once and acts on the result directly.
Previous behavior:
FeatureEnabledreturnedtrueif any registered page hadHideSoftInputOnTappedset totrue— even if that page was no longer the active/visiblepage (this was the root cause of #35890, the "sticky" keyboard-dismissal bug). In effect, one
page's setting could leak and affect the keyboard behavior on unrelated pages.
New behavior:
FeatureEnablednow only checks the page enclosing the currently focusedview. Only the active page's
HideSoftInputOnTappedvalue applies.**Tested the behavior in the following platforms: **
Reference:
N/A
Issues Fixed:
Fixes #35890
Screenshots
Before_35890.mov
After_35890.mov