Skip to content

Commit

Permalink
Add BlazorWebView skeleton code (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
myroot authored and rookiejava committed Mar 27, 2022
1 parent 90b37bc commit 15fd3ef
Show file tree
Hide file tree
Showing 4 changed files with 281 additions and 0 deletions.
144 changes: 144 additions & 0 deletions src/BlazorWebView/src/Maui/Tizen/BlazorWebViewHandler.Tizen.cs
@@ -0,0 +1,144 @@
using System;
using System.IO;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Maui.Handlers;
using TChromium = Tizen.WebView.Chromium;
using TWebView = Tizen.WebView.WebView;

namespace Microsoft.AspNetCore.Components.WebView.Maui
{
public partial class BlazorWebViewHandler : ViewHandler<IBlazorWebView, WebViewContainer>
{
private const string AppOrigin = "app://0.0.0.0/";
private const string BlazorInitScript = @"
window.__receiveMessageCallbacks = [];
window.__dispatchMessageCallback = function(message) {
window.__receiveMessageCallbacks.forEach(function(callback) { callback(message); });
};
window.external = {
sendMessage: function(message) {
window.webkit.messageHandlers.webwindowinterop.postMessage(message);
},
receiveMessage: function(callback) {
window.__receiveMessageCallbacks.push(callback);
}
};
Blazor.start();
(function () {
window.onpageshow = function(event) {
if (event.persisted) {
window.location.reload();
}
};
})();
";

private TizenWebViewManager? _webviewManager;
private WebViewExtension.InterceptRequestCallback? _interceptRequestCallback;

private TWebView NativeWebView => NativeView.WebView;

private bool RequiredStartupPropertiesSet =>
//_webview != null &&
HostPage != null &&
Services != null;

protected override WebViewContainer CreateNativeView()
{
TChromium.Initialize();
Context!.CurrentApplication!.Terminated += (s, e) => TChromium.Shutdown();
return new WebViewContainer(Context!.NativeParent);
}

protected override void ConnectHandler(WebViewContainer nativeView)
{
_interceptRequestCallback = OnRequestInterceptCallback;
NativeWebView.LoadStarted += OnLoadStarted;
NativeWebView.LoadFinished += OnLoadFinished;
//NativeWebView.AddJavaScriptMessageHandler("BlazorHandler", PostMessageFromJS);
NativeWebView.SetInterceptRequestCallback(_interceptRequestCallback);
NativeWebView.GetSettings().JavaScriptEnabled = true;

}

protected override void DisconnectHandler(WebViewContainer nativeView)
{
base.DisconnectHandler(nativeView);
}

private void StartWebViewCoreIfPossible()
{
if (!RequiredStartupPropertiesSet ||
_webviewManager != null)
{
return;
}
if (NativeView == null)
{
throw new InvalidOperationException($"Can't start {nameof(BlazorWebView)} without native web view instance.");
}

var assetConfig = Services!.GetRequiredService<BlazorAssetsAssemblyConfiguration>()!;

// We assume the host page is always in the root of the content directory, because it's
// unclear there's any other use case. We can add more options later if so.
var contentRootDir = Path.GetDirectoryName(HostPage!) ?? string.Empty;
var hostPageRelativePath = Path.GetRelativePath(contentRootDir, HostPage!);

var fileProvider = new ManifestEmbeddedFileProvider(assetConfig.AssetsAssembly, root: contentRootDir);

_webviewManager = new TizenWebViewManager(this, NativeWebView, Services!, MauiDispatcher.Instance, fileProvider, hostPageRelativePath);
if (RootComponents != null)
{
foreach (var rootComponent in RootComponents)
{
// Since the page isn't loaded yet, this will always complete synchronously
_ = rootComponent.AddToWebViewManagerAsync(_webviewManager);
}
}
_webviewManager.Navigate("/");
}

private void OnRequestInterceptCallback(IntPtr context, IntPtr request, IntPtr userdata)
{
if (request == IntPtr.Zero)
{
throw new ArgumentNullException(nameof(request));
}

var url = NativeWebView.GetInterceptRequestUrl(request);
var urlScheme = url.Substring(0, url.IndexOf(':'));

if (urlScheme == "app")
{
var allowFallbackOnHostPage = url.EndsWith("/");
if (_webviewManager!.TryGetResponseContentInternal(url, allowFallbackOnHostPage, out var statusCode, out var statusMessage, out var content, out var headers))
{
var contentType = headers["Content-Type"];
var header = $"HTTP/1.0 200 OK\r\nContent-Type:{contentType}; charset=utf-8\r\nCache-Control:no-cache, max-age=0, must-revalidate, no-store\r\n\r\n";
var body = new StreamReader(content).ReadToEnd();
NativeWebView.SetInterceptRequestResponse(request, header, body, (uint)body.Length);
return;
}
}

NativeWebView.IgnoreInterceptRequest(request);
}

private void OnLoadStarted(object? sender, EventArgs e)
{
}

private void OnLoadFinished(object? sender, EventArgs e)
{
NativeWebView.SetFocus(true);

var url = NativeWebView.Url;
if (url == AppOrigin)
NativeWebView.Eval(BlazorInitScript);
}
}
}
37 changes: 37 additions & 0 deletions src/BlazorWebView/src/Maui/Tizen/TizenWebViewManager.cs
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.FileProviders;
using TWebView = Tizen.WebView.WebView;

namespace Microsoft.AspNetCore.Components.WebView.Maui
{
public class TizenWebViewManager : WebViewManager
{
private const string AppOrigin = "app://0.0.0.0/";

private readonly BlazorWebViewHandler _blazorMauiWebViewHandler;
private readonly TWebView _webview;

public TizenWebViewManager(BlazorWebViewHandler blazorMauiWebViewHandler, TWebView webview, IServiceProvider services, Dispatcher dispatcher, IFileProvider fileProvider, string hostPageRelativePath)
: base(services, dispatcher, new Uri(AppOrigin), fileProvider, hostPageRelativePath)
{
_blazorMauiWebViewHandler = blazorMauiWebViewHandler ?? throw new ArgumentNullException(nameof(blazorMauiWebViewHandler));
_webview = webview ?? throw new ArgumentNullException(nameof(webview));

}

internal bool TryGetResponseContentInternal(string uri, bool allowFallbackOnHostPage, out int statusCode, out string statusMessage, out Stream content, out IDictionary<string, string> headers) =>
TryGetResponseContent(uri, allowFallbackOnHostPage, out statusCode, out statusMessage, out content, out headers);

/// <inheritdoc />
protected override void NavigateCore(Uri absoluteUri)
{
}

/// <inheritdoc />
protected override void SendMessage(string message)
{
}
}
}
31 changes: 31 additions & 0 deletions src/BlazorWebView/src/Maui/Tizen/WebViewContainer.cs
@@ -0,0 +1,31 @@
using System;
using Tizen.UIExtensions.ElmSharp;
using ElmSharp;
using TWebView = Tizen.WebView.WebView;

namespace Microsoft.AspNetCore.Components.WebView.Maui
{
public class WebViewContainer : WidgetLayout
{
public TWebView WebView { get; }

public WebViewContainer(EvasObject parent) : base(parent)
{
WebView = new TWebView(parent);
SetContent(WebView);
AllowFocus(true);
Focused += OnFocused;
Unfocused += OnUnfocused;
}

void OnFocused(object? sender, EventArgs e)
{
WebView.SetFocus(true);
}

void OnUnfocused(object? sender, EventArgs e)
{
WebView.SetFocus(false);
}
}
}
69 changes: 69 additions & 0 deletions src/BlazorWebView/src/Maui/Tizen/WebViewExtension.cs
@@ -0,0 +1,69 @@
using System;
using System.Runtime.InteropServices;
using TWebView = Tizen.WebView.WebView;

namespace Microsoft.AspNetCore.Components.WebView.Maui
{
public static class WebViewExtension
{
public const string ChromiumEwk = "libchromium-ewk.so";

public static void SetInterceptRequestCallback(this TWebView webView, InterceptRequestCallback callback)
{
var context = webView.GetContext();
var handleField = context.GetType().GetField("_handle", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var contextHandle = (IntPtr?)handleField?.GetValue(context);
if (contextHandle != null)
ewk_context_intercept_request_callback_set(contextHandle.Value, callback, IntPtr.Zero);
}

#pragma warning disable IDE0060 // Remove unused parameter
public static bool SetInterceptRequestResponse(this TWebView webView, IntPtr request, string header, string body, uint length)
#pragma warning restore IDE0060 // Remove unused parameter
{
return ewk_intercept_request_response_set(request, header, body, length);
}

#pragma warning disable IDE0060 // Remove unused parameter
public static bool IgnoreInterceptRequest(this TWebView webView, IntPtr request)
#pragma warning restore IDE0060 // Remove unused parameter
{
return ewk_intercept_request_ignore(request);
}

#pragma warning disable IDE0060 // Remove unused parameter
public static string GetInterceptRequestUrl(this TWebView webView, IntPtr request)
#pragma warning restore IDE0060 // Remove unused parameter
{
return Marshal.PtrToStringAnsi(_ewk_intercept_request_url_get(request)) ?? string.Empty;
}

[DllImport(ChromiumEwk)]
internal static extern IntPtr ewk_view_context_get(IntPtr obj);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void InterceptRequestCallback(IntPtr context, IntPtr request, IntPtr userData);

[DllImport(ChromiumEwk)]
internal static extern void ewk_context_intercept_request_callback_set(IntPtr context, InterceptRequestCallback callback, IntPtr userData);

[DllImport(ChromiumEwk, EntryPoint = "ewk_intercept_request_url_get")]
internal static extern IntPtr _ewk_intercept_request_url_get(IntPtr request);

[DllImport(ChromiumEwk, EntryPoint = "ewk_intercept_request_http_method_get")]
internal static extern IntPtr _ewk_intercept_request_http_method_get(IntPtr request);

internal static string ewk_intercept_request_http_method_get(IntPtr request)
{
return Marshal.PtrToStringAnsi(_ewk_intercept_request_http_method_get(request)) ?? string.Empty;
}

[DllImport(ChromiumEwk)]
internal static extern bool ewk_intercept_request_ignore(IntPtr request);

#pragma warning disable CA2101 // Specify marshaling for P/Invoke string arguments
[DllImport(ChromiumEwk)]
#pragma warning restore CA2101 // Specify marshaling for P/Invoke string arguments
internal static extern bool ewk_intercept_request_response_set(IntPtr request, string header, string body, uint length);
}
}

0 comments on commit 15fd3ef

Please sign in to comment.