共用方式為


Win32 範例 WebView2Browser

這個範例 WebView2Browser,是一款使用 Microsoft Edge WebView2 控制項所建構的網頁瀏覽器。

這個範例有自己的專用倉庫。

  • 範例名稱: WebView2Browser
  • 倉庫: WebView2Browser
  • 解答檔案: WebViewBrowserApp.sln

WebView2Browser 範例應用程式

WebView2Browser 是一個範例 Windows 桌面應用程式,展示 WebView2 控制項的功能。 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 儲存庫

  • (複製或下載 WebView2Samples.zip 倉庫) 。 請參考 Brion WebView2Samples 倉庫「建立你的 WebView2 開發環境」一節。

步驟 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);
}

步驟四:建立並執行應用程式

  1. 設定你想建立的目標 (例如除錯或發佈,目標是 x86 或 x64) 。

  2. 打造解決方案。

  3. 執行 (或) 應用程式除錯。

  4. 關閉應用程式。

步驟 5:更新 WebView2 SDK

  • 在 Visual Studio 更新 WebView2 SDK 的版本。 操作方法是右鍵點擊專案,然後點選 管理 NuGet 套件

步驟 6:使用更新的 WebView2 SDK 建置並執行應用程式

  • 重新建立並執行應用程式。

瀏覽器版面

WebView2Browser 範例應用程式使用多個 WebView2 實例。

WebView2Browser 採用多重 WebView 方法,將網頁內容與應用程式介面整合到 Windows 桌面應用程式中。 這讓瀏覽器能使用標準網頁技術 (HTML、CSS、JavaScript) 來點亮介面,同時也能從網頁擷取 favicons,並使用 IndexedDB 來儲存收藏與歷史紀錄。

多重 WebView 方法涉及使用兩個獨立的 WebView 環境, (各自擁有自己的使用者資料目錄) :一個用於 UI WebView,另一個用於所有內容 WebView。 UI WebViews (控制項和選項下拉選單) 使用介面環境,而網頁內容 WebViews 則 (每個分頁) 使用內容環境。

瀏覽器版面

功能

WebView2Browser 範例提供了製作基本瀏覽器的所有功能,但你仍有許多空間可以自由嘗試。

WebView2Browser 範例實作了以下功能:

  • 返回/前進
  • 重新載入頁面
  • 取消導航
  • 多重分頁
  • 歷程記錄
  • 我的最愛
  • 從地址欄搜尋
  • 頁面安全狀態
  • 清除快取與 Cookie

WebView2 API

WebView2Browser 利用了 WebView2 中少數可用的 API。 關於未在此處使用的 API,您可以在 Microsoft Edge WebView2 參考中找到更多相關資訊。 以下是 WebView2Browser 使用的最有趣 API 及其所啟用的功能清單。

API 功能
CreateCoreWebView2EnvironmentWithOptions 用於建立 UI 與內容 WebView 的環境。 不同的使用者資料目錄會被傳遞,以隔離 UI 與網頁內容。
ICoreWebView2 WebView2Browser 中有多個 WebView,且大多數功能在此介面中使用成員,下表展示了它們的使用方式。
ICoreWebView2DevToolsProtocolEventReceivedEventHandler 搭配 add_DevToolsProtocolEventReceived 一起用來監聽 CDP 的安全事件,以更新瀏覽器介面中的鎖圖示。
ICoreWebView2DevToolsProtocolEventReceiver add_DevToolsProtocolEventReceived搭配用來監聽 CDP 的安全事件,以更新瀏覽器介面中的鎖定圖示。
ICoreWebView2ExecuteScriptCompletedHandler 搭配 來 ExecuteScript 取得訪問頁面的標題和同檔。
ICoreWebView2FocusChangedEventHandler add_LostFocus搭配使用來隱藏瀏覽器選項下拉選單,當它失去焦點時。
ICoreWebView2HistoryChangedEventHandler add_HistoryChanged搭配用來更新瀏覽器介面中的導航按鈕。
ICoreWebView2Controller WebView2Browser 中有多個 WebViewController,我們會從它們中取得相關的 WebView。
ICoreWebView2NavigationCompletedEventHandler add_NavigationCompleted搭配更新瀏覽器介面中的換彈按鈕一起使用。
ICoreWebView2Settings 用來在瀏覽器介面中停用 DevTools。
ICoreWebView2SourceChangedEventHandler 同時用 add_SourceChanged 來更新瀏覽器介面中的地址列。
ICoreWebView2WebMessageReceivedEventHandler 這是 WebView2Browser 最重要的 API 之一。 大多數涉及跨 WebViews 通訊的功能都使用此方法。
ICoreWebView2 API 功能
add_NavigationStarting 用於顯示控制 WebView 中的取消導航按鈕。
add_SourceChanged 用來更新地址列。
add_HistoryChanged 以前會更新返回/前進按鈕。
add_NavigationCompleted 用來在導航完成後顯示換彈按鈕。
ExecuteScript 以前會取得已瀏覽頁面的標題和收藏。
PostWebMessageAsJson 用來通訊 WebViews。 所有訊息都使用 JSON 來傳遞所需的參數。
add_WebMessageReceived 用於處理發佈到 WebView 的網頁訊息。
CallDevToolsProtocolMethod 用於啟用監聽安全事件,該事件會通知文件中安全狀態變更。
ICoreWebView2Controller API 特色 (的)
get_CoreWebView2 用來讓 CoreWebView2 與這個 CoreWebView2Controller 綁定。
add_LostFocus 以前是用來隱藏使用者點擊離開選項下拉選單時的選項。

功能實作

以下章節說明 WebView2Browser 部分功能的實作方式。 你可以參考原始碼,了解更多運作細節。 略:

基本知識

設定環境,建立 WebView。

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 中驅動瀏覽器控制項的 DevTools。 我們也新增了一個處理程式來處理已接收的網路訊息。 這個處理器會讓我們在使用者操作 WebView 控制項時做些事情。

你可以在地址列輸入網頁的 URI 來導航。 按下 Enter 鍵時,WebView 控制項會向主機應用程式發布網頁訊息,使其能透過活動分頁導航到指定位置。 以下程式碼顯示主機 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;
}

我們已將訊息連同 URI 一同傳送 MG_UPDATE_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 都會保留其已執行的導航歷史,因此我們只需將瀏覽器介面與相應的方法連結即可。 如果活動分頁的 WebView 可以前後移動,點擊按鈕會向主機應用程式發布網頁訊息。

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;

正在換彈,停止導航

我們會利用 NavigationStarting 內容 WebView 觸發的事件,在 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 的溝通

我們需要溝通驅動分頁和使用者介面的 WebView,讓一個分頁的 WebView 使用者互動在另一個 WebView 中達到預期效果。 WebView2Browser 為此目的使用了一組非常實用的 WebView2 API,包括 PostWebMessageAsJsonadd_WebMessageReceived 以及 ICoreWebView2WebMessageReceivedEventHandler

在 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);
}

Tab 處理

每當使用者點擊開啟分頁右側的新 分頁 按鈕時,就會建立一個新分頁。 控制項的 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 如何讓你像在瀏覽器中一樣存取標準網路技術的例子。 導航項目會在 URI 更新後立即建立。 這些項目會被歷史介面中的分頁取出,並利用 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。

另請參閱