diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21631.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21631.xaml new file mode 100644 index 000000000000..c35fc16d51e3 --- /dev/null +++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21631.xaml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21631.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21631.xaml.cs new file mode 100644 index 000000000000..b371cb4437ba --- /dev/null +++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21631.xaml.cs @@ -0,0 +1,15 @@ +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Xaml; + +namespace Maui.Controls.Sample.Issues +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + [Issue(IssueTracker.Github, 21631, "Injecting base tag in Webview2 works", PlatformAffected.UWP)] + public partial class Issue21631 : ContentPage + { + public Issue21631() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue21631.cs b/src/Controls/tests/UITests/Tests/Issues/Issue21631.cs new file mode 100644 index 000000000000..c14ad533a888 --- /dev/null +++ b/src/Controls/tests/UITests/Tests/Issues/Issue21631.cs @@ -0,0 +1,24 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.AppiumTests.Issues +{ + public class Issue21631 : _IssuesUITest + { + public Issue21631(TestDevice device) : base(device) { } + + public override string Issue => + "Injecting base tag in Webview2 works"; + + [Test] + public async Task NavigateToStringWithWebviewWorks() + { + this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Android, TestDevice.Mac, TestDevice.iOS }); + + App.WaitForElement("WaitForWebView"); + await Task.Delay(500); + VerifyScreenshot(); + } + } +} \ No newline at end of file diff --git a/src/Controls/tests/UITests/snapshots/windows/NavigateToStringWithWebviewWorks.png b/src/Controls/tests/UITests/snapshots/windows/NavigateToStringWithWebviewWorks.png new file mode 100644 index 000000000000..aecb430ed3c8 Binary files /dev/null and b/src/Controls/tests/UITests/snapshots/windows/NavigateToStringWithWebviewWorks.png differ diff --git a/src/Core/src/Platform/Windows/MauiWebView.cs b/src/Core/src/Platform/Windows/MauiWebView.cs index e21f7586e59e..932db7b69f4d 100644 --- a/src/Core/src/Platform/Windows/MauiWebView.cs +++ b/src/Core/src/Platform/Windows/MauiWebView.cs @@ -1,7 +1,6 @@ using System; using System.Diagnostics; using System.Text; -using System.Text.RegularExpressions; using Microsoft.Maui.ApplicationModel; using Microsoft.UI.Xaml.Controls; using Windows.ApplicationModel; @@ -28,8 +27,6 @@ public MauiWebView(WebViewHandler handler) SetupPlatformEvents(); } - WebView2? _internalWebView; - // Arbitrary local host name for virtual folder mapping const string LocalHostName = "appdir"; const string LocalScheme = $"https://{LocalHostName}/"; @@ -48,66 +45,29 @@ public MauiWebView(WebViewHandler handler) : AppContext.BaseDirectory; public async void LoadHtml(string? html, string? baseUrl) - { + { var mapBaseDirectory = false; - if (string.IsNullOrEmpty(baseUrl)) { baseUrl = LocalScheme; mapBaseDirectory = true; } - // Generate a base tag for the document - var baseTag = $""; - - string htmlWithBaseTag; - - // Set up an internal WebView we can use to load and parse the original HTML string - // Make _internalWebView a field instead of local variable to avoid garbage collection - _internalWebView = new WebView2(); - - // TODO: For now, the CoreWebView2 won't be created without either setting Source or - // calling EnsureCoreWebView2Async(). - await _internalWebView.EnsureCoreWebView2Async(); + await EnsureCoreWebView2Async(); - // When the 'navigation' to the original HTML string is done, we can modify it to include our tag - _internalWebView.NavigationCompleted += async (sender, args) => - { - // Generate a version of the script with the correct tag - var script = BaseInsertionScript.Replace("baseTag", baseTag, StringComparison.Ordinal); - - // Run it and retrieve the updated HTML from our WebView - await sender.ExecuteScriptAsync(script); - htmlWithBaseTag = await sender.ExecuteScriptAsync("document.documentElement.outerHTML;"); + if (mapBaseDirectory) + { + CoreWebView2.SetVirtualHostNameToFolderMapping( + LocalHostName, + ApplicationPath, + Web.WebView2.Core.CoreWebView2HostResourceAccessKind.Allow); + } - htmlWithBaseTag = Regex.Unescape(htmlWithBaseTag); - htmlWithBaseTag = htmlWithBaseTag.Remove(0, 1); - htmlWithBaseTag = htmlWithBaseTag.Remove(htmlWithBaseTag.Length - 1, 1); - - await EnsureCoreWebView2Async(); + // Insert script to set the base tag + var script = GetBaseTagInsertionScript(baseUrl); + var htmlWithScript = $"{script}\n{html}"; - if (mapBaseDirectory) - { - CoreWebView2.SetVirtualHostNameToFolderMapping( - LocalHostName, - ApplicationPath, - Web.WebView2.Core.CoreWebView2HostResourceAccessKind.Allow); - } - - // Set the HTML for the 'real' WebView to the updated HTML - NavigateToString(!string.IsNullOrEmpty(htmlWithBaseTag) ? htmlWithBaseTag : html); - - // Free up memory after we're done with _internalWebView - if (_internalWebView.IsValid()) - { - _internalWebView.Close(); - _internalWebView = null; - } - }; - - // Kick off the initial navigation - if (_internalWebView.IsValid()) - _internalWebView.NavigateToString(html); + NavigateToString(htmlWithScript); } public async void LoadUrl(string? url) @@ -184,9 +144,16 @@ static bool IsWebView2DataUriWithBaseUrl(string? uri) Convert.FromBase64String( uri.Substring(dataUriBase64.Length))); + var localSchemeScript = GetBaseTagInsertionScript(LocalScheme); return decodedHtml.Contains( - $""; + return $""; + } } }