共用方式為


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 存放庫

  • 複製 (或下載為 .zipWebView2Samples 存放庫) 。 請參閱設定 WebView2 的開發環境中的複製 WebView2Samples 存放庫

步驟 3:在 Visual Studio 中開啟解決方案

  1. 在 Visual Studio 2019 中開啟解決方案。 WebView2 SDK 已包含為專案中的 NuGet 套件。 如果您想要使用 Visual Studio 2017,請在 [專案屬性設定] 屬性 > [一般>平臺工具組] 中變更專案的 > [平臺工具組]。 您可能也需要將 Windows SDK 變更為最新版本。

  2. 如果您使用下列 Windows 版本,請進行下列變更 Windows 10。

使用下列版本 Windows 10

如果您想要在 Windows 10 之前,先在 Windows 版本中建置並執行瀏覽器,請進行下列變更。 這是必要的,因為在 Windows 10 與舊版 Windows 中處理 DPI 的方式。

  1. 如果您想要在 Windows 版本中建置及執行瀏覽器,請先 Windows 10:在 中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 版本中建置並執行瀏覽器,請先 Windows 10:在 中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 具有多 WebView 方法,可將 Web 內容和應用程式 UI 整合到 Windows 傳統型應用程式中。 這可讓瀏覽器使用標準 Web 技術 (HTML、CSS、JavaScript) 來啟動介面,但也可讓應用程式從 Web 擷取服務,並使用 IndexedDB 來儲存我的最愛和歷程記錄。

多 WebView 方法牽涉到使用兩個不同的 WebView 環境 (每個環境都有自己的使用者數據目錄) :一個用於 UI WebView,另一個用於所有內容 WebView。 UI WebViews (控件和選項下拉式清單) 使用 UI 環境,而 Web 內容 WebViews (每個索引標籤一個) 使用內容環境。

瀏覽器配置

功能

WebView2Browser 範例提供所有功能來製作基本網頁瀏覽器,但您有足夠空間可以四處玩玩。

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 與一起 add_HistoryChanged 用來更新瀏覽器 UI 中的瀏覽按鈕。
ICoreWebView2Controller WebView2Browser 中有數個 WebViewController,我們會從中擷取相關聯的 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 用來與 WebView 通訊。 所有訊息都會使用 JSON 來傳遞所需的參數。
add_WebMessageReceived 用來處理張貼至 WebView 的 Web 訊息。
CallDevToolsProtocolMethod 用來啟用接聽安全性事件,這會通知檔中的安全性狀態變更。
ICoreWebView2Controller API 功能 ()
get_CoreWebView2 用來取得與此 CoreWebView2Controller 相關聯的 CoreWebView2。
add_LostFocus 當使用者按下拉式清單時,用來隱藏選項下拉式清單。

實作功能

下列各節說明如何實作 WebView2Browser 中的某些功能。 如需有關一切運作方式的詳細資訊,您可以查看原始程式碼。 大綱:

基本知識

設定環境、建立 WebView

WebView2 可讓您在 Windows 應用程式中裝載 Web 內容。 它會公開全域 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 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,以瀏覽至網頁。 按 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;
}

我們已將訊息連同 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 都會保留其已執行之導覽的歷程記錄,因此我們只需要將瀏覽器 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;

重載,停止導覽

我們會使用 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"");
        }

一些有趣的功能

通訊 WebView

我們需要傳達支援索引標籤和 UI 的 WebView,讓一個索引標籤標的 WebView 中的使用者互動在其他 WebView 中具有所需的效果。 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 更新後,就會立即建立導覽的專案。 然後,使用的索引標籤 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。

另請參閱