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 se compila como un proyecto de Visual Studio 2019 de Win32. 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 webView2 para compilar su propia aplicación.

Paso 1: Instalar un canal de versión preliminar de Microsoft Edge

Paso 2: Instalar Visual Studio

  1. Instale Visual Studio, incluida la compatibilidad con C++.

Se recomienda el tiempo de ejecución de WebView2 para la instalación. La versión mínima es 86.0.622.38.

Paso 3: Clonación del repositorio WebView2Samples

Paso 4: Abrir la solución en Visual Studio

  1. Abra la solución en Visual Studio 2019. El SDK de WebView2 ya se incluye como un paquete NuGet en el proyecto. Si desea usar Visual Studio 2017, cambie el conjunto de herramientas de plataforma del proyecto en > propiedades de configuración de propiedades del proyecto Conjunto de herramientas > general > de la plataforma. También es posible que tenga que cambiar el Windows SDK a la versión más reciente.

  2. Realice los cambios que se enumeran a continuación, si usa una versión de Windows siguiente Windows 10.

Uso de versiones siguientes Windows 10

Si desea compilar y ejecutar el explorador en versiones de Windows antes de Windows 10, realice los siguientes cambios. Esto es necesario debido a cómo se controla el PPP en Windows 10 frente a versiones anteriores de Windows.

  1. Si desea compilar y ejecutar el explorador en versiones de Windows antes de Windows 10: En WebViewBrowserApp.cpp, cambie SetProcessDpiAwarenessContext a 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);

    // ...
  1. Si desea compilar y ejecutar el explorador en versiones de Windows antes de 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 5: Compilación y ejecución de la aplicación

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

  2. Compile la solución.

  3. Ejecute (o depure) la aplicación.

  4. Cierre la aplicación.

Paso 6: Actualización del 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 7: Compilación y ejecución de la aplicación con el SDK de WebView2 actualizado

  • Compile y vuelva a ejecutar la aplicación.

Diseño del explorador

La aplicación de ejemplo WebView2Browser usa varias instancias de WebView2.

WebView2Browser tiene un enfoque multi-WebView para integrar el contenido web y la interfaz de usuario de la aplicación en una aplicación de escritorio de Windows. Esto permite al explorador usar tecnologías web estándar (HTML, CSS, JavaScript) para iluminar la interfaz, pero también permite que la aplicación capture los iconos de la web y use IndexedDB para almacenar favoritos e historial.

El enfoque multi-WebView implica el uso de dos entornos WebView independientes (cada uno con su propio directorio de datos de usuario): uno para las vistas web de la interfaz de usuario y el otro para todos los WebView de contenido. Las vistas web de la interfaz de usuario (lista desplegable de controles y opciones) usan el entorno de interfaz de usuario, mientras que las vistas web de contenido web (una por pestaña) usan el entorno de contenido.

Diseño del explorador

Características

El ejemplo WebView2Browser proporciona todas las funcionalidades para crear un explorador web básico, pero hay mucho espacio para jugar.

El ejemplo WebView2Browser implementa las siguientes características:

  • Volver/forward
  • Página Volver a cargar
  • Cancelar navegación
  • Varias pestañas
  • Historial
  • Favoritos
  • Búsqueda desde la barra de direcciones
  • Estado de seguridad de página
  • Borrar la memoria caché y las cookies

API de WebView2

WebView2Browser hace uso de unas cuantas API disponibles en WebView2. Para las API que no se usan aquí, puede encontrar más información 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 contenido y la interfaz de usuario. 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 para actualizar el icono de bloqueo en la interfaz de usuario del explorador.
ICoreWebView2DevToolsProtocolEventReceiver Se usa junto con add_DevToolsProtocolEventReceived para escuchar eventos de seguridad cdp para 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 icono de favoritos 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 WebView 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 entre WebViews lo usan.
ICoreWebView2 API Características
add_NavigationStarting Se usa para mostrar el botón cancelar navegación en el control WebView de controles.
add_SourceChanged Se usa para actualizar la barra de direcciones.
add_HistoryChanged Se usa para actualizar los botones ir hacia atrás o hacia delante.
add_NavigationCompleted Se usa para mostrar el botón de recarga una vez completada la 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 WebViews. 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án los cambios de estado de seguridad en un documento.
ICoreWebView2Controller API Características
get_CoreWebView2 Se usa para obtener el CoreWebView2 asociado a este CoreWebView2Controller.
add_LostFocus Se usa para ocultar la lista desplegable de opciones cuando el usuario hace clic en 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 ver el código fuente para obtener más detalles sobre cómo funciona todo aquí.

