-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
MauiWebView.cs
148 lines (121 loc) · 4.52 KB
/
MauiWebView.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;
using Microsoft.Maui.ApplicationModel;
using Microsoft.UI.Xaml.Controls;
using Windows.ApplicationModel;
namespace Microsoft.Maui.Platform
{
public class MauiWebView : WebView2, IWebViewDelegate
{
readonly WeakReference<WebViewHandler> _handler;
public MauiWebView(WebViewHandler handler)
{
ArgumentNullException.ThrowIfNull(handler, nameof(handler));
_handler = new WeakReference<WebViewHandler>(handler);
NavigationStarting += (sender, args) =>
{
// Auto map local virtual app dir host, e.g. if navigating back to local site from a link to an external site
if (args?.Uri?.ToLowerInvariant().StartsWith(LocalScheme.TrimEnd('/').ToLowerInvariant()) == true)
{
CoreWebView2.SetVirtualHostNameToFolderMapping(
LocalHostName,
ApplicationPath,
Web.WebView2.Core.CoreWebView2HostResourceAccessKind.Allow);
}
// Auto unmap local virtual app dir host if navigating to any other potentially unsafe domain
else
{
CoreWebView2.ClearVirtualHostNameToFolderMapping(LocalHostName);
}
};
}
WebView2? _internalWebView;
// Arbitrary local host name for virtual folder mapping
const string LocalHostName = "appdir";
const string LocalScheme = $"https://{LocalHostName}/";
// Script to insert a <base> 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;
}";
// Allow for packaged/unpackaged app support
string ApplicationPath => AppInfoUtils.IsPackagedApp
? Package.Current.InstalledLocation.Path
: 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();
// 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;");
htmlWithBaseTag = Regex.Unescape(htmlWithBaseTag);
htmlWithBaseTag = htmlWithBaseTag.Remove(0, 1);
htmlWithBaseTag = htmlWithBaseTag.Remove(htmlWithBaseTag.Length - 1, 1);
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);
// 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);
}
public async void LoadUrl(string? url)
{
Uri uri = new Uri(url ?? string.Empty, UriKind.RelativeOrAbsolute);
if (!uri.IsAbsoluteUri)
{
await EnsureCoreWebView2Async();
CoreWebView2.SetVirtualHostNameToFolderMapping(
LocalHostName,
ApplicationPath,
Web.WebView2.Core.CoreWebView2HostResourceAccessKind.Allow);
uri = new Uri(LocalScheme + url, UriKind.RelativeOrAbsolute);
}
if (_handler.TryGetTarget(out var handler))
await handler.SyncPlatformCookies(uri.AbsoluteUri);
try
{
Source = uri;
}
catch (Exception exc)
{
Debug.WriteLine(nameof(MauiWebView), $"Failed to load: {uri} {exc}");
}
}
}
}