Compartilhar via


WebView2Browser de exemplo Win32

Este exemplo, WebView2Browser, é um browser criado com o controlo Microsoft Edge WebView2 .

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

  • Nome de exemplo: WebView2Browser
  • Repositório: WebView2Browser
  • Ficheiro de solução: WebViewBrowserApp.sln

A aplicação de exemplo WebView2Browser

WebView2Browser é uma aplicação de ambiente de trabalho do Windows de exemplo que demonstra as capacidades do controlo WebView2. A aplicação de exemplo WebView2Browser utiliza várias instâncias webView2.

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

O WebView2Browser mostra algumas das utilizações mais simples do WebView2, como criar e navegar numa WebView, mas também alguns fluxos de trabalho mais complexos, como utilizar a API PostWebMessageAsJson para comunicar entre controlos WebView2 em ambientes separados. Este é um exemplo de código avançado para demonstrar como pode utilizar as APIs WebView2 para criar a sua própria aplicação.

Passo 1: Instalar o Visual Studio

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

Passo 2: Clonar o repositório WebView2Samples

Passo 3: abrir a solução no Visual Studio

  1. Abra a solução no Visual Studio 2019. O SDK WebView2 já está incluído como um pacote NuGet no projeto. Se quiser utilizar o Visual Studio 2017, altere o Conjunto de Ferramentas de Plataforma do projeto nas propriedades de Configuração de Propriedades > do Projeto Conjunto de Ferramentas > de Plataforma Geral>. Também poderá ter de alterar a SDK do Windows para a versão mais recente.

  2. Efetue as alterações listadas abaixo, se estiver a utilizar uma versão do Windows abaixo Windows 10.

Utilizar versões abaixo Windows 10

Se quiser criar e executar o browser em versões do Windows antes de Windows 10, faça as seguintes alterações. Isto é necessário devido à forma como o DPI é processado em Windows 10 vs. versões anteriores do Windows.

  1. Se quiser criar e executar o browser em versões do Windows antes de 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 quiser criar e executar o browser 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);
}

Passo 4: criar e executar a aplicação

  1. Defina o destino que pretende criar (como Depuração ou Versão, filtrando x86 ou x64).

  2. Crie a solução.

  3. Execute (ou Depurar) a aplicação.

  4. Feche a aplicação.

Passo 5: Atualizar o SDK WebView2

  • Atualize a versão do SDK WebView2, no Visual Studio. Para tal, clique com o botão direito do rato no projeto e, em seguida, clique em Gerir Pacotes NuGet.

Passo 6: criar e executar a aplicação com o SDK WebView2 atualizado

  • Crie e execute a aplicação novamente.

Esquema do browser

A aplicação de exemplo WebView2Browser utiliza várias instâncias webView2.

O WebView2Browser tem uma abordagem multi-WebView para integrar conteúdo Web e IU da aplicação numa aplicação de Ambiente de Trabalho do Windows. Isto permite que o browser utilize tecnologias Web padrão (HTML, CSS, JavaScript) para iluminar a interface, mas também permite que a aplicação obtenha favicons da Web e utilize IndexedDB para armazenar favoritos e histórico.

A abordagem multi-WebView envolve a utilização de dois ambientes WebView separados (cada um com o seu próprio diretório de dados de utilizador): um para webViews de IU e outro para todos os webViews de conteúdo. Os WebViews da IU (lista pendente de controlos e opções) utilizam o ambiente de IU, enquanto os WebViews de conteúdo Web (um por separador) utilizam o ambiente de conteúdo.

Esquema do browser

Recursos

O exemplo WebView2Browser fornece todas as funcionalidades para criar um browser básico, mas há muito espaço para brincar.

O exemplo WebView2Browser implementa as seguintes funcionalidades:

  • Voltar/avançar
  • Recarregar página
  • Cancelar navegação
  • Vários separadores
  • Histórico
  • Favoritos
  • Procurar a partir da barra de endereço
  • Status de segurança de páginas
  • Limpar cache e cookies

WebView2 APIs

O WebView2Browser utiliza algumas das APIs disponíveis no WebView2. Para as APIs não utilizadas aqui, pode encontrar mais informações sobre as mesmas na Referência webView2 do Microsoft Edge. Segue-se uma lista das APIs mais interessantes que o WebView2Browser utiliza e as funcionalidades que ativam.

