diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue35788.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue35788.cs
new file mode 100644
index 000000000000..12c95524a5b8
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue35788.cs
@@ -0,0 +1,51 @@
+namespace Maui.Controls.Sample.Issues;
+
+[Issue(IssueTracker.Github, 35788, "[Android] WebView CanGoBack returns true unexpectedly on first page due to spurious about:blank history entry", PlatformAffected.Android)]
+public class Issue35788 : ContentPage
+{
+ const string StatusLabelId = "Issue35788StatusLabel";
+ const string NavigateButtonId = "Issue35788NavigateButton";
+
+ readonly Label _statusLabel;
+ readonly WebView _webView;
+
+ public Issue35788()
+ {
+ _statusLabel = new Label
+ {
+ AutomationId = StatusLabelId,
+ Text = "Waiting"
+ };
+
+ _webView = new WebView
+ {
+ HeightRequest = 300
+ };
+
+ _webView.Navigated += OnWebViewNavigated;
+
+ var navigateButton = new Button
+ {
+ AutomationId = NavigateButtonId,
+ Text = "Load Page"
+ };
+
+ navigateButton.Clicked += (s, e) =>
+ _webView.Source = new HtmlWebViewSource { Html = "
Hello
" };
+
+ Content = new VerticalStackLayout
+ {
+ Padding = 20,
+ Spacing = 10,
+ Children = { navigateButton, _statusLabel, _webView }
+ };
+ }
+
+ void OnWebViewNavigated(object sender, WebNavigatedEventArgs e)
+ {
+ if (e.Result == WebNavigationResult.Success)
+ _statusLabel.Text = _webView.CanGoBack ? "CanGoBack=True" : "CanGoBack=False";
+ else
+ _statusLabel.Text = $"NavFailed:{e.Result}";
+ }
+}
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35788.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35788.cs
new file mode 100644
index 000000000000..42c5dadc5025
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35788.cs
@@ -0,0 +1,32 @@
+#if ANDROID
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Issues;
+
+public class Issue35788 : _IssuesUITest
+{
+ public Issue35788(TestDevice device) : base(device)
+ {
+ }
+
+ public override string Issue => "[Android] WebView CanGoBack returns true unexpectedly on first page due to spurious about:blank history entry";
+
+ [Test]
+ [Category(UITestCategories.WebView)]
+ public void WebViewCanGoBackShouldBeFalseOnFirstPage()
+ {
+ App.WaitForElement("Issue35788NavigateButton");
+ App.Tap("Issue35788NavigateButton");
+
+ App.WaitForTextToBePresentInElement("Issue35788StatusLabel", "CanGoBack=");
+
+ var statusText = App.FindElement("Issue35788StatusLabel").GetText();
+
+ Assert.That(statusText, Is.EqualTo("CanGoBack=False"),
+ "WebView.CanGoBack should be false on the first navigated page. " +
+ "If true, the about:blank layout entry was not cleared from the native history stack.");
+ }
+}
+#endif
diff --git a/src/Core/src/Handlers/WebView/WebViewHandler.Android.cs b/src/Core/src/Handlers/WebView/WebViewHandler.Android.cs
index 12fbc975f6d7..4dff350474a7 100644
--- a/src/Core/src/Handlers/WebView/WebViewHandler.Android.cs
+++ b/src/Core/src/Handlers/WebView/WebViewHandler.Android.cs
@@ -64,6 +64,14 @@ protected override void DisconnectHandler(AWebView platformView)
webChromeClient.Disconnect();
}
+ // Reset layout flag so a stale true value does not trigger ClearHistory()
+ // if this handler is re-connected (e.g., Shell tab switch). (#35788)
+ if (platformView is MauiWebView mauiWebView)
+ {
+ mauiWebView.IsLoadingForLayout = false;
+ }
+
+ platformView.SetWebViewClient(null!);
platformView.SetWebChromeClient(null);
platformView.StopLoading();
diff --git a/src/Core/src/Platform/Android/MauiWebView.cs b/src/Core/src/Platform/Android/MauiWebView.cs
index 5ccc05cdfeef..c1abe5859adb 100644
--- a/src/Core/src/Platform/Android/MauiWebView.cs
+++ b/src/Core/src/Platform/Android/MauiWebView.cs
@@ -21,6 +21,11 @@ public class MauiWebView : WebView, IWebViewDelegate
// https://github.com/dotnet/maui/issues/35771
bool _isAutoSizing;
+ // Tracks whether about:blank was loaded synthetically for layout (null source).
+ // MauiWebViewClient clears this entry from the native back stack once the real URL loads,
+ // preventing CanGoBack() returning true unexpectedly. Fixes #35788.
+ internal bool IsLoadingForLayout { get; set; }
+
public MauiWebView(WebViewHandler handler, Context context) : base(context)
{
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
diff --git a/src/Core/src/Platform/Android/MauiWebViewClient.cs b/src/Core/src/Platform/Android/MauiWebViewClient.cs
index aa554dc0e2d5..260f59ba44bd 100644
--- a/src/Core/src/Platform/Android/MauiWebViewClient.cs
+++ b/src/Core/src/Platform/Android/MauiWebViewClient.cs
@@ -56,20 +56,42 @@ public override void OnPageStarted(WebView? view, string? url, Bitmap? favicon)
public override void OnPageFinished(WebView? view, string? url)
{
if (!_handler.TryGetTarget(out var handler) || handler.VirtualView == null || string.IsNullOrWhiteSpace(url))
+ {
return;
+ }
bool navigate = _navigationResult != WebNavigationResult.Failure || !GetValidUrl(url).Equals(_lastUrlNavigatedCancel, StringComparison.OrdinalIgnoreCase);
_lastUrlNavigatedCancel = _navigationResult == WebNavigationResult.Cancel ? url : null;
+ var mauiWebView = view as MauiWebView;
+ bool isLayoutLoad = mauiWebView?.IsLoadingForLayout == true;
+
// Skip Navigated event for about:blank to prevent unwanted events when Source is null
if (navigate && !IsBlankNavigation(url))
{
- handler.VirtualView.Navigated(handler.CurrentNavigationEvent, GetValidUrl(url), _navigationResult);
+ // Clear the synthetic about:blank entry (loaded for layout, see #32030) now that
+ // the real URL is current. ClearHistory() removes all entries except the current
+ // page, ensuring CanGoBack() is already false when Navigated fires (#35788).
+ if (isLayoutLoad)
+ {
+ mauiWebView!.ClearHistory();
+ mauiWebView!.IsLoadingForLayout = false;
+ // Called BEFORE Navigated fires so user handlers observe CanGoBack=false immediately.
+ handler?.PlatformView?.UpdateCanGoBackForward(handler.VirtualView);
+ }
+
+ handler!.VirtualView.Navigated(handler.CurrentNavigationEvent, GetValidUrl(url), _navigationResult);
+ }
+ else if (isLayoutLoad && (_navigationResult == WebNavigationResult.Failure || _navigationResult == WebNavigationResult.Cancel))
+ {
+ // Navigation failed or canceled — reset the layout flag so a subsequent successful load
+ // does not incorrectly trigger ClearHistory() (#35788).
+ mauiWebView!.IsLoadingForLayout = false;
}
handler.SyncPlatformCookiesToVirtualView(url);
- handler?.PlatformView.UpdateCanGoBackForward(handler.VirtualView);
+ handler?.PlatformView?.UpdateCanGoBackForward(handler.VirtualView);
// Only inject the scroll-capture observer when the WebView is hosted inside
// a RefreshView – avoids unnecessary JS overhead for standalone WebViews.
diff --git a/src/Core/src/Platform/Android/WebViewExtensions.cs b/src/Core/src/Platform/Android/WebViewExtensions.cs
index f09cd7397f23..448248e7f5e2 100644
--- a/src/Core/src/Platform/Android/WebViewExtensions.cs
+++ b/src/Core/src/Platform/Android/WebViewExtensions.cs
@@ -27,8 +27,16 @@ public static void UpdateSource(this AWebView platformWebView, IWebView webView,
}
else
{
- // Load about:blank when source is null to ensure proper layout bounds
- platformWebView.LoadUrl("about:blank");
+ // Load about:blank to constrain the WebView within its layout bounds when source
+ // is null, preventing overflow in grid cells (#32030). Flag IsLoadingForLayout so
+ // MauiWebViewClient can remove this synthetic entry from the native back stack
+ // once the real URL loads, fixing CanGoBack() returning true unexpectedly (#35788).
+ // Guard: skip when real history exists so ClearHistory() does not destroy back entries.
+ if (platformWebView is MauiWebView mauiWebView && !platformWebView.CanGoBack() && !platformWebView.CanGoForward())
+ {
+ mauiWebView.IsLoadingForLayout = true;
+ platformWebView.LoadUrl("about:blank");
+ }
}
}