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. C++ サポートを含む Visual Studio をインストールします。

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

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

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

  2. 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 コンテンツ WebView (タブごとに 1 つ) はコンテンツ環境を使用します。

ブラウザー レイアウト

機能

WebView2Browser サンプルは、基本的な Web ブラウザーを作成するためのすべての機能を提供しますが、遊ぶには十分な余地があります。

WebView2Browser サンプルでは、次の機能が実装されています。

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

WebView2 API

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

API 機能
CreateCoreWebView2EnvironmentWithOptions UI とコンテンツ WebView の環境を作成するために使用されます。 UI を Web コンテンツから分離するために、さまざまなユーザー データ ディレクトリが渡されます。
ICoreWebView2 WebView2Browser にはいくつかの WebView があり、ほとんどの機能がこのインターフェイスでメンバーを使用しています。次の表は、それらの使用方法を示しています。
ICoreWebView2DevToolsProtocolEventReceivedEventHandler add_DevToolsProtocolEventReceivedと共に使用して CDP セキュリティ イベントをリッスンし、ブラウザー UI のロック アイコンを更新します。
ICoreWebView2DevToolsProtocolEventReceiver add_DevToolsProtocolEventReceivedと共に使用して CDP セキュリティ イベントをリッスンし、ブラウザー UI のロック アイコンを更新します。
ICoreWebView2ExecuteScriptCompletedHandler ExecuteScriptと共に使用して、アクセスしたページからタイトルとファビコンを取得します。
ICoreWebView2FocusChangedEventHandler add_LostFocusと共に使用して、フォーカスが失われるときにブラウザー オプションのドロップダウン リストを非表示にします。
ICoreWebView2HistoryChangedEventHandler add_HistoryChangedと共に使用して、ブラウザー UI のナビゲーション ボタンを更新します。
ICoreWebView2Controller WebView2Browser にはいくつかの WebViewController があり、関連する WebView をそれらの WebView からフェッチします。
ICoreWebView2NavigationCompletedEventHandler add_NavigationCompletedと共に使用して、ブラウザー UI の [再読み込み] ボタンを更新します。
ICoreWebView2Settings ブラウザー UI で DevTools を無効にするために使用されます。
ICoreWebView2SourceChangedEventHandler add_SourceChangedと共に使用して、ブラウザー UI のアドレス バーを更新します。
ICoreWebView2WebMessageReceivedEventHandler これは、WebView2Browser にとって最も重要な API の 1 つです。 WebView 間の通信に関連するほとんどの機能では、これを使用します。
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 WebView を作成します。

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 のコントロールと対話するときに何かを行うことができます。

Web ページに移動するには、アドレス バーにその URI を入力します。 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"");
        }

いくつかの興味深い機能

WebView の通信

あるタブの WebView でのユーザー操作が他の WebView で望ましい効果を得ることができるように、タブと UI に力を与える 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 が非表示になり、クリックされたタブのタブが表示されます。

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 によって取得されます。

この場合、ほとんどの機能は両端で JavaScript を使用して実装されます (WebView とコンテンツ WebView が UI を読み込むのを制御します)。

        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 にも使用できます。

関連項目