Win32 サンプル WebView2Browser

このサンプル WebView2Browser は、 Microsoft Edge WebView2 コントロールで構築された Web ブラウザーです。

このサンプルには、独自の専用リポジトリがあります。

  • サンプル名: WebView2Browser
  • リポジトリ: WebView2Browser
  • ソリューション ファイル: WebViewBrowserApp.sln

WebView2Browser サンプル アプリ

WebView2Browser は、WebView2 コントロールの機能を示すサンプル Windows デスクトップ アプリケーションです。 WebView2Browser サンプル アプリでは、複数の WebView2 インスタンスを使用します。

このサンプルは、Win32 Visual Studio 2019 プロジェクトとしてビルドされます。 WebView2 環境では C++ と JavaScript を使用します。

WebView2Browser では、WebView の作成やナビゲーションなど、WebView2 の最も簡単な使用方法をいくつか示していますが、 PostWebMessageAsJson API を使用して別個の環境にある WebView2 コントロール間で通信するなどの複雑なワークフローも示しています。 これは、WebView2 API を使用して独自のアプリを構築する方法を示すリッチ コード サンプルです。

手順 1: Visual Studio をインストールする

  1. Visual Studio (C++ サポートを含む) をインストールします。

手順 2: WebView2Samples リポジトリを複製する

  • WebView2Samples リポジトリを複製 (または.zipとしてダウンロード) します。 「WebView2 用の開発環境をセットアップする」の「WebView2Samples リポジトリの複製」を参照してください。

手順 3: Visual Studio でソリューションを開く

  1. Visual Studio 2019 でソリューションを開きます。 WebView2 SDK は、NuGet パッケージとしてプロジェクトに既に含まれています。 Visual Studio 2017 を使用する場合は、[ プロジェクトのプロパティ] > [構成プロパティ] > [全般 > プラットフォーム ツールセット] でプロジェクトのプラットフォーム ツールセットを変更します。 場合によっては、Windows SDK を最新バージョンに変更する必要がある場合もあります。

  2. Windows Windows 10 より前のバージョンの Windows を使用している場合は、以下に変更を加えます。

Windows 10 より前のバージョンを使用する

Windows 10 より前のバージョンの Windows でブラウザーを構築して実行する場合は、次の変更を行います。 これは、Windows 10 と以前のバージョンの Windows では DPI が処理される方法のため必要です。

  1. Windows 10より前のバージョンの Windows でブラウザーをビルドして実行する場合は、次の手順を実行します。 WebViewBrowserApp.cpp で、SetProcessDpiAwarenessContextSetProcessDPIAware に変更します。
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                      _In_opt_ HINSTANCE hPrevInstance,
                      _In_ LPWSTR    lpCmdLine,
                      _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // Call SetProcessDPIAware() instead when using Windows 7 or any version
    // below 1703 (Windows 10).
    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

    BrowserWindow::RegisterClass(hInstance);

    // ...
  1. Windows 10より前のバージョンの Windows でブラウザーをビルドして実行する場合は、次の手順を実行します。 BrowserWindow.cpp で、次の GetDpiForWindow の呼び出しを削除するかコメント アウトします。
int BrowserWindow::GetDPIAwareBound(int bound)
{
    // Remove the GetDpiForWindow call when using Windows 7 or any version
    // below 1607 (Windows 10). You will also have to make sure the build
    // directory is clean before building again.
    return (bound * GetDpiForWindow(m_hWnd) / DEFAULT_DPI);
}

手順 4: アプリをビルドして実行する

  1. ビルドするターゲットを設定します (デバッグやリリースなど、x86 または x64 を対象とします)。

  2. ソリューションをビルドします。

  3. アプリの実行 (またはデバッグ) を行います。

  4. アプリを閉じます。

手順 5: WebView2 SDK を更新する

  • Visual Studio で WebView2 SDK のバージョンを更新します。 これを行うには、プロジェクトを右クリックし、[ NuGet パッケージの管理] をクリックします。

手順 6: 更新された WebView2 SDK を使用してアプリをビルドして実行する

  • アプリを再度ビルドして実行します。

ブラウザーのレイアウト

WebView2Browser サンプル アプリでは、複数の WebView2 インスタンスを使用します。

