Exemplo de Win32: WebView2Browser

Este exemplo, WebView2Browser, é um navegador da Web criado com o controle Microsoft Edge WebView2 .

Este exemplo tem seu próprio repositório dedicado.

  • Nome da amostra: WebView2Browser
  • Repositório: WebView2Browser
  • Arquivo da solução: WebViewBrowserApp.sln

O aplicativo de exemplo WebView2Browser

WebView2Browser é um aplicativo de área de trabalho do Windows de exemplo que demonstra os recursos do controle WebView2. O aplicativo de exemplo WebView2Browser usa várias instâncias de WebView2.

Este exemplo foi criado como um projeto Win32 do Visual Studio 2019 . Ele usa C++ e JavaScript no ambiente WebView2.

O WebView2Browser mostra alguns dos usos mais simples do WebView2, como criar e navegar em um WebView, mas também alguns fluxos de trabalho mais complexos, como usar a API PostWebMessageAsJson para se comunicar entre controles WebView2 em ambientes separados. Este é um exemplo de código avançado para demonstrar como você pode usar as APIs do WebView2 para criar seu próprio aplicativo.

Etapa 1: Instalar o Visual Studio

  1. Instale o Visual Studio, incluindo suporte a C++.

Etapa 2: Clonar o repositório WebView2Samples

Etapa 3: Abrir a solução no Visual Studio

  1. Abra a solução no Visual Studio 2019. O SDK do WebView2 já está incluído como um pacote NuGet no projeto. Se você quiser usar o Visual Studio 2017, altere o Conjunto de Ferramentas da Plataforma do projeto em Propriedades > do Projeto Propriedades > de configuração Conjunto Geral > de Ferramentas da Plataforma. Talvez você também precise alterar o SDK do Windows para a versão mais recente.

  2. Faça as alterações listadas abaixo, se estiver usando uma versão do Windows inferior ao Windows 10.

Usando versões anteriores ao Windows 10

Se você deseja compilar e executar o navegador em versões do Windows anteriores ao Windows 10, faça as alterações a seguir. Isso é necessário devido à forma como o DPI é tratado no Windows 10 em comparação com as versões anteriores do Windows.

  1. Se você deseja compilar e executar o navegador em versões do Windows anteriores ao Windows 10: Em WebViewBrowserApp.cpp, altere SetProcessDpiAwarenessContext para 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. Se você deseja compilar e executar o navegador em versões do Windows anteriores ao Windows 10: Em BrowserWindow.cpp, remova ou comente a seguinte chamada paraGetDpiForWindow:
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);
}

Etapa 4: compilar e executar o aplicativo

  1. Defina o destino que você deseja criar (como Depurar ou Liberar, direcionar x86 ou x64).

  2. Compile a solução.

  3. Execute (ou depure) o aplicativo.

  4. Feche o aplicativo.

Etapa 5: Atualizar o SDK do WebView2

  • Atualize a versão do SDK do WebView2 no Visual Studio. Para fazer isso, clique com o botão direito do mouse no projeto e clique em Gerenciar Pacotes NuGet.

Etapa 6: compilar e executar o aplicativo com o SDK do WebView2 atualizado

  • Compile e execute o aplicativo novamente.

Layout do navegador

O aplicativo de exemplo WebView2Browser usa várias instâncias de WebView2.

O WebView2Browser tem uma abordagem multi-WebView para integrar o conteúdo da Web e a interface do usuário do aplicativo em um aplicativo da Área de Trabalho do Windows. Isso permite que o navegador use tecnologias da Web padrão (HTML, CSS, JavaScript) para iluminar a interface, mas também permite que o aplicativo busque favicons na Web e use IndexedDB para armazenar favoritos e histórico.

A abordagem multi-WebView envolve o uso de dois ambientes WebView separados (cada um com seu próprio diretório de dados do usuário): um para as WebViews da interface do usuário e outro para todas as WebViews de conteúdo. Os WebViews da interface do usuário (lista suspensa de controles e opções) usam o ambiente da interface do usuário, enquanto os WebViews de conteúdo da Web (um por guia) usam o ambiente de conteúdo.

Layout do navegador

Recursos

O exemplo WebView2Browser fornece todas as funcionalidades para criar um navegador da Web básico, mas há muito espaço para você brincar.

O exemplo WebView2Browser implementa os seguintes recursos:

  • Voltar/avançar
  • Recarregar página
  • Cancelar navegação
  • Várias guias
  • Histórico
  • Favoritos
  • Pesquisar na barra de endereços
  • Status de segurança da página
  • Limpar cache e cookies

