Win32 範例 WebView2Browser

此範例 WebView2Browser 是使用 Microsoft Edge WebView2 控制項建置的網頁瀏覽器。

此範例有自己的專用存放庫。

  • 範例名稱: WebView2Browser
  • 存放庫: WebView2Browser
  • 解決方案檔案: WebViewBrowserApp.sln

WebView2Browser 範例應用程式

WebView2Browser 是示範 WebView2 控制項功能的範例 Windows 桌面應用程式。 WebView2Browser 範例應用程式使用多個 WebView2 執行個體。

此範例建置為 Win32 Visual Studio 2019 專案。 它在 WebView2 環境中使用 C++ 和 JavaScript。

WebView2Browser 顯示 WebView2 的一些最簡單的用法,例如建立和瀏覽 WebView,但也顯示一些更複雜的工作流程,例如使用 PostWebMessageAsJson API 在不同環境中跨 WebView2 控制項進行通訊。 這是一個豐富的程式碼範例,示範如何使用 WebView2 API 來建立您自己的應用程式。

步驟 1:安裝 Visual Studio

  1. 安裝 Visual Studio,包括 C++ 支援。

步驟 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 時,Windows 10 會處理 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 方法牽涉到使用兩個不同的 WebView 環境, (每個環境都有自己的使用者資料目錄) :一個用於 UI WebViews,另一個用於所有內容 WebView。 UI WebViews (控制項和選項下拉式清單) 使用 UI 環境,而 Web 內容 WebViews 每個索引標籤 (一個) 使用內容環境。

瀏覽器配置

功能

WebView2Browser 範例提供了製作基本網頁瀏覽器的所有功能,但還有足夠的空間供您試用。

WebView2Browser 範例實作下列功能:

  • 返回/前進
  • 重新載入頁面
  • 取消瀏覽
  • 多個索引標籤
  • 歷程記錄
  • 我的最愛
  • 從網址列搜尋
  • 頁面安全性狀態
  • 清除快取和 Cookie

WebView2 API

WebView2Browser 會使用 WebView2 中一些可用的 API。 如需未在此處使用的 API,您可以在 Microsoft Edge WebView2 參考中找到詳細資訊。 以下是 WebView2Browser 使用的最有趣 API 及其啟用的功能清單。

API 功能
CreateCoreWebView2EnvironmentWithOptions 用來建立 UI 和內容 Web 檢視的環境。 傳遞不同的使用者資料目錄,以將 UI 與 Web 內容隔離。
ICoreWebView2 WebView2Browser 中有數個 Web 視圖,而且大部分功能都會使用此介面中的成員,下表顯示其使用方式。
ICoreWebView2DevToolsProtocolEventReceivedEventHandler 與add_DevToolsProtocolEventReceived一起使用,以接聽 CDP 安全性事件,以更新瀏覽器 UI 中的鎖定圖示。
ICoreWebView2DevToolsProtocolEventReceiver 搭配 add_DevToolsProtocolEventReceived 用來接聽 CDP 安全性事件,以更新瀏覽器 UI 中的鎖定圖示。
ICoreWebView2ExecuteScriptCompletedHandler 一起使用 ExecuteScript 以從訪問的頁面獲取標題和網站圖標。
ICoreWebView2FocusChangedEventHandler 搭配用 add_LostFocus 來在瀏覽器選項下拉式清單失去焦點時隱藏它。
ICoreWebView2HistoryChangedEventHandler 搭配 add_HistoryChanged 用來更新瀏覽器 UI 中的導覽按鈕。
ICoreWebView2Controller WebView2Browser 中有數個 WebViewControllers,我們會從中擷取相關聯的 WebView。
ICoreWebView2NavigationCompletedEventHandler 搭配 add_NavigationCompleted 用來更新瀏覽器 UI 中的重新載入按鈕。
ICoreWebView2Settings 用來停用瀏覽器 UI 中的 DevTools。
ICoreWebView2SourceChangedEventHandler 搭配用 add_SourceChanged 來更新瀏覽器 UI 中的網址列。
ICoreWebView2WebMessageReceivedEventHandler 這是 WebView2Browser 最重要的 API 之一。 大部分涉及跨 WebView 通訊的功能都會使用此選項。
ICoreWebView2 API 功能
add_NavigationStarting 用來在控制項 WebView 中顯示 [取消導覽] 按鈕。
add_SourceChanged 用來更新網址列。
add_HistoryChanged 用來更新 [返回/前進] 按鈕。
add_NavigationCompleted 用來在瀏覽完成後顯示 [重新載入] 按鈕。
ExecuteScript 用來取得已造訪頁面的標題和網站圖示。
PostWebMessageAsJson 用來通訊 Web 檢視。 所有訊息都使用 JSON 來傳遞所需的參數。
add_WebMessageReceived 用來處理張貼至 WebView 的 Web 訊息。
CallDevToolsProtocolMethod 用來啟用監聽安全性事件,以通知文件中的安全性狀態變更。
ICoreWebView2Controller API 功能
get_CoreWebView2 用來取得與此相關 CoreWebView2Controller聯的 CoreWebView2 。
add_LostFocus 用來在使用者按一下離開時隱藏選項下拉式清單。

實作功能

以下各節描述了 WebView2Browser 中的一些功能是如何實作的。 您可以在此處查看源代碼以獲取有關一切工作原理的更多詳細信息。 大綱:

基本知識

設定環境,建立 Web 檢視

WebView2 可讓您在 Windows 應用程式中託管網頁內容。 它會公開全域 CreateCoreWebView2EnvironmentCreateCoreWebView2EnvironmentWithOptions ,我們可以從中為瀏覽器的 UI 和內容建立兩個不同的環境。

    // 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 中的開發工具,為瀏覽器控制項提供動力。 我們也新增了接收網頁郵件的處理常式。 此處理常式可讓我們在使用者與此 WebView 中的控制項互動時執行某些動作。

您可以在網址列中輸入網頁的 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

我們需要傳達支援索引標籤和 UI 的 WebView,讓一個索引標籤的 Web 檢視中的使用者互動在另一個 Web 檢視中達到預期的效果。 WebView2Browser 會針對此目的使用一組非常有用的 WebView2 API,包括 PostWebMessageAsJsonadd_WebMessageReceivedICoreWebView2WebMessageReceivedEventHandler

在 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 更新,就會建立用於瀏覽的項目。 然後,歷程記錄 UI 會在索引標籤 window.chrome.postMessage中使用 擷取這些項目。

在這種情況下,大部分功能都是使用 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。

另請參閱