WebView2Browser には、Web コンテンツとアプリケーション UI を Windows デスクトップ アプリケーションに統合するためのマルチ WebView アプローチがあります。 これにより、ブラウザーは標準的な Web テクノロジ (HTML、CSS、JavaScript) を使用してインターフェイスを照らすことができます。また、アプリは Web からファビコンを取得し、IndexedDB を使用してお気に入りや履歴を保存することもできます。

マルチ WebView アプローチでは、2 つの個別の WebView 環境 (それぞれに独自のユーザー データ ディレクトリがあります) を使用します。1 つは UI WebView 用、もう 1 つはすべてのコンテンツ WebView 用です。 UI WebViews (コントロールとオプションのドロップダウン リスト) は UI 環境を使用し、Web コンテンツ WebViews (タブごとに 1 つ) はコンテンツ環境を使用します。

ブラウザーのレイアウト

機能

WebView2Browser サンプルには、基本的な Web ブラウザーを作成するためのすべての機能が用意されていますが、まだまだ試す余地は十分にあります。

WebView2Browser サンプルでは、次の機能を実装しています:

  • 戻る/進む
  • ページの再読み込み
  • ナビゲーションのキャンセル
  • 複数のタブ
  • 履歴
  • お気に入り
  • アドレス バーから検索する
  • ページのセキュリティ状態
  • キャッシュと Cookie のクリア

WebView2 API

WebView2Browser は、WebView2 で利用できるいくつかの API を使用します。 ここで使用されていない API については、 Microsoft Edge WebView2 リファレンスで詳細を確認できます。 WebView2Browser が使用する最も興味深い API と、それらが有効にする機能の一覧を次に示します。

API 機能
CreateCoreWebView2EnvironmentWithOptions UI とコンテンツ WebViews の環境を作成するために使用されます。 UI を Web コンテンツから分離するために、異なるユーザー データ ディレクトリが渡されます。
ICoreWebView2 WebView2Browser にはいくつかの WebView があり、ほとんどの機能はこのインターフェイスのメンバーを使用します。次の表は、それらがどのように使用されるかを示しています。
ICoreWebView2DevToolsProtocolEventReceivedEventHandler add_DevToolsProtocolEventReceived と一緒に使用して CDP セキュリティ イベントをリッスンし、ブラウザー UI のロック アイコンを更新します。
ICoreWebView2DevToolsProtocolEventReceiver add_DevToolsProtocolEventReceived と共に使用して CDP セキュリティイベントをリッスンし、ブラウザー UI のロックアイコンを更新します。
ICoreWebView2ExecuteScriptCompletedHandler 訪問したページからタイトルとファビコンを取得するために ExecuteScript と共に使用されます。
ICoreWebView2FocusChangedEventHandler フォーカスが失われたときにブラウザー オプションのドロップダウン リストを非表示にするために add_LostFocus と共に使用されます。
ICoreWebView2HistoryChangedEventHandler ブラウザー UI のナビゲーション ボタンを更新するために add_HistoryChanged と共に使用されます。
ICoreWebView2Controller WebView2Browser にはいくつかの WebViewController があり、そこから関連する WebView をフェッチします。
ICoreWebView2NavigationCompletedEventHandler ブラウザー UI の再読み込みボタンを更新するために add_NavigationCompleted と共に使用されます。
ICoreWebView2Settings ブラウザー UI で DevTools を無効にするために使用します。
ICoreWebView2SourceChangedEventHandler ブラウザー UI のアドレス バーを更新するために add_SourceChanged と共に使用されます。
ICoreWebView2WebMessageReceivedEventHandler これは、WebView2Browser にとって最も重要な API の 1 つです。 WebViews 間の通信に関わるほとんどの機能で、この値が使用されます。
ICoreWebView2 API 機能
add_NavigationStarting コントロール WebView にナビゲーションのキャンセル ボタンを表示するために使用します。
add_SourceChanged アドレス バーを更新するために使用します。
add_HistoryChanged 戻る/進むボタンを更新するために使います。
add_NavigationCompleted ナビゲーションの完了後に [再読み込み] ボタンを表示するために使用します。
ExecuteScript 訪問したページのタイトルとファビコンを取得するために使用されます。
PostWebMessageAsJson WebView の通信に使用されます。 すべてのメッセージでは、JSON を使用して必要なパラメーターを渡します。
add_WebMessageReceived WebView に投稿された Web メッセージを処理するために使用します。
CallDevToolsProtocolMethod セキュリティ イベントのリッスンを有効にするために使用します。これにより、ドキュメント内のセキュリティの状態の変更を通知します。
ICoreWebView2Controller API 機能
get_CoreWebView2 この CoreWebView2Controllerに関連付けられている CoreWebView2 を取得するために使用されます。
add_LostFocus ユーザーがクリックして離れたときに、オプション ドロップダウン リストを非表示にするために使用されます。

