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
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
- Instale o Visual Studio, incluindo o suporte C++.
Passo 2: Clonar o repositório WebView2Samples
- Clone (ou transfira como
.zip
) o repositório WebView2Samples . Veja Clonar o repositório WebView2Samples em Configurar o ambiente Dev para WebView2.
Passo 3: abrir a solução no Visual Studio
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.
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.
- Se quiser criar e executar o browser em versões do Windows antes de Windows 10: Em
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 quiser criar e executar o browser 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);
}
Passo 4: criar e executar a aplicação
Defina o destino que pretende criar (como Depuração ou Versão, filtrando x86 ou x64).
Crie a solução.
Execute (ou Depurar) a aplicação.
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.
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.
Navegar para a página Web
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.postMessage
o .
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.