Contenido:

Conceptos básicos

Configuración del entorno, creación de una instancia de WebView

WebView2 permite hospedar contenido web en la aplicación de Windows. Expone los globales CreateCoreWebView2Environment y CreateCoreWebView2EnvironmentWithOptions desde los que 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 vistas web 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 activa los controles del explorador. También vamos a agregar un controlador para los mensajes web recibidos. Este controlador nos permitirá hacer algo cuando el usuario interactúe con los controles de este WebView.

Para navegar 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 controlará 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 en 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 bing como reserva.

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 WebView desencadenará un evento cuando cambie el estado del documento, podemos usar este evento para obtener el nuevo origen en las actualizaciones y reenviar el cambio a los controles WebView (también actualizaremos los botones Volver atrás e Ir hacia delante).

        // 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 a los controles WebView. 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;

Volver atrás, en el futuro

Cada WebView conservará un historial para las navegación que ha realizado, por lo que solo necesitamos conectar la interfaz de usuario del explorador con los métodos correspondientes. Si la vista web de la pestaña activa se puede navegar hacia atrás o hacia delante, los botones publicarán un mensaje web en la aplicación host cuando se haga clic en ella.

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);
        }
    });

En 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, detención de la navegación

Usamos el NavigationStarting evento desencadenado por un WebView de contenido para actualizar su estado de carga de tabulación asociado en el Control 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 tabulación activo de los controles WebView determinará si se va a mostrar la recarga o el botón cancelar. Cada uno de ellos volverá a publicar un mensaje en la aplicación host cuando se haga clic en ella, de modo que el WebView de esa pestaña se pueda volver a cargar o cancelar su navegación, en consecuencia.

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

Comunicación de las vistas web

Es necesario comunicar las vistas web que alimentan las pestañas y la interfaz de usuario, de modo que las interacciones del usuario en el WebView de una pestaña tengan el efecto deseado en el otro WebView. WebView2Browser usa el conjunto de API WebView2 muy útiles para este propósito, como PostWebMessageAsJson, add_WebMessageReceived e ICoreWebView2WebMessageReceivedEventHandler.

En el lado de JavaScript, vamos a usar el window.chrome.webview objeto expuesto para llamar al postMessage método y agregar un lister de eventos para los 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 nuevo botón de pestaña situado a la derecha de las pestañas abiertas. WebView del control publicará un mensaje en la aplicación host para crear el WebView para esa pestaña y crear un objeto que haga 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á el 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 que pueda reenviar las actualizaciones a los controles WebView cuando se activen los 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, se publicará un mensaje en la aplicación host, que a su vez ocultará el WebView de la pestaña activa anteriormente y mostrará el de la pestaña en la que se ha hecho 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 CallDevToolsProtocolMethod para habilitar la escucha de eventos de seguridad. Cada vez que se desencadena un securityStateChanged evento, usaremos el nuevo estado para actualizar el icono de seguridad en el Control WebView de controles.

        // 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;

Rellenar la historia

WebView2Browser usa IndexedDB en los controles WebView para almacenar elementos de historial, solo un ejemplo de cómo WebView2 le permite acceder a tecnologías web estándar como lo haría en el explorador. El elemento de una navegación se creará en cuanto se actualice el URI. A continuación, la interfaz de usuario del historial recupera estos elementos en una pestaña que usa window.chrome.postMessage.

En este caso, la mayoría de la funcionalidad se implementa mediante JavaScript en ambos extremos (controla WebView y WebView de contenido que carga 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 (Casablanca) de Microsoft para controlar todo JSON en el lado de las cosas de C++. IUri y CreateUri también se usan para analizar rutas de acceso de archivo en URI y también se pueden usar para otros URI.

Consulte también