WebView2 APIs

O WebView2Browser usa algumas das APIs disponíveis no WebView2. Para as APIs não usadas aqui, você pode encontrar mais sobre elas na Referência do Microsoft Edge WebView2. A seguir está uma lista das APIs mais interessantes que o WebView2Browser usa e os recursos que elas habilitam.

API Recursos
CreateCoreWebView2EnvironmentWithOptions Usado para criar os ambientes para a interface do usuário e o conteúdo WebViews. Diferentes diretórios de dados do usuário são passados para isolar a interface do usuário do conteúdo da Web.
ICoreWebView2 Há vários WebViews no WebView2Browser e a maioria dos recursos usa membros nesta interface, a tabela abaixo mostra como eles são usados.
ICoreWebView2DevToolsProtocolEventReceivedEventHandler Usado junto com add_DevToolsProtocolEventReceived para escutar eventos de segurança de CDP para atualizar o ícone de cadeado na interface do usuário do navegador.
ICoreWebView2DevToolsProtocolEventReceiver Usado junto com add_DevToolsProtocolEventReceived para escutar eventos de segurança de CDP para atualizar o ícone de cadeado na interface do usuário do navegador.
ICoreWebView2ExecuteScriptCompletedHandler Usado junto com ExecuteScript para obter o título e o favicon da página visitada.
ICoreWebView2FocusChangedEventHandler Usado junto com add_LostFocus para ocultar a lista suspensa de opções do navegador quando ele perde o foco.
ICoreWebView2HistoryChangedEventHandler Usado junto com add_HistoryChanged para atualizar os botões de navegação na IU do navegador.
ICoreWebView2Controller Existem vários WebViewControllers no WebView2Browser e buscamos os WebViews associados deles.
ICoreWebView2NavigationCompletedEventHandler Usado junto com add_NavigationCompleted para atualizar o botão recarregar na interface do usuário do navegador.
ICoreWebView2Settings Usado para desabilitar o DevTools na interface do usuário do navegador.
ICoreWebView2SourceChangedEventHandler Usado junto com add_SourceChanged para atualizar a barra de endereços na interface do usuário do navegador.
ICoreWebView2WebMessageReceivedEventHandler Esta é uma das APIs mais importantes para o WebView2Browser. A maioria das funcionalidades que envolvem comunicação entre WebViews usa isso.
ICoreWebView2 API Recursos
add_NavigationStarting Usado para exibir o botão cancelar navegação nos controles WebView.
add_SourceChanged Usado para atualizar a barra de endereços.
add_HistoryChanged Usado para atualizar os botões voltar/avançar.
add_NavigationCompleted Usado para exibir o botão recarregar quando uma navegação é concluída.
ExecuteScript Usado para obter o título e o favicon de uma página visitada.
PostWebMessageAsJson Usado para comunicar WebViews. Todas as mensagens usam JSON para passar os parâmetros necessários.
add_WebMessageReceived Usado para lidar com mensagens da Web postadas no WebView.
CallDevToolsProtocolMethod Usado para habilitar a escuta de eventos de segurança, que notificarão sobre alterações de status de segurança em um documento.
ICoreWebView2Controller API Destaque(s)
get_CoreWebView2 Usado para obter o CoreWebView2 associado a este CoreWebView2Controller.
add_LostFocus Usado para ocultar a lista suspensa de opções quando o usuário clica para sair dela.

Implementando os recursos

As seções abaixo descrevem como alguns dos recursos do WebView2Browser foram implementados. Você pode consultar o código-fonte para obter mais detalhes sobre como tudo funciona aqui. Estrutura de tópicos:

Noções básicas

Configurar o ambiente, criar um Modo de Exibição da Web

O WebView2 permite hospedar conteúdo da Web em seu aplicativo do Windows. Ele expõe os globais CreateCoreWebView2Environment e CreateCoreWebView2EnvironmentWithOptions a partir dos quais podemos criar os dois ambientes separados para a interface do usuário e o conteúdo do navegador.

    // 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 o ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler para criar o WebViews da interface do usuário assim que o ambiente estiver pronto.

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 algumas coisas aqui. A interface ICoreWebView2Settings é usada para desabilitar o DevTools no WebView, alimentando os controles do navegador. Também estamos adicionando um manipulador para mensagens da Web recebidas. Esse manipulador nos permitirá fazer algo quando o usuário interagir com os controles neste WebView.

