Compartilhar via


Exemplo do Win32 WebView2Browser

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

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 do WebView2.

Este exemplo é criado como um projeto do Win32 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 APIs Do WebView2 para criar seu próprio aplicativo.

Etapa 1: instalar um canal de visualização do Microsoft Edge

  • Se ainda não estiver instalado, instale um canal de visualização do Microsoft Edge em um sistema operacional com suporte. Para fazer isso, acesse Tornar-se um Microsoft Edge Insider.

Etapa 2: Instalar o Visual Studio

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

O WebView2 Runtime é recomendado para a instalação. A versão mínima é 86.0.622.38.

Etapa 3: Clonar o repositório WebView2Samples

Etapa 4: 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 de Plataforma do projeto nas propriedades de Configuração de Propriedades > do Projeto 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 você estiver usando uma versão do Windows abaixo Windows 10.

Usando versões abaixo Windows 10

Se você quiser criar e executar o navegador em versões do Windows antes de Windows 10, faça as seguintes alterações. Isso é necessário devido à forma como a DPI é tratada em Windows 10 versus versões anteriores do Windows.

  1. Se você quiser criar e executar o navegador em versões do Windows antes de Windows 10: In 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ê quiser criar e executar o navegador em versões do Windows antes de Windows 10: Em BrowserWindow.cpp, remova ou comente a seguinte chamada para 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);
}

Etapa 5: Criar e executar o aplicativo

  1. Defina o destino que você deseja criar (como Depuração ou Versão, direcionando x86 ou x64).

  2. Crie a solução.

  3. Execute (ou Depurar) o aplicativo.

  4. Feche o aplicativo.

Etapa 6: 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 7: criar e executar o aplicativo com o SDK do WebView2 atualizado

  • Crie e execute o aplicativo novamente.

Layout do navegador

O aplicativo de exemplo WebView2Browser usa várias instâncias do 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 web padrão (HTML, CSS, JavaScript) para iluminar a interface, mas também permite que o aplicativo busque favicons da 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 de usuário): um para o WebViews da interface do usuário e outro para todos os WebViews de conteúdo. WebViews da interface do usuário (lista suspensa de controles e opções) usam o ambiente da interface do usuário, enquanto 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 do WebView2Browser implementa os seguintes recursos:

  • Voltar/forward
  • Página recarregar
  • Cancelar navegação
  • Várias guias
  • Histórico
  • Favoritos
  • Pesquisa da barra de endereços
  • Segurança da página status
  • Limpar cache e cookies

WebView2 APIs

O WebView2Browser disponibiliza um punhado de APIs 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 eles habilitam.

API Recursos
CreateCoreWebView2EnvironmentWithOptions Usado para criar os ambientes para WebViews de interface do usuário e conteúdo. 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 faz uso de membros nesta interface, a tabela abaixo mostra como eles são usados.
ICoreWebView2DevToolsProtocolEventReceivedEventHandler Usado junto com add_DevToolsProtocolEventReceived para ouvir eventos de segurança cdp para atualizar o ícone de bloqueio na interface do usuário do navegador.
ICoreWebView2DevToolsProtocolEventReceiver Usado junto com add_DevToolsProtocolEventReceived para ouvir eventos de segurança cdp para atualizar o ícone de bloqueio na interface do usuário do navegador.
ICoreWebView2ExecuteScriptCompletedHandler Usado junto com ExecuteScript para obter o título e 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 interface do usuário do navegador.
ICoreWebView2Controller Há vários WebViewControllers no WebView2Browser e buscamos os WebViews associados deles.
ICoreWebView2NavigationCompletedEventHandler Usado junto com add_NavigationCompleted para atualizar o botão de recarga na interface do usuário do navegador.
ICoreWebView2Settings Usado para desabilitar 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 envolvendo 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 botões de voltar/avançar.
add_NavigationCompleted Usado para exibir o botão de recarga quando uma navegação for 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 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 as alterações de status de segurança em um documento.
ICoreWebView2Controller API Recursos(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 em longe dela.

Implementando os recursos

As seções abaixo descrevem como alguns dos recursos no WebView2Browser foram implementados. Você pode examinar o código-fonte para obter mais detalhes sobre como tudo funciona aqui.

Conteúdo:

Noções básicas

Configurar o ambiente, criar um WebView

O WebView2 permite hospedar conteúdo da Web em seu aplicativo Windows. Ele expõe os globais CreateCoreWebView2Environment e CreateCoreWebView2EnvironmentWithOptions 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 quando 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 DevTools no WebView que alimenta 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 abaixo mostra como o aplicativo Win32 do host lidará com 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 em relação às 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 um 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 guias. Cada WebView disparará um evento quando o estado do documento for alterado, podemos usar esse evento para obter a nova fonte sobre 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, indo para a frente

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

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;

Recarregar, parar a navegação

Usamos o NavigationStarting evento disparado por um conteúdo WebView para atualizar o estado de carregamento da guia associada 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 nos controles WebView determinará se o botão de recarga ou cancelamento será exibido. Cada uma delas postará uma mensagem de volta no aplicativo host quando clicada, para que o WebView para essa 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

Comunicando o WebViews

Precisamos comunicar as 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 o conjunto de APIs WebView2 muito úteis para essa finalidade, incluindo PostWebMessageAsJson, add_WebMessageReceived e ICoreWebView2WebMessageReceivedEventHandler.

No lado javaScript, estamos fazendo uso do window.chrome.webview objeto exposto para chamar o postMessage método e adicionar um lister de evento 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);
}

Manipulação de guias

Uma nova guia será criada sempre que o usuário clicar no botão 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 que acompanha 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 registrará 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 ele 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 para a guia anteriormente ativa e mostrará a 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 para eventos de segurança. Sempre que um securityStateChanged evento for disparado, 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;

Povoando a história

O WebView2Browser usa IndexedDB nos controles WebView para armazenar itens de histórico, apenas um exemplo de como o WebView2 permite que você acesse tecnologias Da Web padrão como 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 de histórico em uma guia que faz uso de window.chrome.postMessage.

Nesse caso, a maioria das funcionalidades é implementada usando o JavaScript em ambas as extremidades (controla o WebView e o conteúdo WebView carregando a interface do usuário) de modo que o aplicativo host esteja 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 (Casablanca) da Microsoft para lidar com todos os 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 outras URIs.

Confira também