From bb15e5954eb8dd4bd44102a2f911f94b324f9e14 Mon Sep 17 00:00:00 2001 From: LazuliKao Date: Tue, 30 Jan 2024 14:38:27 +0800 Subject: [PATCH 1/2] fix: fix AndroidWebViewCore event --- .../Clients/AvaloniaWebViewClient.cs | 160 +++--------- .../AvaloniaBlazorWebChromeClient.cs} | 42 ++-- .../Blazor/AvaloniaBlazorWebViewClient.cs | 235 ++++++++++++++++++ .../Core/AndroidWebViewCore-core.cs | 14 +- .../Core/AndroidWebViewCore-override.cs | 4 +- 5 files changed, 312 insertions(+), 143 deletions(-) rename Source/Platform/Android/Avalonia.WebView.Android/Clients/{AvaloniaWebChromeClient.cs => Blazor/AvaloniaBlazorWebChromeClient.cs} (69%) create mode 100644 Source/Platform/Android/Avalonia.WebView.Android/Clients/Blazor/AvaloniaBlazorWebViewClient.cs diff --git a/Source/Platform/Android/Avalonia.WebView.Android/Clients/AvaloniaWebViewClient.cs b/Source/Platform/Android/Avalonia.WebView.Android/Clients/AvaloniaWebViewClient.cs index b00f1a5b..9d8f0048 100644 --- a/Source/Platform/Android/Avalonia.WebView.Android/Clients/AvaloniaWebViewClient.cs +++ b/Source/Platform/Android/Avalonia.WebView.Android/Clients/AvaloniaWebViewClient.cs @@ -1,25 +1,25 @@ -using WebViewCore.Enums; +using Android.Graphics; +using WebViewCore.Enums; namespace Avalonia.WebView.Android.Clients; [SupportedOSPlatform("android23.0")] internal class AvaloniaWebViewClient : WebViewClient { - public AvaloniaWebViewClient(AndroidWebViewCore webViewHandler, IVirtualWebViewControlCallBack callBack, IVirtualBlazorWebViewProvider provider, WebScheme webScheme) + public AvaloniaWebViewClient( + AndroidWebViewCore webViewHandler, + IVirtualWebViewControlCallBack callBack + ) { ArgumentNullException.ThrowIfNull(webViewHandler); ArgumentNullException.ThrowIfNull(callBack); - ArgumentNullException.ThrowIfNull(provider); - ArgumentNullException.ThrowIfNull(webScheme); _callBack = callBack; _webViewCore = webViewHandler; - _provider = provider; _webView = webViewHandler.WebView; - _webScheme = webScheme; } - - protected AvaloniaWebViewClient(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) + protected AvaloniaWebViewClient(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. @@ -29,74 +29,44 @@ protected AvaloniaWebViewClient(IntPtr javaReference, JniHandleOwnership transfe readonly AndroidWebViewCore? _webViewCore; readonly AndroidWebView? _webView; readonly IVirtualWebViewControlCallBack? _callBack; - readonly IVirtualBlazorWebViewProvider? _provider; - readonly WebScheme? _webScheme; - - bool _isStarted = false; - public override bool ShouldOverrideUrlLoading(AndroidWebView? view, IWebResourceRequest? request) + public override bool ShouldOverrideUrlLoading( + AndroidWebView? view, + IWebResourceRequest? request + ) #pragma warning disable CA1416 - => ShouldOverrideUrlLoadingCore(request) || base.ShouldOverrideUrlLoading(view, request); + => + ShouldOverrideUrlLoadingCore(view, request) || base.ShouldOverrideUrlLoading(view, request); #pragma warning restore CA1416 - public override AndroidWebResourceResponse? ShouldInterceptRequest(AndroidWebView? view, IWebResourceRequest? request) - { - ArgumentException.ThrowIfNullOrEmpty(nameof(request)); - Func func = () => - { - if (_webScheme is null || _provider is null) - return default; - - var requestUri = request?.Url?.ToString(); - if (requestUri == null) - return default; - - var allowFallbackOnHostPage = _webScheme.BaseUri.IsBaseOfPage(requestUri); - - var webRequest = new WebResourceRequest - { - RequestUri = requestUri!, - AllowFallbackOnHostPage = allowFallbackOnHostPage - }; - - if (!_provider.PlatformWebViewResourceRequested(_webViewCore, webRequest, out var webResponse)) - return default; - - if (webResponse is null) - return default; - - var contentType = webResponse.Headers[QueryStringHelper.ContentTypeKey]; - - return new AndroidWebResourceResponse(contentType, "UTF-8", webResponse.StatusCode, webResponse.StatusMessage, webResponse.Headers, webResponse.Content); - }; - var ret = func.Invoke(); - - if (ret is null) - return base.ShouldInterceptRequest(view, request); - else - return ret; - } - public override void OnPageFinished(AndroidWebView? view, string? url) { base.OnPageFinished(view, url); if (view is null) return; - - if (string.IsNullOrWhiteSpace(url)) - return; - - if (_webScheme is null) + System.Diagnostics.Debug.WriteLine($"OnPageFinished: {url}"); + if (_callBack is null || !Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri)) return; - - if (_webScheme.BaseUri.IsBaseOfPage(url)) - RunBlazorStarupScripts(); + _callBack.PlatformWebViewNavigationCompleted( + _webViewCore, + new WebViewUrlLoadedEventArg { IsSuccess = true } + ); } - bool ShouldOverrideUrlLoadingCore(IWebResourceRequest? request) + bool ShouldOverrideUrlLoadingCore(AndroidWebView? view, IWebResourceRequest? request) { - if (_callBack is null || !Uri.TryCreate(request?.Url?.ToString(), UriKind.RelativeOrAbsolute, out var uri)) + if (view is null) + { + return false; + } + System.Diagnostics.Debug.WriteLine( + $"ShouldOverrideUrlLoadingCore: {request?.Url?.ToString()}" + ); + if ( + _callBack is null + || !Uri.TryCreate(request?.Url?.ToString(), UriKind.RelativeOrAbsolute, out var uri) + ) return false; WebViewUrlLoadingEventArg args = new() { Url = uri, RawArgs = request }; _callBack.PlatformWebViewNavigationStarting(_webViewCore, args); @@ -125,71 +95,15 @@ bool ShouldOverrideUrlLoadingCore(IWebResourceRequest? request) _webView?.LoadUrl(uri.OriginalString); isSucceed = true; break; - case UrlRequestStrategy.CancelLoad: + case UrlRequestStrategy.CancelLoad: break; default: break; } - - _callBack.PlatformWebViewNavigationCompleted(_webViewCore, new WebViewUrlLoadedEventArg() {IsSuccess = isSucceed }); - + _callBack.PlatformWebViewNavigationCompleted( + _webViewCore, + new WebViewUrlLoadedEventArg { IsSuccess = isSucceed } + ); return true; } - - void RunBlazorStarupScripts() - { - if (_webView is null) - return; - - _webView.EvaluateJavascript(BlazorScriptHelper.BlazorStartedScript, new JavaScriptValueCallback(blazorStarted => - { - var result = blazorStarted?.ToString(); - - if (result != BlazorScriptHelper.UndefinedString) - return; - - _webView.EvaluateJavascript(BlazorScriptHelper.BlazorMessageScript, new JavaScriptValueCallback(_ => - { - _isStarted = true; - BlazorMessageChannel(_webView, _provider!); - - _webView.EvaluateJavascript(BlazorScriptHelper.BlazorStartingScript, new JavaScriptValueCallback(_ => - { - - })); - - })); - })); - } - - void BlazorMessageChannel(AndroidWebView webView, IVirtualBlazorWebViewProvider provider) - { - if (webView is null) - return; - - if (provider is null) - return; - - if (_webScheme is null) - return; - - var nativeToJSPorts = webView.CreateWebMessageChannel(); - var nativeToJs = new BlazorWebMessageCallback(message => - { - if (string.IsNullOrWhiteSpace(message)) - return; - - provider.PlatformWebViewMessageReceived(_webViewCore, new WebViewMessageReceivedEventArgs() - { - Source = _webScheme.BaseUri, - Message = message - }); - }); - - var destPort = new[] { nativeToJSPorts[1] }; - nativeToJSPorts[0].SetWebMessageCallback(nativeToJs); - - webView.PostWebMessage(new WebMessage("capturePort", destPort), AndroidUri.Parse(_webScheme.BaseUri.AbsoluteUri)!); - } - } diff --git a/Source/Platform/Android/Avalonia.WebView.Android/Clients/AvaloniaWebChromeClient.cs b/Source/Platform/Android/Avalonia.WebView.Android/Clients/Blazor/AvaloniaBlazorWebChromeClient.cs similarity index 69% rename from Source/Platform/Android/Avalonia.WebView.Android/Clients/AvaloniaWebChromeClient.cs rename to Source/Platform/Android/Avalonia.WebView.Android/Clients/Blazor/AvaloniaBlazorWebChromeClient.cs index 72dc2482..e10ed48f 100644 --- a/Source/Platform/Android/Avalonia.WebView.Android/Clients/AvaloniaWebChromeClient.cs +++ b/Source/Platform/Android/Avalonia.WebView.Android/Clients/Blazor/AvaloniaBlazorWebChromeClient.cs @@ -1,8 +1,8 @@ -namespace Avalonia.WebView.Android.Clients; +namespace Avalonia.WebView.Android.Clients.Blazor; -internal class AvaloniaWebChromeClient : WebChromeClient +internal class AvaloniaBlazorWebChromeClient : WebChromeClient { - public AvaloniaWebChromeClient(AndroidWebViewCore androidWebViewCore) + public AvaloniaBlazorWebChromeClient(AndroidWebViewCore androidWebViewCore) { _androidWebViewCore = androidWebViewCore; var topLevel = androidWebViewCore.GetTopLevel(); @@ -15,7 +15,12 @@ public AvaloniaWebChromeClient(AndroidWebViewCore androidWebViewCore) readonly AndroidWebViewCore _androidWebViewCore; readonly TopLevel _topLevel; - public override bool OnCreateWindow(AndroidWebView? view, bool isDialog, bool isUserGesture, Message? resultMsg) + public override bool OnCreateWindow( + AndroidWebView? view, + bool isDialog, + bool isUserGesture, + Message? resultMsg + ) { if (view?.Context is not null) { @@ -28,7 +33,11 @@ public override bool OnCreateWindow(AndroidWebView? view, bool isDialog, bool is return false; } - public override bool OnShowFileChooser(AndroidWebView? webView, IValueCallback? filePathCallback, FileChooserParams? fileChooserParams) + public override bool OnShowFileChooser( + AndroidWebView? webView, + IValueCallback? filePathCallback, + FileChooserParams? fileChooserParams + ) { if (filePathCallback is null) return base.OnShowFileChooser(webView, filePathCallback, fileChooserParams); @@ -37,7 +46,10 @@ public override bool OnShowFileChooser(AndroidWebView? webView, IValueCallback? return true; } - private async Task CallFilePickerAsync(IValueCallback filePathCallback, FileChooserParams? fileChooserParams) + private async Task CallFilePickerAsync( + IValueCallback filePathCallback, + FileChooserParams? fileChooserParams + ) { var pickOptions = GetPickOptions(fileChooserParams); if (pickOptions is null) @@ -76,7 +88,10 @@ private async Task CallFilePickerAsync(IValueCallback filePathCallback, FileChoo return default; var acceptedFileTypes = fileChooserParams.GetAcceptTypes(); - if (acceptedFileTypes is null || (acceptedFileTypes.Length == 1 && string.IsNullOrEmpty(acceptedFileTypes[0]))) + if ( + acceptedFileTypes is null + || (acceptedFileTypes.Length == 1 && string.IsNullOrEmpty(acceptedFileTypes[0])) + ) return null; bool allowMultiple = fileChooserParams.Mode == ChromeFileChooserMode.OpenMultiple; @@ -86,15 +101,14 @@ private async Task CallFilePickerAsync(IValueCallback filePathCallback, FileChoo AllowMultiple = allowMultiple, FileTypeFilter = new List() { - new FilePickerFileType("Accepted File") - { - Patterns = acceptedFileTypes, - AppleUniformTypeIdentifiers = new string[1] { "public.accepted"}, - MimeTypes = new string[1] { "accepted/*" } - } + new FilePickerFileType("Accepted File") + { + Patterns = acceptedFileTypes, + AppleUniformTypeIdentifiers = new string[1] { "public.accepted" }, + MimeTypes = new string[1] { "accepted/*" } + } } }; return pickOptions; } } - diff --git a/Source/Platform/Android/Avalonia.WebView.Android/Clients/Blazor/AvaloniaBlazorWebViewClient.cs b/Source/Platform/Android/Avalonia.WebView.Android/Clients/Blazor/AvaloniaBlazorWebViewClient.cs new file mode 100644 index 00000000..4b24e95c --- /dev/null +++ b/Source/Platform/Android/Avalonia.WebView.Android/Clients/Blazor/AvaloniaBlazorWebViewClient.cs @@ -0,0 +1,235 @@ +using WebViewCore.Enums; + +namespace Avalonia.WebView.Android.Clients.Blazor; + +[SupportedOSPlatform("android23.0")] +internal class AvaloniaBlazorWebViewClient : WebViewClient +{ + public AvaloniaBlazorWebViewClient( + AndroidWebViewCore webViewHandler, + IVirtualWebViewControlCallBack callBack, + IVirtualBlazorWebViewProvider provider, + WebScheme webScheme + ) + { + ArgumentNullException.ThrowIfNull(webViewHandler); + ArgumentNullException.ThrowIfNull(callBack); + ArgumentNullException.ThrowIfNull(provider); + ArgumentNullException.ThrowIfNull(webScheme); + _callBack = callBack; + _webViewCore = webViewHandler; + _provider = provider; + _webView = webViewHandler.WebView; + _webScheme = webScheme; + } + + protected AvaloniaBlazorWebViewClient(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. + } + + readonly AndroidWebViewCore? _webViewCore; + readonly AndroidWebView? _webView; + readonly IVirtualWebViewControlCallBack? _callBack; + readonly IVirtualBlazorWebViewProvider? _provider; + readonly WebScheme? _webScheme; + + bool _isStarted = false; + + public override bool ShouldOverrideUrlLoading( + AndroidWebView? view, + IWebResourceRequest? request + ) +#pragma warning disable CA1416 + => ShouldOverrideUrlLoadingCore(request) || base.ShouldOverrideUrlLoading(view, request); +#pragma warning restore CA1416 + + public override AndroidWebResourceResponse? ShouldInterceptRequest( + AndroidWebView? view, + IWebResourceRequest? request + ) + { + ArgumentException.ThrowIfNullOrEmpty(nameof(request)); + Func func = () => + { + if (_webScheme is null || _provider is null) + return default; + + var requestUri = request?.Url?.ToString(); + if (requestUri == null) + return default; + + var allowFallbackOnHostPage = _webScheme.BaseUri.IsBaseOfPage(requestUri); + + var webRequest = new WebResourceRequest + { + RequestUri = requestUri!, + AllowFallbackOnHostPage = allowFallbackOnHostPage + }; + + if ( + !_provider.PlatformWebViewResourceRequested( + _webViewCore, + webRequest, + out var webResponse + ) + ) + return default; + + if (webResponse is null) + return default; + + var contentType = webResponse.Headers[QueryStringHelper.ContentTypeKey]; + + return new AndroidWebResourceResponse( + contentType, + "UTF-8", + webResponse.StatusCode, + webResponse.StatusMessage, + webResponse.Headers, + webResponse.Content + ); + }; + var ret = func.Invoke(); + + if (ret is null) + return base.ShouldInterceptRequest(view, request); + else + return ret; + } + + public override void OnPageFinished(AndroidWebView? view, string? url) + { + base.OnPageFinished(view, url); + + if (view is null) + return; + + if (string.IsNullOrWhiteSpace(url)) + return; + + if (_webScheme is null) + return; + + if (_webScheme.BaseUri.IsBaseOfPage(url)) + RunBlazorStarupScripts(); + } + + bool ShouldOverrideUrlLoadingCore(IWebResourceRequest? request) + { + if ( + _callBack is null + || !Uri.TryCreate(request?.Url?.ToString(), UriKind.RelativeOrAbsolute, out var uri) + ) + return false; + WebViewUrlLoadingEventArg args = new() { Url = uri, RawArgs = request }; + _callBack.PlatformWebViewNavigationStarting(_webViewCore, args); + if (args.Cancel) + return false; + + var newWindowEventArgs = new WebViewNewWindowEventArgs() + { + Url = uri, + UrlLoadingStrategy = UrlRequestStrategy.OpenInWebView + }; + + if (!_callBack.PlatformWebViewNewWindowRequest(_webViewCore, newWindowEventArgs)) + return false; + + bool isSucceed = false; + switch (newWindowEventArgs.UrlLoadingStrategy) + { + case UrlRequestStrategy.OpenExternally: + case UrlRequestStrategy.OpenInNewWindow: + var intent = Intent.ParseUri(uri.OriginalString, IntentUriType.Scheme); + AndroidApplication.Context.StartActivity(intent); + isSucceed = true; + break; + case UrlRequestStrategy.OpenInWebView: + _webView?.LoadUrl(uri.OriginalString); + isSucceed = true; + break; + case UrlRequestStrategy.CancelLoad: + break; + default: + break; + } + + _callBack.PlatformWebViewNavigationCompleted( + _webViewCore, + new WebViewUrlLoadedEventArg() { IsSuccess = isSucceed } + ); + + return true; + } + + void RunBlazorStarupScripts() + { + if (_webView is null) + return; + + _webView.EvaluateJavascript( + BlazorScriptHelper.BlazorStartedScript, + new JavaScriptValueCallback(blazorStarted => + { + var result = blazorStarted?.ToString(); + + if (result != BlazorScriptHelper.UndefinedString) + return; + + _webView.EvaluateJavascript( + BlazorScriptHelper.BlazorMessageScript, + new JavaScriptValueCallback(_ => + { + _isStarted = true; + BlazorMessageChannel(_webView, _provider!); + + _webView.EvaluateJavascript( + BlazorScriptHelper.BlazorStartingScript, + new JavaScriptValueCallback(_ => { }) + ); + }) + ); + }) + ); + } + + void BlazorMessageChannel(AndroidWebView webView, IVirtualBlazorWebViewProvider provider) + { + if (webView is null) + return; + + if (provider is null) + return; + + if (_webScheme is null) + return; + + var nativeToJSPorts = webView.CreateWebMessageChannel(); + var nativeToJs = new BlazorWebMessageCallback(message => + { + if (string.IsNullOrWhiteSpace(message)) + return; + + provider.PlatformWebViewMessageReceived( + _webViewCore, + new WebViewMessageReceivedEventArgs() + { + Source = _webScheme.BaseUri, + Message = message + } + ); + }); + + var destPort = new[] { nativeToJSPorts[1] }; + nativeToJSPorts[0].SetWebMessageCallback(nativeToJs); + + webView.PostWebMessage( + new WebMessage("capturePort", destPort), + AndroidUri.Parse(_webScheme.BaseUri.AbsoluteUri)! + ); + } +} diff --git a/Source/Platform/Android/Avalonia.WebView.Android/Core/AndroidWebViewCore-core.cs b/Source/Platform/Android/Avalonia.WebView.Android/Core/AndroidWebViewCore-core.cs index f9cc75e1..903e6710 100644 --- a/Source/Platform/Android/Avalonia.WebView.Android/Core/AndroidWebViewCore-core.cs +++ b/Source/Platform/Android/Avalonia.WebView.Android/Core/AndroidWebViewCore-core.cs @@ -1,8 +1,13 @@ -namespace Avalonia.WebView.Android.Core; +using Avalonia.WebView.Android.Clients.Blazor; + +namespace Avalonia.WebView.Android.Core; partial class AndroidWebViewCore { - Task PrepareBlazorWebViewStarting(AndroidWebView webView, IVirtualBlazorWebViewProvider? provider) + Task PrepareBlazorWebViewStarting( + AndroidWebView webView, + IVirtualBlazorWebViewProvider? provider + ) { if (webView is null) return Task.FromResult(false); @@ -13,8 +18,8 @@ Task PrepareBlazorWebViewStarting(AndroidWebView webView, IVirtualBlazorWe if (!provider.ResourceRequestedFilterProvider(this, out var filter)) return Task.FromResult(false); - _webViewClient = new AvaloniaWebViewClient(this, _callBack, provider, filter); - _webChromeClient = new AvaloniaWebChromeClient(this); + _webViewClient = new AvaloniaBlazorWebViewClient(this, _callBack, provider, filter); + _webChromeClient = new AvaloniaBlazorWebChromeClient(this); webView.SetWebViewClient(_webViewClient); webView.SetWebChromeClient(_webChromeClient); _isBlazorWebView = true; @@ -26,4 +31,3 @@ void ClearBlazorWebViewCompleted() _isBlazorWebView = false; } } - diff --git a/Source/Platform/Android/Avalonia.WebView.Android/Core/AndroidWebViewCore-override.cs b/Source/Platform/Android/Avalonia.WebView.Android/Core/AndroidWebViewCore-override.cs index 6ae4d8dd..8a8fc5a2 100644 --- a/Source/Platform/Android/Avalonia.WebView.Android/Core/AndroidWebViewCore-override.cs +++ b/Source/Platform/Android/Avalonia.WebView.Android/Core/AndroidWebViewCore-override.cs @@ -66,7 +66,9 @@ async Task IPlatformWebView.Initialize() var bRet = await PrepareBlazorWebViewStarting(webView, _provider); if (!bRet) { - _webViewClient = new WebViewClient(); +#pragma warning disable CA1416 // Validate platform compatibility + _webViewClient = new AvaloniaWebViewClient(this, _callBack); +#pragma warning restore CA1416 // Validate platform compatibility _webChromeClient = new WebChromeClient(); webView.SetWebViewClient(_webViewClient); webView.SetWebChromeClient(_webChromeClient); From e2a6d3af767938d6b2ea390f5186e00f57b99938 Mon Sep 17 00:00:00 2001 From: LazuliKao Date: Tue, 30 Jan 2024 14:38:47 +0800 Subject: [PATCH 2/2] feat: impl ExecuteScriptAsync for android --- .../Core/AndroidWebViewCore-override.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Platform/Android/Avalonia.WebView.Android/Core/AndroidWebViewCore-override.cs b/Source/Platform/Android/Avalonia.WebView.Android/Core/AndroidWebViewCore-override.cs index 8a8fc5a2..9b214a8b 100644 --- a/Source/Platform/Android/Avalonia.WebView.Android/Core/AndroidWebViewCore-override.cs +++ b/Source/Platform/Android/Avalonia.WebView.Android/Core/AndroidWebViewCore-override.cs @@ -16,12 +16,12 @@ partial class AndroidWebViewCore Task IWebViewControl.ExecuteScriptAsync(string javaScript) { - _webView.EvaluateJavascript(javaScript, new JavaScriptValueCallback(result => - { - - - })); - throw new NotImplementedException(); + var cts = new TaskCompletionSource(); + _webView.EvaluateJavascript( + javaScript, + new JavaScriptValueCallback(result => cts.TrySetResult(result?.ToString())) + ); + return cts.Task; } bool IWebViewControl.GoBack()