WebView2Browser de ejemplo de Win32

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

La aplicación de ejemplo WebView2Browser

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

  1. Instalar Visual Studio, incluido el soporte de C++.

Paso 2: Clonar el repositorio de WebView2Samples

Paso 3: Abrir la solución en Visual Studio

  1. 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.

  2. 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.

  1. Si desea compilar y ejecutar el explorador en versiones de Windows anteriores a Windows 10: En WebViewBrowserApp.cpp, cambie SetProcessDpiAwarenessContext aSetProcessDPIAware:
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. Si desea compilar y ejecutar el explorador en versiones de Windows anteriores a Windows 10: En BrowserWindow.cpp, quite o comente la siguiente llamada a 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);
}

Paso 4: Crear y ejecutar la aplicación

  1. Establezca el destino que desea compilar (como Depurar o Liberar, con destino a x86 o x64).

  2. Compile la solución.

  3. Ejecuta (o depura) la aplicación.

  4. 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.

Diseño del explorador

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.

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.

Vea también