2017/5/19

UWP - 加入 Windows Runtime Component 到 JavaScript context

在 WebView 裏面讓 Javascript 與 App 互動已經是很平常的事情了,爲什麽要特別寫這篇。
因爲發現除了 window.external.notify 允許從網頁送入訊息到 App 之外,還可以加入自定義的 component 到網頁裏讓 Javascript 使用。
這篇將介紹怎麽實作。

根據 WebView class 的介紹,在 Windows 10 開始允許利用 AddWebAllowedObject method 去引用建立好的 Windows Runtime component 到 WebView 的 Javascript context 之中,讓 Javascript 有能力存取 native 的 properties, methods, events。

那我們來看要做些什麽才能完成這樣的功能。

1. 建立 Universal Windows Platform 與 Windows Runtime component ,再將 Component 的專案加入到 App 的參考之中;


2. 為 component 專案中的 class 加入 AllowForWeb 的 attribute;
Allow​For​Web​Attribute Class 允許讓開發者公開 native 物件變成類似 global parameter 的内容放在 WebView 的 top-level。
由於宣告 AllowForWeb 的類別是受到保護的,所以需要是 sealed 的 class
如下的程式碼:

[AllowForWeb]
public sealed class JavaScriptExternalObject
{
    /// <summary>
    /// 提供給外部使用時收聽的事件
    /// </summary>
    public event EventHandler FromJavaScriptMessage;
        
    /// <summary>
    /// 提供給 JavaScript 呼叫的方法
    /// </summary>
    public void onOpenNativeShareDialog(string json)
    {
            FromJavaScriptMessage?.Invoke(null, json);
    }
}

3. 在 WebView 的 NavigationStarting 事件發生時為每個新進入的網頁加入 JavaScriptExternalObject;
利用 AddWebAllowedObject 將 native Windows Runtime Component 宣告的類別加入到 Webview 之中,如下:

public sealed partial class MainPage : Page
{
    private JavaScriptExternalObject javascriptExternalObject;

    public MainPage()
    {
        javascriptExternalObject = new JavaScriptExternalObject();
        javascriptExternalObject.FromJavaScriptMessage += JavascriptExternalObject_FromJavaScriptMessage;
        this.InitializeComponent();
    }

    private void WebView_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
    {
        // 定義公開的名稱為: external
        // JavaScriptExternalObject: 為 Javascript 裏面可以利用 external.{JavaScriptExternalObject 裏面的内容}
        sender.AddWebAllowedObject("external", javascriptExternalObject);
    }

    private async void JavascriptExternalObject_FromJavaScriptMessage(object sender, string e)
    {
        // 接受來自 JavasScript 的訊息
        await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
        {
            var dialog = new MessageDialog(e);
            await dialog.ShowAsync();
        });
    }
}
[注意]
如果註冊 external 的話,WebView 會自動覆寫原本 external 的 methods,所以 window.external.notify 也就無法使用了 (ScriptyNotify event 不會被觸發)。需要自行在加入 notify method 到自定義的 JavaScriptExternalObject class 裏面才能使用哦

4. WebView 載入 html 内容,並且測試内容;
html 内文如下:

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>JavaScriptInvokeNativeSample</title>
    <script type='text/javascript'>
        function onInvokeNativeMethod() {
            // invoke native method
            external.onOpenNativeShareDialog("from javascript"); 
        }
    </script>
</head>
<body>
    <button type="button" onclick="onInvokeNativeMethod();">Invoke native method</button>
</body>
</html>

XAML 内文如下:

<WebView HorizontalAlignment="Stretch" VerticalAlignment="Stretch" NavigationStarting="WebView_NavigationStarting" 
         Source="ms-appx-web:///html/TestPage.html" />

執行結果:


詳細的範例可參考 How to interoperate JS with native in WebView of Universal Windows Platform(UWP)

[補充]
  1. 如果是自己開發或是受信任的網站,可以在 Package.appxmanifest 加入 ApplicationContentUriRules 的宣告,讓 Javasscript 可與 Windows RuntimeAPI 互動;
  2. 在 Win10 支援 WebView.Settings 可以設定 JavaScript 與 IndexedDB 是否啓用;
  3. WebView 支援 CaptureSelectedContentToDataPackageAsync 把 WebView 内容分享給其他 app,變成 DataPackage。因爲是非同步的 method 要記得處理 deferral 去保護 DataRequested 事件能執行完畢;
  4. 可利用 CapturePreviewToStreamAsync 將現在 WebView 的内容變成一張圖片;
  5. 預設 WebView 在 Desktop device family 是運作在 UI Thread,而其他 device family 則不是;可以設定 WebView.DefaultExecutionMode 讓預設的 UI thread 負責控制 WebView;

======
簡單筆記一下有這樣的用法,希望對大家在撰寫與網頁互動時也可以加入這樣的彈性。

References:

沒有留言:

張貼留言