Win32 示例 WebView2Browser
此示例 WebView2Browser 是使用 Microsoft Edge WebView2 控件生成的 Web 浏览器 。
此示例具有自己的专用存储库。
- 示例名称: WebView2Browser
- 存储库: WebView2Browser
- 解决方案文件:
WebViewBrowserApp.sln
WebView2Browser 是一个示例 Windows 桌面应用程序,用于演示 WebView2 控件的功能。 WebView2Browser 示例应用使用多个 WebView2 实例。
此示例生成为 Win32 Visual Studio 2019 项目。 它在 WebView2 环境中使用 C++ 和 JavaScript。
WebView2Browser 展示了 WebView2 的一些最简单的用法(例如创建和导航 WebView),但也展示了一些更复杂的工作流,例如使用 PostWebMessageAsJson API 在单独的环境中跨 WebView2 控件进行通信。 这是一个丰富的代码示例,演示如何使用 WebView2 API 生成自己的应用。
步骤 1:安装 Visual Studio
- 安装 Visual Studio,包括C++支持。
步骤 2:克隆 WebView2Samples 存储库
- 克隆 (或下载为
.zip
webView2Samples 存储库) 。 请参阅为 WebView2 设置开发环境中的克隆 WebView2Samples存储库。
步骤 3:在 Visual Studio 中打开解决方案
在 Visual Studio 2019 中打开解决方案。 WebView2 SDK 已作为 NuGet 包包含在项目中。 若要使用 Visual Studio 2017,请在“项目属性配置”属性>“”常规>平台工具集“>中更改项目的”平台工具集”。 可能还需要将Windows SDK更改为最新版本。
如果使用低于 Windows 10 的 Windows 版本,请执行下面列出的更改。
使用低于 Windows 10 的版本
如果要在Windows 10之前在 Windows 版本中生成并运行浏览器,请进行以下更改。 由于在 Windows 10 与以前版本的 Windows 中处理 DPI 的方式,因此需要这样做。
- 如果要在 Windows 10 之前在 Windows 版本中生成并运行浏览器:在 中
WebViewBrowserApp.cpp
,将 更改为SetProcessDpiAwarenessContext
SetProcessDPIAware
:
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);
// ...
- 如果要在 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:生成并运行应用
设置要生成 (的目标,例如“调试”或“发布”,面向 x86 或 x64) 。
生成解决方案。
运行 (或调试) 应用。
关闭应用。
步骤 5:更新 WebView2 SDK
- 在 Visual Studio 中更新 WebView2 SDK 的版本。 为此,请右键单击项目,然后单击“ 管理 NuGet 包”。
步骤 6:使用更新的 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 内容。 它公开全局 CreateCoreWebView2Environment 和 CreateCoreWebView2EnvironmentWithOptions ,我们可以从中为浏览器的 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 来实现此目的,包括 PostWebMessageAsJson、 add_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);
}
制表符处理
每当用户单击打开的选项卡右侧的 “新建”选项卡 按钮时,都会创建一个新选项卡。 控件的 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。