機能の実装

以下のセクションでは、WebView2Browser の一部の機能がどのように実装されたかについて説明します。 ここですべての仕組みの詳細については、ソース コードを参照してください。 概要:

基本事項

環境を設定し、WebView を作成する

WebView2 を使用すると、Windows アプリで Web コンテンツをホストできます。 これは、グローバル CreateCoreWebView2EnvironmentCreateCoreWebView2EnvironmentWithOptions を公開し、そこからブラウザーの UI とコンテンツに対して 2 つの個別の環境を作成できます。

    // Get directory for user data. This will be kept separated from the
    // directory for the browser UI data.
    std::wstring userDataDirectory = GetAppDataDirectory();
    userDataDirectory.append(L"\\User Data");

    // Create WebView environment for web content requested by the user. All
    // tabs will be created from this environment and kept isolated from the
    // browser UI. This environment is created first so the UI can request new
    // tabs when it's ready.
    HRESULT hr = CreateCoreWebView2EnvironmentWithOptions(nullptr, userDataDirectory.c_str(),
        L"", Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
            [this](HRESULT result, ICoreWebView2Environment* env) -> HRESULT
    {
        RETURN_IF_FAILED(result);

        m_contentEnv = env;
        HRESULT hr = InitUIWebViews();

        if (!SUCCEEDED(hr))
        {
            OutputDebugString(L"UI WebViews environment creation failed\n");
        }

        return hr;
    }).Get());
HRESULT BrowserWindow::InitUIWebViews()
{
    // Get data directory for browser UI data
    std::wstring browserDataDirectory = GetAppDataDirectory();
    browserDataDirectory.append(L"\\Browser Data");

    // Create WebView environment for browser UI. A separate data directory is
    // used to isolate the browser UI from web content requested by the user.
    return CreateCoreWebView2EnvironmentWithOptions(nullptr, browserDataDirectory.c_str(),
        L"", Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
            [this](HRESULT result, ICoreWebView2Environment* env) -> HRESULT
    {
        // Environment is ready, create the WebView
        m_uiEnv = env;

        RETURN_IF_FAILED(CreateBrowserControlsWebView());
        RETURN_IF_FAILED(CreateBrowserOptionsWebView());

        return S_OK;
    }).Get());
}

環境の準備が整ったら、 ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler を使用して UI WebViews を作成します。

HRESULT BrowserWindow::CreateBrowserControlsWebView()
{
    return m_uiEnv->CreateCoreWebView2Controller(m_hWnd, Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
        [this](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT
    {
        if (!SUCCEEDED(result))
        {
            OutputDebugString(L"Controls WebView creation failed\n");
            return result;
        }
        // WebView created
        m_controlsController = controller;
        CheckFailure(m_controlsController->get_CoreWebView2(&m_controlsWebView), L"");

        wil::com_ptr<ICoreWebView2Settings> settings;
        RETURN_IF_FAILED(m_controlsWebView->get_Settings(&settings));
        RETURN_IF_FAILED(settings->put_AreDevToolsEnabled(FALSE));

        RETURN_IF_FAILED(m_controlsController->add_ZoomFactorChanged(Callback<ICoreWebView2ZoomFactorChangedEventHandler>(
            [](ICoreWebView2Controller* controller, IUnknown* args) -> HRESULT
        {
            controller->put_ZoomFactor(1.0);
            return S_OK;
        }
        ).Get(), &m_controlsZoomToken));

        RETURN_IF_FAILED(m_controlsWebView->add_WebMessageReceived(m_uiMessageBroker.Get(), &m_controlsUIMessageBrokerToken));
        RETURN_IF_FAILED(ResizeUIWebViews());

        std::wstring controlsPath = GetFullPathFor(L"wvbrowser_ui\\controls_ui\\default.html");
        RETURN_IF_FAILED(m_controlsWebView->Navigate(controlsPath.c_str()));

        return S_OK;
    }).Get());
}

