-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
WebKitWebViewClient.cs
204 lines (175 loc) · 6.49 KB
/
WebKitWebViewClient.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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
using System;
using System.Runtime.Versioning;
using Android.Content;
using Android.Runtime;
using Android.Webkit;
using Java.Net;
using AWebView = Android.Webkit.WebView;
namespace Microsoft.AspNetCore.Components.WebView.Maui
{
[SupportedOSPlatform("android21.0")]
internal class WebKitWebViewClient : WebViewClient
{
// Using an IP address means that WebView doesn't wait for any DNS resolution,
// making it substantially faster. Note that this isn't real HTTP traffic, since
// we intercept all the requests within this origin.
private static readonly string AppOrigin = $"https://{BlazorWebView.AppHostAddress}/";
private static readonly Uri AppOriginUri = new(AppOrigin);
private readonly BlazorWebViewHandler? _webViewHandler;
public WebKitWebViewClient(BlazorWebViewHandler webViewHandler)
{
ArgumentNullException.ThrowIfNull(webViewHandler);
_webViewHandler = webViewHandler;
}
protected WebKitWebViewClient(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
// This constructor is called whenever the .NET proxy was disposed, and it was recreated by Java. It also
// happens when overridden methods are called between execution of this constructor and the one above.
// because of these facts, we have to check all methods below for null field references and properties.
}
public override bool ShouldOverrideUrlLoading(AWebView? view, IWebResourceRequest? request)
#pragma warning disable CA1416 // TODO: base.ShouldOverrideUrlLoading(,) is supported from Android 24.0
=> ShouldOverrideUrlLoadingCore(request) || base.ShouldOverrideUrlLoading(view, request);
#pragma warning restore CA1416
private bool ShouldOverrideUrlLoadingCore(IWebResourceRequest? request)
{
if (_webViewHandler is null || !Uri.TryCreate(request?.Url?.ToString(), UriKind.RelativeOrAbsolute, out var uri))
{
return false;
}
// This method never gets called for navigation to a new window ('_blank'),
// so we know we can safely invoke the UrlLoading event.
var callbackArgs = UrlLoadingEventArgs.CreateWithDefaultLoadingStrategy(uri, AppOriginUri);
_webViewHandler.UrlLoading(callbackArgs);
if (callbackArgs.UrlLoadingStrategy == UrlLoadingStrategy.OpenExternally)
{
try
{
var intent = Intent.ParseUri(uri.OriginalString, IntentUriType.Scheme);
_webViewHandler.Context.StartActivity(intent);
}
catch (URISyntaxException)
{
// This can occur if there is a problem with the URI formatting given its specified scheme.
// Other platforms will silently ignore formatting issues, so we do the same here.
}
catch (ActivityNotFoundException)
{
// Do nothing if there is no activity to handle the intent. This is consistent with the
// behavior on other platforms when a URL with an unknown scheme is clicked.
}
return true;
}
return callbackArgs.UrlLoadingStrategy != UrlLoadingStrategy.OpenInWebView;
}
public override WebResourceResponse? ShouldInterceptRequest(AWebView? view, IWebResourceRequest? request)
{
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}
var requestUri = request?.Url?.ToString();
var allowFallbackOnHostPage = AppOriginUri.IsBaseOfPage(requestUri);
requestUri = QueryStringHelper.RemovePossibleQueryString(requestUri);
if (requestUri != null &&
_webViewHandler != null &&
_webViewHandler.WebviewManager != null &&
_webViewHandler.WebviewManager.TryGetResponseContentInternal(requestUri, allowFallbackOnHostPage, out var statusCode, out var statusMessage, out var content, out var headers))
{
var contentType = headers["Content-Type"];
return new WebResourceResponse(contentType, "UTF-8", statusCode, statusMessage, headers, content);
}
return base.ShouldInterceptRequest(view, request);
}
public override void OnPageFinished(AWebView? view, string? url)
{
base.OnPageFinished(view, url);
if (view != null && url != null && AppOriginUri.IsBaseOfPage(url))
{
// Startup scripts must run in OnPageFinished. If scripts are run earlier they will have no lasting
// effect because once the page content loads all the document state gets reset.
RunBlazorStartupScripts(view);
}
}
private void RunBlazorStartupScripts(AWebView view)
{
// Confirm Blazor hasn't already initialized
view.EvaluateJavascript(@"
(function() { return typeof(window.__BlazorStarted); })();
", new JavaScriptValueCallback(blazorStarted =>
{
if (blazorStarted?.ToString() != "\"undefined\"")
{
// Blazor has already started, we can just abort startup process
return;
}
// Set up JS ports
view.EvaluateJavascript(@"
const channel = new MessageChannel();
var nativeJsPortOne = channel.port1;
var nativeJsPortTwo = channel.port2;
window.addEventListener('message', function (event) {
if (event.data != 'capturePort') {
nativeJsPortOne.postMessage(event.data)
}
else if (event.data == 'capturePort') {
if (event.ports[0] != null) {
nativeJsPortTwo = event.ports[0]
}
}
}, false);
nativeJsPortOne.addEventListener('message', function (event) {
}, false);
nativeJsPortTwo.addEventListener('message', function (event) {
// data from native code to JS
if (window.external.__callback) {
window.external.__callback(event.data);
}
}, false);
nativeJsPortOne.start();
nativeJsPortTwo.start();
window.external.sendMessage = function (message) {
// data from JS to native code
nativeJsPortTwo.postMessage(message);
};
window.external.receiveMessage = function (callback) {
window.external.__callback = callback;
}
", new JavaScriptValueCallback(_ =>
{
// Set up Server ports
_webViewHandler?.WebviewManager?.SetUpMessageChannel();
// Start Blazor
view.EvaluateJavascript(@"
Blazor.start();
window.__BlazorStarted = true;
", new JavaScriptValueCallback(_ =>
{
// Done; no more action required
}));
}));
}));
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
//_webViewManager = null;
}
}
private class JavaScriptValueCallback : Java.Lang.Object, IValueCallback
{
private readonly Action<Java.Lang.Object?> _callback;
public JavaScriptValueCallback(Action<Java.Lang.Object?> callback)
{
ArgumentNullException.ThrowIfNull(callback);
_callback = callback;
}
public void OnReceiveValue(Java.Lang.Object? value)
{
_callback(value);
}
}
}
}