API Recursos
CreateCoreWebView2EnvironmentWithOptions Utilizado para criar ambientes para IU e WebViews de conteúdo. São transmitidos diferentes diretórios de dados de utilizador para isolar a IU do conteúdo Web.
ICoreWebView2 Existem vários WebViews no WebView2Browser e a maioria das funcionalidades utiliza os membros nesta interface. A tabela abaixo mostra como são utilizados.
ICoreWebView2DevToolsProtocolEventReceivedEventHandler Utilizado juntamente com add_DevToolsProtocolEventReceived para escutar eventos de segurança CDP para atualizar o ícone de bloqueio na IU do browser.
ICoreWebView2DevToolsProtocolEventReceiver Utilizado juntamente com add_DevToolsProtocolEventReceived para escutar eventos de segurança CDP para atualizar o ícone de bloqueio na IU do browser.
ICoreWebView2ExecuteScriptCompletedHandler Utilizado juntamente com ExecuteScript para obter o título e favicon da página visitada.
ICoreWebView2FocusChangedEventHandler Utilizado juntamente com add_LostFocus para ocultar a lista pendente de opções do browser quando perde o foco.
ICoreWebView2HistoryChangedEventHandler Utilizado juntamente com add_HistoryChanged para atualizar os botões de navegação na IU do browser.
ICoreWebView2Controller Existem vários WebViewControllers no WebView2Browser e obtemos os WebViews associados dos mesmos.
ICoreWebView2NavigationCompletedEventHandler Utilizado juntamente com add_NavigationCompleted para atualizar o botão de recarregamento na IU do browser.
ICoreWebView2Settings Utilizado para desativar DevTools na IU do browser.
ICoreWebView2SourceChangedEventHandler Utilizado juntamente com add_SourceChanged para atualizar a barra de endereço na IU do browser.
ICoreWebView2WebMessageReceivedEventHandler Esta é uma das APIs mais importantes para WebView2Browser. A maioria das funcionalidades que envolvem a comunicação entre WebViews utilizam isto.
ICoreWebView2 API Recursos
add_NavigationStarting Utilizado para apresentar o botão de navegação cancelar nos controlos WebView.
add_SourceChanged Utilizado para atualizar a barra de endereço.
add_HistoryChanged Utilizado para atualizar botões retroceder/avançar.
add_NavigationCompleted Utilizado para apresentar o botão de recarregamento assim que uma navegação for concluída.
ExecuteScript Utilizado para obter o título e favicon de uma página visitada.
PostWebMessageAsJson Utilizado para comunicar WebViews. Todas as mensagens utilizam JSON para transmitir os parâmetros necessários.
add_WebMessageReceived Utilizado para processar mensagens Web publicadas no WebView.
CallDevToolsProtocolMethod Utilizado para ativar a escuta de eventos de segurança, que notificarão de segurança status alterações num documento.
ICoreWebView2Controller API Funcionalidade(s)
get_CoreWebView2 Utilizado para associar o CoreWebView2 a este CoreWebView2Controller.
add_LostFocus Utilizado para ocultar a lista pendente de opções quando o utilizador clica fora da mesma.

Implementar as funcionalidades

As secções abaixo descrevem como algumas das funcionalidades no WebView2Browser foram implementadas. Pode ver o código fonte para obter mais detalhes sobre como tudo funciona aqui. Destaque:

Noções básicas

Configurar o ambiente, criar um WebView

O WebView2 permite-lhe alojar conteúdo Web na sua aplicação Windows. Expõe os globais CreateCoreWebView2Environment e CreateCoreWebView2EnvironmentWithOptions a partir dos quais podemos criar os dois ambientes separados para a IU e o conteúdo do browser.

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

Utilizamos o ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler para criar as WebViews da IU 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 a configurar algumas coisas aqui. A interface ICoreWebView2Settings é utilizada para desativar DevTools no WebView que alimenta os controlos do browser. Também estamos a adicionar um processador para mensagens Web recebidas. Este processador irá permitir-nos fazer algo quando o utilizador interagir com os controlos neste WebView.

Pode navegar para uma página Web ao introduzir o respetivo URI na barra de endereço. Ao premir Enter, os controlos WebView irão publicar uma mensagem Web na aplicação anfitriã para que possa navegar no separador ativo para a localização especificada. O código abaixo mostra como a aplicação Win32 do anfitrião irá processar 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 irá marcar o URI em páginas do browser (ou seja, favoritos, definições, histórico) e navegará para a localização pedida ou utilizará o URI fornecido para procurar no Bing como contingência.