ここではいくつかの設定を行っています。 ICoreWebView2Settings インターフェイスは、ブラウザー コントロールを強化する WebView で DevTools を無効にするために使用されます。 また、受信した Web メッセージのハンドラーも追加しています。 このハンドラーを使用すると、ユーザーがこの WebView 内のコントロールを操作するときに、何らかの操作を実行できます。

アドレス バーに URI を入力すると、Web ページに移動できます。 Enter キーを押すと、コントロール WebView がホスト アプリに Web メッセージを投稿し、アクティブなタブを指定した場所に移動できるようにします。 次のコードは、ホスト Win32 アプリケーションがそのメッセージを処理する方法を示しています。

        case MG_NAVIGATE:
        {
            std::wstring uri(args.at(L"uri").as_string());
            std::wstring browserScheme(L"browser://");

            if (uri.substr(0, browserScheme.size()).compare(browserScheme) == 0)
            {
                // No encoded search URI
                std::wstring path = uri.substr(browserScheme.size());
                if (path.compare(L"favorites") == 0 ||
                    path.compare(L"settings") == 0 ||
                    path.compare(L"history") == 0)
                {
                    std::wstring filePath(L"wvbrowser_ui\\content_ui\\");
                    filePath.append(path);
                    filePath.append(L".html");
                    std::wstring fullPath = GetFullPathFor(filePath.c_str());
                    CheckFailure(m_tabs.at(m_activeTabId)->m_contentWebView->Navigate(fullPath.c_str()), L"Can't navigate to browser page.");
                }
                else
                {
                    OutputDebugString(L"Requested unknown browser page\n");
                }
            }
            else if (!SUCCEEDED(m_tabs.at(m_activeTabId)->m_contentWebView->Navigate(uri.c_str())))
            {
                CheckFailure(m_tabs.at(m_activeTabId)->m_contentWebView->Navigate(args.at(L"encodedSearchURI").as_string().c_str()), L"Can't navigate to requested page.");
            }
        }
        break;

WebView2Browser は、ブラウザー ページ (お気に入り、設定、履歴など) に対して URI をチェックし、要求された場所に移動するか、指定された URI を使用してフォールバックとして Bing を検索します。

アドレス バーを更新する

アドレス バーは、アクティブなタブのドキュメント ソースに変更があるたびに更新され、タブを切り替えると他のコントロールと共に更新されます。 各 WebView は、ドキュメントの状態が変更されたときにイベントを発生させます。このイベントを使用して、更新時に新しいソースを取得し、変更をコントロール WebView に転送できます ([戻る] ボタンと [進む] ボタンも更新します)。

        // Register event handler for doc state change
        RETURN_IF_FAILED(m_contentWebView->add_SourceChanged(Callback<ICoreWebView2SourceChangedEventHandler>(
            [this, browserWindow](ICoreWebView2* webview, ICoreWebView2SourceChangedEventArgs* args) -> HRESULT
        {
            BrowserWindow::CheckFailure(browserWindow->HandleTabURIUpdate(m_tabId, webview), L"Can't update Address bar");

            return S_OK;
        }).Get(), &m_uriUpdateForwarderToken));
HRESULT BrowserWindow::HandleTabURIUpdate(size_t tabId, ICoreWebView2* webview)
{
    wil::unique_cotaskmem_string source;
    RETURN_IF_FAILED(webview->get_Source(&source));

    web::json::value jsonObj = web::json::value::parse(L"{}");
    jsonObj[L"message"] = web::json::value(MG_UPDATE_URI);
    jsonObj[L"args"] = web::json::value::parse(L"{}");
    jsonObj[L"args"][L"tabId"] = web::json::value::number(tabId);
    jsonObj[L"args"][L"uri"] = web::json::value(source.get());

    // ...

    RETURN_IF_FAILED(PostJsonToWebView(jsonObj, m_controlsWebView.Get()));

    return S_OK;
}

HRESULT BrowserWindow::HandleTabHistoryUpdate(size_t tabId, ICoreWebView2* webview)
{
    // ...

    BOOL canGoForward = FALSE;
    RETURN_IF_FAILED(webview->get_CanGoForward(&canGoForward));
    jsonObj[L"args"][L"canGoForward"] = web::json::value::boolean(canGoForward);

    BOOL canGoBack = FALSE;
    RETURN_IF_FAILED(webview->get_CanGoBack(&canGoBack));
    jsonObj[L"args"][L"canGoBack"] = web::json::value::boolean(canGoBack);

    RETURN_IF_FAILED(PostJsonToWebView(jsonObj, m_controlsWebView.Get()));

    return S_OK;
}

