From 3006205226bc30860c6f20eb0ea2b628f1573547 Mon Sep 17 00:00:00 2001 From: Mike Corsaro Date: Wed, 17 Apr 2024 14:22:51 -0700 Subject: [PATCH 1/3] Remove 2nd webview2 used to add `base` tag to passed html --- src/Core/src/Platform/Windows/MauiWebView.cs | 77 +++++--------------- 1 file changed, 18 insertions(+), 59 deletions(-) diff --git a/src/Core/src/Platform/Windows/MauiWebView.cs b/src/Core/src/Platform/Windows/MauiWebView.cs index e21f7586e59e..7039c1546a3e 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,19 +27,15 @@ public MauiWebView(WebViewHandler handler) SetupPlatformEvents(); } - WebView2? _internalWebView; - // Arbitrary local host name for virtual folder mapping const string LocalHostName = "appdir"; const string LocalScheme = $"https://{LocalHostName}/"; // Script to insert a tag into an HTML document const string BaseInsertionScript = @" - var head = document.getElementsByTagName('head')[0]; - var bases = head.getElementsByTagName('base'); - if(bases.length == 0) { - head.innerHTML = 'baseTag' + head.innerHTML; - }"; + var base = document.createElement('base'); + base.href = 'baseTag'; + document.getElementsByTagName('head')[0].appendChild(base);"; // Allow for packaged/unpackaged app support static string ApplicationPath => AppInfoUtils.IsPackagedApp @@ -48,66 +43,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(); - - // 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;"); - - htmlWithBaseTag = Regex.Unescape(htmlWithBaseTag); - htmlWithBaseTag = htmlWithBaseTag.Remove(0, 1); - htmlWithBaseTag = htmlWithBaseTag.Remove(htmlWithBaseTag.Length - 1, 1); - - await EnsureCoreWebView2Async(); + await EnsureCoreWebView2Async(); - 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); + if (mapBaseDirectory) + { + CoreWebView2.SetVirtualHostNameToFolderMapping( + LocalHostName, + ApplicationPath, + Web.WebView2.Core.CoreWebView2HostResourceAccessKind.Allow); + } - // Free up memory after we're done with _internalWebView - if (_internalWebView.IsValid()) - { - _internalWebView.Close(); - _internalWebView = null; - } - }; + // Insert script to set the base tag + var script = $""; + var htmlWithScript = $"{script}\n{html}"; - // Kick off the initial navigation - if (_internalWebView.IsValid()) - _internalWebView.NavigateToString(html); + NavigateToString(htmlWithScript); } public async void LoadUrl(string? url) @@ -184,8 +142,9 @@ static bool IsWebView2DataUriWithBaseUrl(string? uri) Convert.FromBase64String( uri.Substring(dataUriBase64.Length))); + var localSchemeScript = $""; return decodedHtml.Contains( - $" Date: Wed, 17 Apr 2024 14:41:06 -0700 Subject: [PATCH 2/3] Revert script to OG version --- src/Core/src/Platform/Windows/MauiWebView.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Core/src/Platform/Windows/MauiWebView.cs b/src/Core/src/Platform/Windows/MauiWebView.cs index 7039c1546a3e..932db7b69f4d 100644 --- a/src/Core/src/Platform/Windows/MauiWebView.cs +++ b/src/Core/src/Platform/Windows/MauiWebView.cs @@ -33,9 +33,11 @@ public MauiWebView(WebViewHandler handler) // Script to insert a tag into an HTML document const string BaseInsertionScript = @" - var base = document.createElement('base'); - base.href = 'baseTag'; - document.getElementsByTagName('head')[0].appendChild(base);"; + var head = document.getElementsByTagName('head')[0]; + var bases = head.getElementsByTagName('base'); + if(bases.length == 0) { + head.innerHTML = 'baseTag' + head.innerHTML; + }"; // Allow for packaged/unpackaged app support static string ApplicationPath => AppInfoUtils.IsPackagedApp @@ -62,7 +64,7 @@ public async void LoadHtml(string? html, string? baseUrl) } // Insert script to set the base tag - var script = $""; + var script = GetBaseTagInsertionScript(baseUrl); var htmlWithScript = $"{script}\n{html}"; NavigateToString(htmlWithScript); @@ -142,10 +144,16 @@ static bool IsWebView2DataUriWithBaseUrl(string? uri) Convert.FromBase64String( uri.Substring(dataUriBase64.Length))); - var localSchemeScript = $""; + var localSchemeScript = GetBaseTagInsertionScript(LocalScheme); return decodedHtml.Contains( localSchemeScript, StringComparison.OrdinalIgnoreCase); } + + static string GetBaseTagInsertionScript(string baseUrl) + { + var baseTag = $""; + return $""; + } } } From 143550b8c37fff20c1b5ee631d55b2c706d29826 Mon Sep 17 00:00:00 2001 From: Mike Corsaro Date: Mon, 22 Apr 2024 09:14:43 -0700 Subject: [PATCH 3/3] Add visual test for webview2 on windows --- .../Issues/Issue21631.xaml | 13 ++++++++++ .../Issues/Issue21631.xaml.cs | 15 +++++++++++ .../tests/UITests/Tests/Issues/Issue21631.cs | 24 ++++++++++++++++++ .../NavigateToStringWithWebviewWorks.png | Bin 0 -> 10093 bytes 4 files changed, 52 insertions(+) create mode 100644 src/Controls/samples/Controls.Sample.UITests/Issues/Issue21631.xaml create mode 100644 src/Controls/samples/Controls.Sample.UITests/Issues/Issue21631.xaml.cs create mode 100644 src/Controls/tests/UITests/Tests/Issues/Issue21631.cs create mode 100644 src/Controls/tests/UITests/snapshots/windows/NavigateToStringWithWebviewWorks.png 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 0000000000000000000000000000000000000000..aecb430ed3c8995d685a84ee2822d108b006ca72 GIT binary patch literal 10093 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 z