Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue35788.cs
Original file line number Diff line number Diff line change
@@ -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 = "<html><body><h1>Hello</h1></body></html>" };

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}";
}
}
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions src/Core/src/Handlers/WebView/WebViewHandler.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
5 changes: 5 additions & 0 deletions src/Core/src/Platform/Android/MauiWebView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
26 changes: 24 additions & 2 deletions src/Core/src/Platform/Android/MauiWebViewClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment thread
praveenkumarkarunanithi marked this conversation as resolved.
{
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))
Comment thread
praveenkumarkarunanithi marked this conversation as resolved.
{
// 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.
Expand Down
12 changes: 10 additions & 2 deletions src/Core/src/Platform/Android/WebViewExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Comment thread
praveenkumarkarunanithi marked this conversation as resolved.
Comment thread
praveenkumarkarunanithi marked this conversation as resolved.
{
mauiWebView.IsLoadingForLayout = true;
platformWebView.LoadUrl("about:blank");
}
}
}

Expand Down
Loading