MG_UPDATE_URI メッセージを URI と共にコントロール WebView に送信しました。 次に、これらの変更をタブの状態に反映し、必要に応じて UI を更新します。

        case commands.MG_UPDATE_URI:
            if (isValidTabId(args.tabId)) {
                const tab = tabs.get(args.tabId);
                let previousURI = tab.uri;

                // Update the tab state
                tab.uri = args.uri;
                tab.uriToShow = args.uriToShow;
                tab.canGoBack = args.canGoBack;
                tab.canGoForward = args.canGoForward;

                // If the tab is active, update the controls UI
                if (args.tabId == activeTabId) {
                    updateNavigationUI(message);
                }

                // ...
            }
            break;

前に戻る、次に進む

各 WebView は実行したナビゲーションの履歴を保持するため、ブラウザー UI に対応するメソッドに接続するだけで済みます。 アクティブなタブの WebView を前後に移動できる場合、ボタンをクリックすると、ホスト アプリケーションに Web メッセージが投稿されます。

JavaScript 側:

    document.querySelector('#btn-forward').addEventListener('click', function(e) {
        if (document.getElementById('btn-forward').className === 'btn') {
            var message = {
                message: commands.MG_GO_FORWARD,
                args: {}
            };
            window.chrome.webview.postMessage(message);
        }
    });

    document.querySelector('#btn-back').addEventListener('click', function(e) {
        if (document.getElementById('btn-back').className === 'btn') {
            var message = {
                message: commands.MG_GO_BACK,
                args: {}
            };
            window.chrome.webview.postMessage(message);
        }
    });

ホスト アプリケーション側:

        case MG_GO_FORWARD:
        {
            CheckFailure(m_tabs.at(m_activeTabId)->m_contentWebView->GoForward(), L"");
        }
        break;
        case MG_GO_BACK:
        {
            CheckFailure(m_tabs.at(m_activeTabId)->m_contentWebView->GoBack(), L"");
        }
        break;

リロード、ナビゲーションの停止

コンテンツ WebView によって発生する NavigationStarting イベントを使用して、コントロール WebView で関連するタブの読み込み状態を更新します。 同様に、WebView が NavigationCompleted イベントを起動すると、そのイベントを使用して、タブの状態を更新するようにコントロール WebView に指示します。 コントロール WebView のアクティブなタブの状態によって、再読み込みボタンを表示するかキャンセル ボタンを表示するかが決まります。 それぞれがクリックされると、ホスト アプリケーションにメッセージが投稿され、そのタブの WebView を再読み込みしたり、それに応じてナビゲーションをキャンセルしたりできます。

function reloadActiveTabContent() {
    var message = {
        message: commands.MG_RELOAD,
        args: {}
    };
    window.chrome.webview.postMessage(message);
}

 // ...

    document.querySelector('#btn-reload').addEventListener('click', function(e) {
        var btnReload = document.getElementById('btn-reload');
        if (btnReload.className === 'btn-cancel') {
            var message = {
                message: commands.MG_CANCEL,
                args: {}
            };
            window.chrome.webview.postMessage(message);
        } else if (btnReload.className === 'btn') {
            reloadActiveTabContent();
        }
    });
        case MG_RELOAD:
        {
            CheckFailure(m_tabs.at(m_activeTabId)->m_contentWebView->Reload(), L"");
        }
        break;
        case MG_CANCEL:
        {
            CheckFailure(m_tabs.at(m_activeTabId)->m_contentWebView->CallDevToolsProtocolMethod(L"Page.stopLoading", L"{}", nullptr), L"");
        }

いくつかの興味深い機能

WebViews の通信

タブと UI を制御する WebView を通信して、あるタブの WebView でのユーザー操作がもう一方の WebView で望ましい効果を得られるようにする必要があります。 WebView2Browser は、 PostWebMessageAsJsonadd_WebMessageReceivedICoreWebView2WebMessageReceivedEventHandler など、この目的に非常に便利な WebView2 API のセットを使用します。

JavaScript 側では、公開されている window.chrome.webview オブジェクトを使用して postMessage メソッドを呼び出し、受信メッセージのイベント リスターを追加しています。