Você pode navegar até uma página da Web inserindo seu URI na barra de endereços. Ao pressionar Enter, os controles WebView postarão uma mensagem da Web no aplicativo host para que ele possa navegar na guia ativa até o local especificado. O código a seguir mostra como o aplicativo host Win32 manipulará essa mensagem.

        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;

O WebView2Browser marcará o URI nas páginas do navegador (ou seja, favoritos, configurações, histórico) e navegará até o local solicitado ou usará o URI fornecido para pesquisar o Bing como fallback.

Atualizando a barra de endereços

A barra de endereços é atualizada sempre que há uma alteração na origem do documento da guia ativa e junto com outros controles ao alternar as guias. Cada WebView disparará um evento quando o estado do documento for alterado, podemos usar esse evento para obter a nova fonte de atualizações e encaminhar a alteração para os controles WebView (também atualizaremos os botões voltar e avançar).

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

Enviamos a MG_UPDATE_URI mensagem junto com o URI para os controles WebView. Agora, queremos refletir essas alterações no estado da guia e atualizar a interface do usuário, se necessário.

        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;

Voltando, avançando

Cada WebView manterá um histórico das navegações realizadas, portanto, só precisamos conectar a interface do usuário do navegador com os métodos correspondentes. Se o Modo de Exibição da Web da guia ativa puder ser navegado para trás/para frente, os botões postarão uma mensagem da Web no aplicativo host quando clicados.

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

O lado do aplicativo 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;

Recarregando, parar navegação

Usamos o NavigationStarting evento acionado por um WebView de conteúdo para atualizar seu estado de carregamento de tabulação associado nos controles WebView. Da mesma forma, quando um WebView dispara o NavigationCompleted evento, usamos esse evento para instruir os controles WebView a atualizar o estado da guia. O estado da guia ativa no WebView de controles determinará se o botão recarregar ou cancelar deve ser mostrado. Cada um deles postará uma mensagem de volta para o aplicativo host quando clicado, para que o WebView dessa guia possa ser recarregado ou ter sua navegação cancelada, de acordo.

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

Alguns recursos interessantes

Comunicação das WebViews

Precisamos comunicar os WebViews que alimentam as guias e a interface do usuário, para que as interações do usuário no WebView de uma guia tenham o efeito desejado no outro WebView. O WebView2Browser usa um conjunto de APIs WebView2 muito úteis para essa finalidade, incluindo PostWebMessageAsJson, add_WebMessageReceived e ICoreWebView2WebMessageReceivedEventHandler.

No lado do JavaScript, estamos usando o window.chrome.webview objeto exposto para chamar o postMessage método e adicionar um listador de eventos para mensagens recebidas.

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

Tratamento de tabulação

Uma nova guia será criada sempre que o usuário clicar no botão da nova guia à direita das guias abertas. O WebView do controle postará uma mensagem no aplicativo host para criar o WebView para essa guia e criar um objeto acompanhando seu 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);
    }
}

No lado do aplicativo host, o ICoreWebView2WebMessageReceivedEventHandler registrado capturará a mensagem e criará o WebView para essa guia.

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

A guia registra todos os manipuladores para que possa encaminhar atualizações para os controles WebView quando os eventos forem disparados. A guia está pronta e será mostrada na área de conteúdo do navegador. Clicar em uma guia nos controles WebView postará uma mensagem no aplicativo host, que por sua vez ocultará o WebView da guia ativa anteriormente e mostrará o da guia clicada.

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

Atualizando o ícone de segurança

Usamos o CallDevToolsProtocolMethod para habilitar a escuta de eventos de segurança. Sempre que um securityStateChanged evento for acionado, usaremos o novo estado para atualizar o ícone de segurança nos 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;

Preenchendo o histórico

O WebView2Browser usa o IndexedDB nos controles WebView para armazenar itens do histórico, apenas um exemplo de como o WebView2 permite que você acesse tecnologias da Web padrão da mesma forma que faria no navegador. O item para uma navegação será criado assim que o URI for atualizado. Esses itens são recuperados pela interface do usuário do histórico em uma guia que usa window.chrome.postMessage.

Nesse caso, a maior parte da funcionalidade é implementada usando JavaScript em ambas as extremidades (controles WebView e conteúdo WebView carregando a interface do usuário), portanto, o aplicativo host está atuando apenas como um agente de mensagens para comunicar essas extremidades.

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

    });
}

Manipulação de JSON e URIs

O WebView2Browser usa o cpprestsdk da Microsoft (Casablanca) para lidar com todo o JSON no lado C++ das coisas. IUri e CreateUri também são usados para analisar caminhos de arquivo em URIs e também podem ser usados para outros URIs.

Confira também