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
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue21631">
<WebView
AutomationId="WaitForWebView"
x:Name="WaitForWebView">
<WebView.Source>
<HtmlWebViewSource
Html="&lt;html&gt;&lt;body&gt;&lt;h1&gt;hello world&lt;/h1&gt;&lt;img src='appiconLargeTile.scale-100.png'/&gt;&lt;/html&gt;"/>
</WebView.Source>
</WebView>
</ContentPage>
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
24 changes: 24 additions & 0 deletions src/Controls/tests/UITests/Tests/Issues/Issue21631.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
75 changes: 21 additions & 54 deletions src/Core/src/Platform/Windows/MauiWebView.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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}/";
Expand All @@ -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 = $"<base href=\"{baseUrl}\"></base>";

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 <base> tag
_internalWebView.NavigationCompleted += async (sender, args) =>
{
// Generate a version of the <base> script with the correct <base> 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)
Expand Down Expand Up @@ -184,9 +144,16 @@ static bool IsWebView2DataUriWithBaseUrl(string? uri)
Convert.FromBase64String(
uri.Substring(dataUriBase64.Length)));

var localSchemeScript = GetBaseTagInsertionScript(LocalScheme);
return decodedHtml.Contains(
$"<base href=\"{LocalScheme}",
localSchemeScript,
StringComparison.OrdinalIgnoreCase);
}

static string GetBaseTagInsertionScript(string baseUrl)
{
var baseTag = $"<base href=\"{baseUrl}\"></base>";
return $"<script>{BaseInsertionScript.Replace("baseTag", baseTag, StringComparison.Ordinal)}</script>";
}
}
}