HRESULT BrowserWindow::CreateBrowserControlsWebView()
{
    return m_uiEnv->CreateCoreWebView2Controller(m_hWnd, Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
        [this](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT
    {
        // ...

        RETURN_IF_FAILED(m_controlsWebView->add_WebMessageReceived(m_uiMessageBroker.Get(), &m_controlsUIMessageBrokerToken));

        // ...

        return S_OK;
    }).Get());
}
HRESULT BrowserWindow::PostJsonToWebView(web::json::value jsonObj, ICoreWebView2* webview)
{
    utility::stringstream_t stream;
    jsonObj.serialize(stream);

    return webview->PostWebMessageAsJson(stream.str().c_str());
}

// ...

HRESULT BrowserWindow::HandleTabNavStarting(size_t tabId, ICoreWebView2* webview)
{
    web::json::value jsonObj = web::json::value::parse(L"{}");
    jsonObj[L"message"] = web::json::value(MG_NAV_STARTING);
    jsonObj[L"args"] = web::json::value::parse(L"{}");
    jsonObj[L"args"][L"tabId"] = web::json::value::number(tabId);

    return PostJsonToWebView(jsonObj, m_controlsWebView.Get());
}
function init() {
    window.chrome.webview.addEventListener('message', messageHandler);
    refreshControls();
    refreshTabs();

    createNewTab(true);
}

// ...

function reloadActiveTabContent() {
    var message = {
        message: commands.MG_RELOAD,
        args: {}
    };
    window.chrome.webview.postMessage(message);
}

タブの処理

開いているタブの右側にある 新しいタブ ボタンをユーザーがクリックするたびに、新しいタブが作成されます。 コントロールの WebView は、ホスト アプリケーションにメッセージを投稿して、そのタブの WebView を作成し、その状態を追跡するオブジェクトを作成します。

function createNewTab(shouldBeActive) {
    const tabId = getNewTabId();

    var message = {
        message: commands.MG_CREATE_TAB,
        args: {
            tabId: parseInt(tabId),
            active: shouldBeActive || false
        }
    };

    window.chrome.webview.postMessage(message);

    tabs.set(parseInt(tabId), {
        title: 'New Tab',
        uri: '',
        uriToShow: '',
        favicon: 'img/favicon.png',
        isFavorite: false,
        isLoading: false,
        canGoBack: false,
        canGoForward: false,
        securityState: 'unknown',
        historyItemId: INVALID_HISTORY_ID
    });

    loadTabUI(tabId);

    if (shouldBeActive) {
        switchToTab(tabId, false);
    }
}

ホスト アプリ側では、登録された ICoreWebView2WebMessageReceivedEventHandler がメッセージをキャッチし、そのタブの WebView を作成します。

        case MG_CREATE_TAB:
        {
            size_t id = args.at(L"tabId").as_number().to_uint32();
            bool shouldBeActive = args.at(L"active").as_bool();
            std::unique_ptr<Tab> newTab = Tab::CreateNewTab(m_hWnd, m_contentEnv.Get(), id, shouldBeActive);

            std::map<size_t, std::unique_ptr<Tab>>::iterator it = m_tabs.find(id);
            if (it == m_tabs.end())
            {
                m_tabs.insert(std::pair<size_t,std::unique_ptr<Tab>>(id, std::move(newTab)));
            }
            else
            {
                m_tabs.at(id)->m_contentWebView->Close();
                it->second = std::move(newTab);
            }
        }
        break;
std::unique_ptr<Tab> Tab::CreateNewTab(HWND hWnd, ICoreWebView2Environment* env, size_t id, bool shouldBeActive)
{
    std::unique_ptr<Tab> tab = std::make_unique<Tab>();

    tab->m_parentHWnd = hWnd;
    tab->m_tabId = id;
    tab->SetMessageBroker();
    tab->Init(env, shouldBeActive);

    return tab;
}

