Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Este ejemplo, WebView2Browser, es un explorador web creado con el control WebView2 de Microsoft Edge .
Este ejemplo tiene su propio repositorio dedicado.
- Nombre de ejemplo: WebView2Browser
- Repositorio: WebView2Browser
- Archivo de solución:
WebViewBrowserApp.sln
WebView2Browser es una aplicación de escritorio de Windows de ejemplo que muestra las funcionalidades del control WebView2. La aplicación de ejemplo WebView2Browser usa varias instancias de WebView2.
Este ejemplo está creado como un proyecto Win32 Visual Studio 2019 . Usa C++ y JavaScript en el entorno WebView2.
WebView2Browser muestra algunos de los usos más sencillos de WebView2, como crear y navegar por un WebView, pero también algunos flujos de trabajo más complejos, como el uso de la API PostWebMessageAsJson para comunicarse entre controles WebView2 en entornos independientes. Este es un ejemplo de código enriquecido para demostrar cómo puede usar las API de WebView2 para crear su propia aplicación.
Paso 1: Instalar Visual Studio
- Instalar Visual Studio, incluido el soporte de C++.
Paso 2: Clonar el repositorio de WebView2Samples
- Clone (o descargue como
.zip) el repositorio WebView2Samples . Consulta Clonar el repositorio de WebView2Samples en Configuración del entorno de desarrollo para WebView2.
Paso 3: Abrir la solución en Visual Studio
Abra la solución en Visual Studio 2019. WebView2 SDK ya está incluido como un paquete NuGet en el proyecto. Si desea usar Visual Studio 2017, cambie el conjunto de herramientas de la plataforma del proyecto en Propiedades > del proyecto Propiedades de configuración Conjunto > de herramientas de la plataforma general>. Es posible que también debas cambiar el Windows SDK a la versión más reciente.
Realiza los cambios que se indican a continuación si usas una versión de Windows anterior a Windows 10.
Uso de versiones inferiores a Windows 10
Si desea compilar y ejecutar el explorador en versiones de Windows anteriores a Windows 10, realice los siguientes cambios. Esto es necesario debido a cómo se administran los PPP en Windows 10 frente a versiones anteriores de Windows.
- Si desea compilar y ejecutar el explorador en versiones de Windows anteriores a Windows 10: En
WebViewBrowserApp.cpp, cambieSetProcessDpiAwarenessContextaSetProcessDPIAware:
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);
// ...
- Si desea compilar y ejecutar el explorador en versiones de Windows anteriores a Windows 10: En
BrowserWindow.cpp, quite o comente la siguiente llamada aGetDpiForWindow:
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);
}
Paso 4: Crear y ejecutar la aplicación
Establezca el destino que desea compilar (como Depurar o Liberar, con destino a x86 o x64).
Compile la solución.
Ejecuta (o depura) la aplicación.
Cierre la aplicación.
Paso 5: Actualizar el SDK de WebView2
- Actualice la versión del SDK de WebView2 en Visual Studio. Para ello, haga clic con el botón derecho en el proyecto y, a continuación, haga clic en Administrar paquetes NuGet.
Paso 6: Crear y ejecutar la aplicación con el SDK de WebView2 actualizado
- Genere y ejecute la aplicación de nuevo.
Diseño del explorador
La aplicación de ejemplo WebView2Browser usa varias instancias de WebView2.
WebView2Browser tiene un enfoque multi-WebView para integrar contenido web y la interfaz de usuario de la aplicación en una aplicación de escritorio de Windows. Esto permite que el explorador use tecnologías web estándar (HTML, CSS, JavaScript) para iluminar la interfaz, pero también permite que la aplicación obtenga favicons de la web y use IndexedDB para almacenar favoritos e historial.
El enfoque multi-WebView implica el uso de dos entornos WebView separados (cada uno con su propio directorio de datos de usuario): uno para los WebView de la interfaz de usuario y el otro para todos los WebViews de contenido. Las WebViews de la interfaz de usuario (lista desplegable de controles y opciones) usan el entorno de la interfaz de usuario, mientras que las WebViews de contenido web (una por pestaña) usan el entorno de contenido.
Características
El ejemplo de WebView2Browser proporciona todas las funcionalidades necesarias para crear un explorador web básico, pero hay mucho espacio para que juegues.
El ejemplo WebView2Browser implementa las siguientes características:
- Volver/avanzar
- Volver a cargar página
- Cancelar navegación
- Varias pestañas
- Historial
- Favoritos
- Buscar desde la barra de direcciones
- Estado de seguridad de la página
- Borrar caché y cookies
API de WebView2
WebView2Browser hace uso de algunas de las API disponibles en WebView2. Para las API que no se usan aquí, puede encontrar más sobre ellas en la Referencia de Microsoft Edge WebView2. A continuación se muestra una lista de las API más interesantes que usa WebView2Browser y las características que habilitan.
| API | Características |
|---|---|
CreateCoreWebView2EnvironmentWithOptions |
Se usa para crear los entornos para las vistas web de la interfaz de usuario y el contenido. Se pasan diferentes directorios de datos de usuario para aislar la interfaz de usuario del contenido web. |
ICoreWebView2 |
Hay varias vistas web en WebView2Browser y la mayoría de las características usan miembros en esta interfaz. En la tabla siguiente se muestra cómo se usan. |
ICoreWebView2DevToolsProtocolEventReceivedEventHandler |
Se usa junto con add_DevToolsProtocolEventReceived para escuchar eventos de seguridad CDP y actualizar el icono de bloqueo en la interfaz de usuario del explorador. |
ICoreWebView2DevToolsProtocolEventReceiver |
Se usa junto con add_DevToolsProtocolEventReceived para escuchar los eventos de seguridad de CDP y actualizar el icono de bloqueo en la interfaz de usuario del explorador. |
ICoreWebView2ExecuteScriptCompletedHandler |
Se usa junto con ExecuteScript para obtener el título y el favicon de la página visitada. |
ICoreWebView2FocusChangedEventHandler |
Se usa junto con add_LostFocus para ocultar la lista desplegable de opciones del explorador cuando pierde el foco. |
ICoreWebView2HistoryChangedEventHandler |
Se usa junto con add_HistoryChanged para actualizar los botones de navegación en la interfaz de usuario del explorador. |
ICoreWebView2Controller |
Hay varios WebViewControllers en WebView2Browser y capturamos los WebViews asociados de ellos. |
ICoreWebView2NavigationCompletedEventHandler |
Se usa junto con add_NavigationCompleted para actualizar el botón de recarga en la interfaz de usuario del explorador. |
ICoreWebView2Settings |
Se usa para deshabilitar DevTools en la interfaz de usuario del explorador. |
ICoreWebView2SourceChangedEventHandler |
Se usa junto con add_SourceChanged para actualizar la barra de direcciones en la interfaz de usuario del explorador. |
ICoreWebView2WebMessageReceivedEventHandler |
Esta es una de las API más importantes para WebView2Browser. La mayoría de las funcionalidades relacionadas con la comunicación a través de WebViews usan esto. |
API ICoreWebView2 |
Características |
|---|---|
add_NavigationStarting |
Se usa para mostrar el botón de navegación de cancelación en los controles WebView. |
add_SourceChanged |
Se usa para actualizar la barra de direcciones. |
add_HistoryChanged |
Se usa para actualizar los botones Retroceder o Avanzar. |
add_NavigationCompleted |
Se usa para mostrar el botón de recarga una vez que se completa una navegación. |
ExecuteScript |
Se usa para obtener el título y el icono de favoritos de una página visitada. |
PostWebMessageAsJson |
Se usa para comunicar vistas web. Todos los mensajes usan JSON para pasar los parámetros necesarios. |
add_WebMessageReceived |
Se usa para controlar los mensajes web publicados en WebView. |
CallDevToolsProtocolMethod |
Se usa para habilitar la escucha de eventos de seguridad, que notificará los cambios de estado de seguridad en un documento. |
API ICoreWebView2Controller |
Características |
|---|---|
get_CoreWebView2 |
Se usa para obtener el CoreWebView2 asociado a este CoreWebView2Controllerarchivo . |
add_LostFocus |
Se usa para ocultar la lista desplegable de opciones cuando el usuario hace clic fuera de ella. |
Implementación de las características
En las secciones siguientes se describe cómo se implementaron algunas de las características de WebView2Browser. Puede consultar el código fuente para obtener más detalles sobre cómo funciona todo aquí. Esquema:
Conceptos básicos
Configurar el entorno, crear un WebView
WebView2 le permite hospedar contenido web en la aplicación de Windows. Expone los globales CreateCoreWebView2Environment y CreateCoreWebView2EnvironmentWithOptions a partir de los cuales podemos crear los dos entornos independientes para la interfaz de usuario y el contenido del explorador.
// 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());
}
Usamos ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler para crear las WebViews de la interfaz de usuario una vez que el entorno está listo.
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());
}
Estamos configurando algunas cosas aquí. La interfaz ICoreWebView2Settings se usa para deshabilitar DevTools en WebView que alimentan los controles del explorador. También estamos agregando un controlador para los mensajes web recibidos. Este controlador nos permitirá hacer algo cuando el usuario interactúe con los controles en este WebView.
Ir a la página web
Para ir a una página web, escriba su URI en la barra de direcciones. Al presionar Entrar, los controles WebView publicarán un mensaje web en la aplicación host para que pueda navegar por la pestaña activa a la ubicación especificada. El código siguiente muestra cómo la aplicación Win32 host administrará ese mensaje.
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 comprobará el URI con las páginas del explorador (es decir, favoritos, configuración, historial) y navegará a la ubicación solicitada o usará el URI proporcionado para buscar en Bing como respaldo.
Actualización de la barra de direcciones
La barra de direcciones se actualiza cada vez que se produce un cambio en el origen del documento de la pestaña activa y junto con otros controles al cambiar de pestaña. Cada vista web desencadenará un evento cuando cambie el estado del documento. Podemos usar este evento para obtener la nueva fuente en las actualizaciones y reenviar el cambio a los controles WebView (también actualizaremos los botones Retroceder y Avanzar).
// 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;
}
Hemos enviado el MG_UPDATE_URI mensaje junto con el URI al WebView de controles. Ahora queremos reflejar esos cambios en el estado de la pestaña y actualizar la interfaz de usuario si es necesario.
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;
Retroceder, seguir adelante
Cada WebView mantendrá un historial de las navegaciones que ha realizado, por lo que solo necesitamos conectar la interfaz de usuario del navegador con los métodos correspondientes. Si se puede navegar hacia atrás o hacia delante en WebView de la pestaña activa, los botones publicarán un mensaje web en la aplicación host cuando se haga clic en ellos.
El lado de 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);
}
});
El lado de la aplicación host:
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;
Recarga, detener navegación
Usamos el evento desencadenado por un contenido WebView para actualizar su NavigationStarting estado de carga de pestañas asociado en el WebView de controles. De forma similar, cuando un WebView desencadena el NavigationCompleted evento, usamos ese evento para indicar a los controles WebView que actualicen el estado de la pestaña. El estado de la pestaña activa en los controles WebView determinará si se muestra la recarga o el botón Cancelar. Cada uno de ellos publicará un mensaje en la aplicación host cuando se haga clic, para que WebView para esa pestaña se pueda volver a cargar o se cancele su navegación, según corresponda.
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"");
}
Algunas características interesantes
Comunicar los WebViews
Necesitamos comunicar las WebViews que impulsan las pestañas y la interfaz de usuario, para que las interacciones del usuario en el WebView de una pestaña tengan el efecto deseado en el otro WebView. WebView2Browser hace uso de un conjunto de API de WebView2 muy útiles para este propósito, incluidas PostWebMessageAsJson, add_WebMessageReceived e ICoreWebView2WebMessageReceivedEventHandler.
En el lado de JavaScript, estamos haciendo uso del objeto expuesto para llamar al método y agregar una lista de window.chrome.webview eventos para los postMessage mensajes recibidos.
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);
}
Control de pestañas
Se creará una nueva pestaña cada vez que el usuario haga clic en el botón de nueva pestaña a la derecha de las pestañas abiertas. El WebView del control publicará un mensaje en la aplicación host para crear el WebView para esa pestaña y crear un objeto que realice un seguimiento de su estado.
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);
}
}
En el lado de la aplicación host, el ICoreWebView2WebMessageReceivedEventHandler registrado capturará el mensaje y creará WebView para esa pestaña.
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());
}
La pestaña registra todos los controladores para poder reenviar actualizaciones a los controles WebView cuando se activen eventos. La pestaña está lista y se mostrará en el área de contenido del explorador. Al hacer clic en una pestaña de los controles, WebView publicará un mensaje en la aplicación host, que a su vez ocultará WebView para la pestaña activa anteriormente y mostrará el de la pestaña en la que se hizo clic.
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;
}
Actualización del icono de seguridad
Usamos el método CallDevToolsProtocolMethod para habilitar la escucha de eventos de seguridad. Siempre que se desencadene un securityStateChanged evento, usaremos el nuevo estado para actualizar el icono de seguridad en los controles 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;
Poblando el historial
WebView2Browser usa IndexedDB en los controles WebView para almacenar elementos del historial, solo un ejemplo de cómo WebView2 permite acceder a tecnologías web estándar como lo haría en el explorador. El elemento para una navegación se creará tan pronto como se actualice el URI. A continuación, la interfaz de window.chrome.postMessageusuario del historial recupera estos elementos en una pestaña que usa .
En este caso, la mayor parte de la funcionalidad se implementa con JavaScript en ambos extremos (controla WebView y el contenido de WebView cargando la interfaz de usuario), por lo que la aplicación host solo actúa como agente de mensajes para comunicar esos extremos.
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);
}
};
}
};
});
}
Control de JSON y URI
WebView2Browser usa cpprestsdk de Microsoft (Casablanca) para controlar todos los JSON en el lado C++ de las cosas. IUri y CreateUri también se usan para analizar rutas de archivo en URI y también se pueden usar para otros URI.