Win32でWebView2を使用する

## 概要

Windowsのハイブリッドアプリ開発やアプリ内にWebコンテンツを埋め込むためのWebView2の使用方法をまとめました。

## WebView2とは

WebView2は、WindowsアプリにWebコンテンツを表示するためのコンポーネントです。

同じくWebコンテンツを表示するWebViewの後継としてリリースされました。  
WebViewではEdgeHTMLベースのEdgeをレンダリングに使用しているのに対し、WebView2ではChromiumベースのEdgeを使用している違いがあります。  
これによってWindowsアプリにChromiumが提供する最新のWeb技術が利用できるようになりました。

## 開発環境

WebView2の開発にはVisual Studio 2015以降が必要です。
また必要に応じて以下のソフトをインストールする必要があります。
- Microsoft Edge Insider (プレビュー) チャネル
- WebView2 ランタイム

また、以下のプログラミング環境がサポートされてます。
- Win32 C/C++
- NET Framework 4.5 以降
- NET Core 3.1 以降
- NET 5
- NET 6
- WinUI 2.0
- WinUI 3.0

今回はWin32 C/C++で開発します。

## サンプルのビルド

以下のリポジトリに各種プログラミング環境用のサンプルプロジェクトが用意されているので  
Win32環境用の `WebView2Samples/GettingStartedGuides/Win32_GettingStarted` フォルダをダウンロードします。

[WebView2 Samples](https://github.com/MicrosoftEdge/WebView2Samples)

フォルダ内のソリューションを開くと、Visual Studioの[ソリューション アクションの確認]ダイアログが表示され、プロジェクトを再ターゲットするかどうかを確認するメッセージが表示される場合があります。  
そのダイアログが表示された場合は、[OK]をクリックします。  
以上でソリューションのセットアップが完了しましたので、ビルドするとサンプルプロジェクトのウィンドウが表示されます。

## WebView2の操作
### WebView2の設定
WebView2にスクリプトの有効化などの設定を追加してカスタマイズできます。

``` C++
wil::com_ptr<ICoreWebView2Settings> settings;
webview->get_Settings(&settings);
settings->put_IsScriptEnabled(TRUE);
settings->put_AreDefaultScriptDialogsEnabled(TRUE);
settings->put_IsWebMessageEnabled(TRUE);
```

### ナビゲーション
表示するコンテンツのURLへ遷移します。(Bingのサイトを表示する場合)
``` C++
webview->Navigate(L"https://www.bing.com/");
```

また、遷移時にリクエストをインターセプトしてURLの制限やキャッシュの利用などもできます。  
サンプルプロジェクトでは遷移時にURLスキームをチェックすることでhttpsのサイトのみ許可しています。

``` C++
EventRegistrationToken token;
webview->add_NavigationStarting(Callback<ICoreWebView2NavigationStartingEventHandler>(
    [](ICoreWebView2* webview, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT {
        wil::unique_cotaskmem_string uri;
        args->get_Uri(&uri);
        std::wstring source(uri.get());
        if (source.substr(0, 5) != L"https") {
            args->put_Cancel(true);
        }
        return S_OK;
    }).Get(), &token);
```

また、文字列からもコンテンツを表示できます。  
``` C++
LPCWCHAR content = 
LR"(
    <html>
        <head>
            <meta charset="UTF-8">
            <title>Hello World</title>
        </head>
        <body>
            <h1>Hello World from string</h1>
        </body>
    </html>
)";
webview->NavigateToString(content);
```

その場合data URLも許可する必要があります。
``` C++
EventRegistrationToken token;
webview->add_NavigationStarting(Callback<ICoreWebView2NavigationStartingEventHandler>(
    [](ICoreWebView2* webview, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT {
        wil::unique_cotaskmem_string uri;
        args->get_Uri(&uri);
        std::wstring source(uri.get());
        auto scheme = source.substr(0, 5);
        if (!(scheme == L"https" || scheme == L"data:")) {
            args->put_Cancel(true);
        }
        return S_OK;
    }).Get(), &token);
```

### WebView2内のコンテンツからデータを受信する
以下のようにWebView2内のコンテンツからのメッセージを受け取ったときのコールバックを設定することができます。

``` C++
webview->add_WebMessageReceived(Callback<ICoreWebView2WebMessageReceivedEventHandler>(
[](ICoreWebView2* webview, ICoreWebView2WebMessageReceivedEventArgs* args) -> HRESULT {
    wil::unique_cotaskmem_string message;
    args->TryGetWebMessageAsString(&message);
    OutputDebugString(message.get());
    return S_OK;
}).Get(), &token);
```

JavaScriptからは以下のようにメッセージを送信できます。

``` JavaScript
window.chrome.webview.postMessage("string message");
```

JSONを受信する場合は `get_WebMessageAsJson` を使います。

``` C++
webview->add_WebMessageReceived(Callback<ICoreWebView2WebMessageReceivedEventHandler>(
[](ICoreWebView2* webview, ICoreWebView2WebMessageReceivedEventArgs* args) -> HRESULT {
    LPWSTR json;
    args->get_WebMessageAsJson(&json);
    OutputDebugString(json);
    return S_OK;
}).Get(), &token);
```
``` JavaScript
window.chrome.webview.postMessage({'foo': 'bar'});
```

### ネイティブからWebView2にデータを送信する

ネイティブ側から文字列を送信する場合は以下のようになります。
``` C++
webview->PostWebMessageAsString(L"test message");
```
JSONの場合は `PostWebMessageAsJson` を使用してJSONフォーマットの文字列を指定します。
``` C++
webview->PostWebMessageAsJson(LR"({'foo': 'bar'})");
```

JavaScript側では、webviewにmessageイベントを登録することでデータを受信できます。
``` JavaScript
window.chrome.webview.addEventListener('message', mes => {
    console.log(mes);
});
```

以上簡単なWebView2の使用方法でした。