HRESULT Tab::Init(ICoreWebView2Environment* env, bool shouldBeActive)
{
    return env->CreateCoreWebView2Controller(m_parentHWnd, Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
        [this, shouldBeActive](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT {
        if (!SUCCEEDED(result))
        {
            OutputDebugString(L"Tab WebView creation failed\n");
            return result;
        }
        m_contentController = controller;
        BrowserWindow::CheckFailure(m_contentController->get_CoreWebView2(&m_contentWebView), L"");
        BrowserWindow* browserWindow = reinterpret_cast<BrowserWindow*>(GetWindowLongPtr(m_parentHWnd, GWLP_USERDATA));
        RETURN_IF_FAILED(m_contentWebView->add_WebMessageReceived(m_messageBroker.Get(), &m_messageBrokerToken));

        // Register event handler for history change
        RETURN_IF_FAILED(m_contentWebView->add_HistoryChanged(Callback<ICoreWebView2HistoryChangedEventHandler>(
            [this, browserWindow](ICoreWebView2* webview, IUnknown* args) -> HRESULT
        {
            BrowserWindow::CheckFailure(browserWindow->HandleTabHistoryUpdate(m_tabId, webview), L"Can't update go back/forward buttons.");

            return S_OK;
        }).Get(), &m_historyUpdateForwarderToken));

        // Register event handler for source change
        RETURN_IF_FAILED(m_contentWebView->add_SourceChanged(Callback<ICoreWebView2SourceChangedEventHandler>(
            [this, browserWindow](ICoreWebView2* webview, ICoreWebView2SourceChangedEventArgs* args) -> HRESULT
        {
            BrowserWindow::CheckFailure(browserWindow->HandleTabURIUpdate(m_tabId, webview), L"Can't update Address bar");

            return S_OK;
        }).Get(), &m_uriUpdateForwarderToken));

        RETURN_IF_FAILED(m_contentWebView->add_NavigationStarting(Callback<ICoreWebView2NavigationStartingEventHandler>(
            [this, browserWindow](ICoreWebView2* webview, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT
        {
            BrowserWindow::CheckFailure(browserWindow->HandleTabNavStarting(m_tabId, webview), L"Can't update reload button");

            return S_OK;
        }).Get(), &m_navStartingToken));

        RETURN_IF_FAILED(m_contentWebView->add_NavigationCompleted(Callback<ICoreWebView2NavigationCompletedEventHandler>(
            [this, browserWindow](ICoreWebView2* webview, ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT
        {
            BrowserWindow::CheckFailure(browserWindow->HandleTabNavCompleted(m_tabId, webview, args), L"Can't update reload button");
            return S_OK;
        }).Get(), &m_navCompletedToken));

        // Handle security state updates

        RETURN_IF_FAILED(m_contentWebView->Navigate(L"https://www.bing.com"));
        browserWindow->HandleTabCreated(m_tabId, shouldBeActive);

        return S_OK;
    }).Get());
}

このタブには、イベントが発生したときにコントロール WebView に更新情報を転送できるように、すべてのハンドラーが登録されます。 タブの準備が整い、ブラウザーのコンテンツ領域に表示されます。 コントロール WebView のタブをクリックすると、ホスト アプリケーションにメッセージが投稿され、以前にアクティブだったタブの WebView が非表示になり、クリックしたタブの WebView が表示されます。

HRESULT BrowserWindow::SwitchToTab(size_t tabId)
{
    size_t previousActiveTab = m_activeTabId;

    RETURN_IF_FAILED(m_tabs.at(tabId)->ResizeWebView());
    RETURN_IF_FAILED(m_tabs.at(tabId)->m_contentWebView->put_IsVisible(TRUE));
    m_activeTabId = tabId;

    if (previousActiveTab != INVALID_TAB_ID && previousActiveTab != m_activeTabId)
    {
        RETURN_IF_FAILED(m_tabs.at(previousActiveTab)->m_contentWebView->put_IsVisible(FALSE));
    }

    return S_OK;
}

セキュリティ アイコンの更新

CallDevToolsProtocolMethod を使用して、セキュリティ イベントのリッスンを有効にします。 securityStateChanged イベントが発生するたびに、新しい状態を使用してコントロール WebView のセキュリティ アイコンが更新されます。

        // Enable listening for security events to update secure icon
        RETURN_IF_FAILED(m_contentWebView->CallDevToolsProtocolMethod(L"Security.enable", L"{}", nullptr));

        BrowserWindow::CheckFailure(m_contentWebView->GetDevToolsProtocolEventReceiver(L"Security.securityStateChanged", &m_securityStateChangedReceiver), L"");

        // Forward security status updates to browser
        RETURN_IF_FAILED(m_securityStateChangedReceiver->add_DevToolsProtocolEventReceived(Callback<ICoreWebView2DevToolsProtocolEventReceivedEventHandler>(
            [this, browserWindow](ICoreWebView2* webview, ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -> HRESULT
        {
            BrowserWindow::CheckFailure(browserWindow->HandleTabSecurityUpdate(m_tabId, webview, args), L"Can't update security icon");
            return S_OK;
        }).Get(), &m_securityUpdateToken));
HRESULT BrowserWindow::HandleTabSecurityUpdate(size_t tabId, ICoreWebView2* webview, ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args)
{
    wil::unique_cotaskmem_string jsonArgs;
    RETURN_IF_FAILED(args->get_ParameterObjectAsJson(&jsonArgs));
    web::json::value securityEvent = web::json::value::parse(jsonArgs.get());

    web::json::value jsonObj = web::json::value::parse(L"{}");
    jsonObj[L"message"] = web::json::value(MG_SECURITY_UPDATE);
    jsonObj[L"args"] = web::json::value::parse(L"{}");
    jsonObj[L"args"][L"tabId"] = web::json::value::number(tabId);
    jsonObj[L"args"][L"state"] = securityEvent.at(L"securityState");

    return PostJsonToWebView(jsonObj, m_controlsWebView.Get());
}
        case commands.MG_SECURITY_UPDATE:
            if (isValidTabId(args.tabId)) {
                const tab = tabs.get(args.tabId);
                tab.securityState = args.state;

                if (args.tabId == activeTabId) {
                    updateNavigationUI(message);
                }
            }
            break;

履歴の事前設定

WebView2Browser は、コントロール WebView で IndexedDB を使用して履歴アイテムを保存します。これは、WebView2 を使用してブラウザーでの場合と同様に標準的な Web テクノロジにアクセスできるようにする方法の一例です。 ナビゲーション用のアイテムは、URI が更新されるとすぐに作成されます。 これらの項目は、 window.chrome.postMessageを利用してタブの履歴 UI によって取得されます。

この場合、ほとんどの機能は両端 (UI の読み込みを制御する WebView とコンテンツ WebView を制御) で JavaScript を使用して実装されるため、ホスト アプリケーションはこれらの端を通信するためのメッセージ ブローカーとしてのみ機能します。

        case commands.MG_UPDATE_URI:
            if (isValidTabId(args.tabId)) {
                // ...

                // Don't add history entry if URI has not changed
                if (tab.uri == previousURI) {
                    break;
                }

                // Filter URIs that should not appear in history
                if (!tab.uri || tab.uri == 'about:blank') {
                    tab.historyItemId = INVALID_HISTORY_ID;
                    break;
                }

                if (tab.uriToShow && tab.uriToShow.substring(0, 10) == 'browser://') {
                    tab.historyItemId = INVALID_HISTORY_ID;
                    break;
                }

                addHistoryItem(historyItemFromTab(args.tabId), (id) => {
                    tab.historyItemId = id;
                });
            }
            break;
function addHistoryItem(item, callback) {
    queryDB((db) => {
        let transaction = db.transaction(['history'], 'readwrite');
        let historyStore = transaction.objectStore('history');

        // Check if an item for this URI exists on this day
        let currentDate = new Date();
        let year = currentDate.getFullYear();
        let month = currentDate.getMonth();
        let date = currentDate.getDate();
        let todayDate = new Date(year, month, date);

        let existingItemsIndex = historyStore.index('stampedURI');
        let lowerBound = [item.uri, todayDate];
        let upperBound = [item.uri, currentDate];
        let range = IDBKeyRange.bound(lowerBound, upperBound);
        let request = existingItemsIndex.openCursor(range);

        request.onsuccess = function(event) {
            let cursor = event.target.result;
            if (cursor) {
                // There's an entry for this URI, update the item
                cursor.value.timestamp = item.timestamp;
                let updateRequest = cursor.update(cursor.value);

                updateRequest.onsuccess = function(event) {
                    if (callback) {
                        callback(event.target.result.primaryKey);
                    }
                };
            } else {
                // No entry for this URI, add item
                let addItemRequest = historyStore.add(item);

                addItemRequest.onsuccess = function(event) {
                    if (callback) {
                        callback(event.target.result);
                    }
                };
            }
        };

    });
}

JSON と URI の処理

WebView2Browser は、Microsoft の cpprestsdk (Casablanca) を使用して、C++ 側のすべての JSON を処理します。 IUri と CreateUri は、ファイル パスを URI に解析するためにも使用され、他の URI にも使用できます。

関連項目