Win32 示例 WebView2Browser

此示例 WebView2Browser 是使用 Microsoft Edge WebView2 控件生成的 Web 浏览器

此示例具有自己的专用存储库。

  • 示例名称: 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:安装 Microsoft Edge 的预览频道

步骤 2:安装 Visual Studio

  1. 安装 Visual Studio,包括 C++ 支持。

建议使用 WebView2 运行时 进行安装。 最低版本为 86.0.622.38。

步骤 3:克隆 WebView2Samples 存储库

  • 克隆 (或下载为 .zipwebView2Samples 存储库) 。 请参阅为 WebView2 设置开发环境中的克隆 WebView2Samples存储库

步骤 4:在 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);
}

步骤 5:生成并运行应用

  1. 设置要生成 (的目标,例如“调试”或“发布”,面向 x86 或 x64) 。

  2. 生成解决方案。

  3. 运行 (或调试) 应用。

  4. 关闭应用。

步骤 6:更新 WebView2 SDK

  • 在 Visual Studio 中更新 WebView2 SDK 的版本。 为此,请右键单击项目,然后单击“ 管理 NuGet 包”。

步骤 7:使用更新的 WebView2 SDK 生成并运行应用

  • 再次生成并运行应用。

浏览器布局

WebView2Browser 示例应用使用多个 WebView2 实例。

WebView2Browser 采用多 WebView 方法将 Web 内容和应用程序 UI 集成到 Windows 桌面应用程序中。 这允许浏览器使用标准 Web 技术( (HTML、CSS、JavaScript) )来点亮界面,还允许应用从 Web 中提取 favicon,并使用 IndexedDB 来存储收藏夹和历史记录。

多 WebView 方法涉及使用两个单独的 WebView 环境, (每个环境都有自己的用户数据目录) :一个用于 UI WebView,另一个用于所有内容 WebView。 UI WebViews (控件和选项下拉列表) 使用 UI 环境,而 Web 内容 WebViews (每个选项卡一个,) 使用内容环境。

浏览器布局

功能

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 用于从访问的页面获取标题和 favicon。
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 之一。 涉及跨 Web 视图通信的大多数功能都使用此功能。
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 中的某些功能是如何实现的。 可在此处查看源代码,了解有关一切工作原理的更多详细信息。

内容:

基础知识

设置环境,创建 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 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 中的控件交互时,此处理程序将使我们能够执行某些操作。

可以通过在地址栏中输入网页的 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 搜索必应作为回退。

更新地址栏

每当活动选项卡的文档源发生更改时,都会更新地址栏,并在切换选项卡时与其他控件一起更新。 每个 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;

重载、停止导航

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

一些有趣的功能

传达 Web 视图

我们需要传达为选项卡和 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 更新后,将立即创建导航项。 然后,历史记录 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。

另请参阅