Atualizar a barra de endereço

A barra de endereço é atualizada sempre que existe uma alteração na origem do documento do separador ativo e juntamente com outros controlos ao mudar de separador. Cada WebView irá acionar um evento quando o estado do documento for alterado, podemos utilizar este evento para obter a nova origem em atualizações e reencaminhar a alteração para os controlos WebView (também atualizaremos os botões retroceder 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;
}

Enviámos a MG_UPDATE_URI mensagem juntamente com o URI para os controlos WebView. Agora, queremos refletir essas alterações no estado do separador e atualizar a IU, 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;

Para trás, para a frente

Cada WebView manterá um histórico para as navegação que executou, pelo que só precisamos de ligar a IU do browser aos métodos correspondentes. Se o WebView do separador ativo puder ser navegado para trás/para a frente, os botões irão publicar uma mensagem Web na aplicação anfitriã quando clicado.

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 da aplicação anfitriã:

        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

Utilizamos o evento acionado NavigationStarting por um WebView de conteúdo para atualizar o estado de carregamento do separador associado nos controlos WebView. Da mesma forma, quando um WebView aciona o NavigationCompleted evento, utilizamos esse evento para instruir os controlos WebView a atualizar o estado do separador. O estado do separador ativo nos controlos WebView determinará se pretende mostrar o recarregamento ou o botão cancelar. Cada um deles irá publicar uma mensagem na aplicação anfitriã quando clicada, para que o WebView desse separador possa ser recarregado ou ter a navegação cancelada, em conformidade.

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

Algumas funcionalidades interessantes

Comunicar as WebViews

Precisamos de comunicar os WebViews que alimentam os separadores e a IU, para que as interações do utilizador na WebView de um separador tenham o efeito desejado na outra WebView. O WebView2Browser utiliza um conjunto de APIs WebView2 muito úteis para este fim, incluindo PostWebMessageAsJson, add_WebMessageReceived e ICoreWebView2WebMessageReceivedEventHandler.

Do lado do JavaScript, estamos a utilizar o window.chrome.webview objeto exposto para chamar o postMessage método e adicionar uma lista 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);
}

Processamento de separadores

Será criado um novo separador sempre que o utilizador clicar no novo botão de separador à direita dos separadores abertos. O WebView do controlo publicará uma mensagem na aplicação anfitriã para criar o WebView desse separador e criar um objeto que controla o 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 da aplicação anfitriã, o ICoreWebView2WebMessageReceivedEventHandler registado irá capturar a mensagem e criar a WebView para esse separador.

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

O separador regista todos os processadores para que possa reencaminhar atualizações para os controlos WebView quando os eventos são acionados. O separador está pronto e será apresentado na área de conteúdo do browser. Ao clicar num separador nos controlos, o WebView irá publicar uma mensagem na aplicação anfitriã, que por sua vez irá ocultar a WebView do separador anteriormente ativo e mostrar a do separador clicado.

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

A atualizar o ícone de segurança

Utilizamos CallDevToolsProtocolMethod para ativar a escuta de eventos de segurança. Sempre que um securityStateChanged evento for acionado, utilizaremos o novo estado para atualizar o ícone de segurança no WebView dos controlos.

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

Preencher o histórico

O WebView2Browser utiliza o IndexedDB nos controlos WebView para armazenar itens de histórico, apenas um exemplo de como o WebView2 lhe permite aceder a tecnologias Web padrão como faria no browser. O item para uma navegação será criado assim que o URI for atualizado. Estes itens são obtidos pela IU do histórico num separador que utiliza window.chrome.postMessageo .

Neste caso, a maioria das funcionalidades é implementada através do JavaScript em ambas as extremidades (controla o WebView e o conteúdo WebView a carregar a IU), pelo que a aplicação anfitriã só está a agir como um mediador 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);
                    }
                };
            }
        };

    });
}

Processar JSON e URIs

O WebView2Browser utiliza o cpprestsdk (Casablanca) da Microsoft para processar todos os JSON no lado C++. O IUri e o CreateUri também são utilizados para analisar caminhos de ficheiros em URIs e também podem ser utilizados para outros URIs.

Confira também