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
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
- 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
- Clonar (ou baixar como
.zip
) o repositório WebView2Samples . Consulte Clonar o repositório WebView2Samples em Configurar seu ambiente de desenvolvimento para WebView2.
Etapa 4: Abrir a solução no Visual Studio
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.
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.
- Se você quiser criar e executar o navegador em versões do Windows antes de Windows 10: In
WebViewBrowserApp.cpp
, altereSetProcessDpiAwarenessContext
paraSetProcessDPIAware
:
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);
// ...
- 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 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 5: Criar e executar o aplicativo
Defina o destino que você deseja criar (como Depuração ou Versão, direcionando x86 ou x64).
Crie a solução.
Execute (ou Depurar) o aplicativo.
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.
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.
Navegar até a página da Web
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.