Exemple Win32 WebView2Browser
Cet exemple, WebView2Browser, est un navigateur web créé avec le contrôle Microsoft Edge WebView2 .
Cet exemple a son propre dépôt dédié.
- Exemple de nom : WebView2Browser
- Dépôt : WebView2Browser
- Fichier de solution : WebViewBrowserApp.sln
WebView2Browser est un exemple d’application de bureau Windows illustrant les fonctionnalités du contrôle WebView2. L’exemple d’application WebView2Browser utilise plusieurs instances WebView2.
Cet exemple est généré en tant que projet Visual Studio 2019 Win32. Il utilise C++ et JavaScript dans l’environnement WebView2.
WebView2Browser présente certaines des utilisations les plus simples de WebView2, telles que la création et la navigation d’un WebView, mais également des flux de travail plus complexes comme l’utilisation de l’API PostWebMessageAsJson pour communiquer entre les contrôles WebView2 dans des environnements distincts. Il s’agit d’un exemple de code enrichi pour montrer comment vous pouvez utiliser les API WebView2 pour créer votre propre application.
Étape 1 : Installer Visual Studio
- Installez Visual Studio, y compris la prise en charge de C++.
Étape 2 : Cloner le référentiel WebView2Samples
- Clonez (ou téléchargez en tant que
.zip
) le référentiel WebView2Samples . Consultez Cloner le référentiel WebView2Samples dans Configurer votre environnement de développement pour WebView2.
Étape 3 : Ouvrir la solution dans Visual Studio
Ouvrez la solution dans Visual Studio 2019. Le Kit de développement logiciel (SDK) WebView2 est déjà inclus en tant que package NuGet dans le projet. Si vous souhaitez utiliser Visual Studio 2017, modifiez l’ensemble d’outils de plateforme du projet dans Propriétés > du projet Propriétés de configuration Ensemble d’outils > de plateforme générale>. Vous devrez peut-être également modifier le SDK Windows vers la dernière version.
Apportez les modifications répertoriées ci-dessous, si vous utilisez une version de Windows ci-dessous Windows 10.
Utilisation des versions ci-dessous Windows 10
Si vous souhaitez générer et exécuter le navigateur dans des versions de Windows avant Windows 10, apportez les modifications suivantes. Cela est nécessaire en raison de la façon dont le ppp est géré dans Windows 10 versions antérieures de Windows.
- Si vous souhaitez générer et exécuter le navigateur dans les versions de Windows avant Windows 10 : Dans
WebViewBrowserApp.cpp
, remplacez parSetProcessDpiAwarenessContext
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);
// ...
- Si vous souhaitez générer et exécuter le navigateur dans les versions de Windows avant Windows 10 : Dans
BrowserWindow.cpp
, supprimez ou commentez l’appel suivant à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);
}
Étape 4 : Générer et exécuter l’application
Définissez la cible que vous souhaitez générer (par exemple, Debug ou Release, ciblant x86 ou x64).
Générez la solution.
Exécutez (ou déboguez) l’application.
Fermez l’application.
Étape 5 : Mettre à jour le Kit de développement logiciel (SDK) WebView2
- Mettez à jour la version du Kit de développement logiciel (SDK) WebView2 dans Visual Studio. Pour ce faire, cliquez avec le bouton droit sur le projet, puis cliquez sur Gérer les packages NuGet.
Étape 6 : Générer et exécuter l’application avec le Kit de développement logiciel (SDK) WebView2 mis à jour
- Générez et réexécutez l’application.
Disposition du navigateur
L’exemple d’application WebView2Browser utilise plusieurs instances WebView2.
WebView2Browser a une approche multi-WebView pour intégrer le contenu web et l’interface utilisateur d’application dans une application de bureau Windows. Cela permet au navigateur d’utiliser des technologies web standard (HTML, CSS, JavaScript) pour éclairer l’interface, mais permet également à l’application d’extraire des favicons à partir du web et d’utiliser IndexedDB pour stocker les favoris et l’historique.
L’approche multi-WebView implique l’utilisation de deux environnements WebView distincts (chacun avec son propre répertoire de données utilisateur) : l’un pour les vues WebView de l’interface utilisateur et l’autre pour tous les webviews de contenu. Les vues web de l’interface utilisateur (liste déroulante des contrôles et des options) utilisent l’environnement d’interface utilisateur, tandis que les webviews de contenu web (un par onglet) utilisent l’environnement de contenu.
Fonctionnalités
L’exemple WebView2Browser fournit toutes les fonctionnalités permettant de créer un navigateur web de base, mais vous avez beaucoup de place pour jouer.
L’exemple WebView2Browser implémente les fonctionnalités suivantes :
- Retour/transfert
- Recharger la page
- Annuler la navigation
- Plusieurs onglets
- Historique
- Favoris
- Rechercher à partir de la barre d’adresses
- Status de sécurité des pages
- Effacement du cache et des cookies
API WebView2
WebView2Browser utilise quelques-unes des API disponibles dans WebView2. Pour les API non utilisées ici, vous trouverez plus d’informations à leur sujet dans la référence Microsoft Edge WebView2. Voici une liste des API les plus intéressantes utilisées par WebView2Browser et des fonctionnalités qu’elles activent.
API | Fonctionnalités |
---|---|
CreateCoreWebView2EnvironmentWithOptions |
Permet de créer les environnements pour l’interface utilisateur et les WebViews de contenu. Différents répertoires de données utilisateur sont passés pour isoler l’interface utilisateur du contenu web. |
ICoreWebView2 |
Il existe plusieurs WebViews dans WebView2Browser et la plupart des fonctionnalités utilisent des membres dans cette interface. Le tableau ci-dessous montre comment ils sont utilisés. |
ICoreWebView2DevToolsProtocolEventReceivedEventHandler |
Utilisé avec add_DevToolsProtocolEventReceived pour écouter les événements de sécurité CDP afin de mettre à jour l’icône de verrou dans l’interface utilisateur du navigateur. |
ICoreWebView2DevToolsProtocolEventReceiver |
Utilisé avec add_DevToolsProtocolEventReceived pour écouter les événements de sécurité CDP afin de mettre à jour l’icône de verrou dans l’interface utilisateur du navigateur. |
ICoreWebView2ExecuteScriptCompletedHandler |
Utilisé avec ExecuteScript pour obtenir le titre et le favicon à partir de la page visitée. |
ICoreWebView2FocusChangedEventHandler |
Utilisé avec add_LostFocus pour masquer la liste déroulante des options du navigateur lorsqu’elle perd le focus. |
ICoreWebView2HistoryChangedEventHandler |
Utilisé avec add_HistoryChanged pour mettre à jour les boutons de navigation dans l’interface utilisateur du navigateur. |
ICoreWebView2Controller |
Il existe plusieurs WebViewControllers dans WebView2Browser et nous récupérons les WebViews associés à partir d’eux. |
ICoreWebView2NavigationCompletedEventHandler |
Utilisé avec add_NavigationCompleted pour mettre à jour le bouton de rechargement dans l’interface utilisateur du navigateur. |
ICoreWebView2Settings |
Permet de désactiver DevTools dans l’interface utilisateur du navigateur. |
ICoreWebView2SourceChangedEventHandler |
Utilisé avec add_SourceChanged pour mettre à jour la barre d’adresse dans l’interface utilisateur du navigateur. |
ICoreWebView2WebMessageReceivedEventHandler |
Il s’agit de l’une des API les plus importantes pour WebView2Browser. La plupart des fonctionnalités impliquant la communication entre les WebViews utilisent cette fonctionnalité. |
ICoreWebView2 API | Fonctionnalités |
---|---|
add_NavigationStarting |
Permet d’afficher le bouton Annuler la navigation dans les contrôles WebView. |
add_SourceChanged |
Utilisé pour mettre à jour la barre d’adresse. |
add_HistoryChanged |
Permet de mettre à jour les boutons Précédent/Précédent. |
add_NavigationCompleted |
Permet d’afficher le bouton recharger une fois la navigation terminée. |
ExecuteScript |
Permet d’obtenir le titre et le favicon d’une page visitée. |
PostWebMessageAsJson |
Utilisé pour communiquer des WebViews. Tous les messages utilisent JSON pour passer les paramètres nécessaires. |
add_WebMessageReceived |
Utilisé pour gérer les messages web publiés sur webView. |
CallDevToolsProtocolMethod |
Permet d’activer l’écoute des événements de sécurité, qui notifie les modifications apportées à la sécurité status dans un document. |
ICoreWebView2Controller API | Fonctionnalité(s) |
---|---|
get_CoreWebView2 |
Permet d’obtenir le CoreWebView2 associé à ce CoreWebView2Controller. |
add_LostFocus |
Permet de masquer la liste déroulante des options lorsque l’utilisateur clique dessus. |
Implémentation des fonctionnalités
Les sections ci-dessous décrivent comment certaines des fonctionnalités de WebView2Browser ont été implémentées. Vous pouvez consulter le code source pour plus d’informations sur la façon dont tout fonctionne ici. Contour:
Principes de base
Configurer l’environnement, créer un WebView
WebView2 vous permet d’héberger du contenu web dans votre application Windows. Il expose les éléments globaux CreateCoreWebView2Environment et CreateCoreWebView2EnvironmentWithOptions à partir desquels nous pouvons créer les deux environnements distincts pour l’interface utilisateur et le contenu du navigateur.
// 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());
}
Nous utilisons ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler pour créer les WebViews d’interface utilisateur une fois que l’environnement est prêt.
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());
}
Nous mettons en place quelques éléments ici. L’interface ICoreWebView2Settings est utilisée pour désactiver DevTools dans le WebView qui alimente les contrôles du navigateur. Nous ajoutons également un gestionnaire pour les messages web reçus. Ce gestionnaire nous permet d’effectuer une action lorsque l’utilisateur interagit avec les contrôles de ce WebView.
Accéder à la page web
Vous pouvez accéder à une page web en entrant son URI dans la barre d’adresses. Lorsque vous appuyez sur Entrée, les contrôles WebView publient un message web sur l’application hôte afin qu’elle puisse naviguer dans l’onglet actif jusqu’à l’emplacement spécifié. Le code ci-dessous montre comment l’application Win32 hôte gère ce message.
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;
WebView2Browser case activée l’URI sur les pages du navigateur (par exemple, favoris, paramètres, historique) et accède à l’emplacement demandé ou utilise l’URI fourni pour rechercher Bing comme secours.
Mise à jour de la barre d’adresses
La barre d’adresses est mise à jour chaque fois qu’une modification est apportée à la source du document de l’onglet actif et aux autres contrôles lors du changement d’onglet. Chaque WebView déclenche un événement lorsque l’état du document change. Nous pouvons utiliser cet événement pour obtenir la nouvelle source sur les mises à jour et transférer la modification aux contrôles WebView (nous allons également mettre à jour les boutons Revenir en arrière et avancer).
// 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;
}
Nous avons envoyé le MG_UPDATE_URI
message avec l’URI aux contrôles WebView. Nous voulons maintenant refléter ces modifications sur l’état de l’onglet et mettre à jour l’interface utilisateur si nécessaire.
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;
Revenir en arrière, aller de l’avant
Chaque WebView conserve un historique pour les navigations qu’il a effectuées. Nous n’avons donc besoin de connecter l’interface utilisateur du navigateur qu’avec les méthodes correspondantes. Si le WebView de l’onglet actif peut être parcouru vers l’arrière/vers l’avant, les boutons publient un message web sur l’application hôte lorsque vous cliquez dessus.
Côté 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);
}
});
Côté application hôte :
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;
Rechargement, arrêt de la navigation
Nous utilisons l’événement NavigationStarting
déclenché par un webView de contenu pour mettre à jour l’état de chargement de l’onglet associé dans les contrôles WebView. De même, lorsqu’un WebView déclenche l’événement NavigationCompleted
, nous utilisons cet événement pour indiquer aux contrôles WebView de mettre à jour l’état de la tabulation. L’état de l’onglet actif dans les contrôles WebView détermine s’il faut afficher le rechargement ou le bouton Annuler. Chacun d’entre eux publie un message dans l’application hôte lorsqu’on clique dessus, afin que le WebView de cet onglet puisse être rechargé ou que sa navigation soit annulée, en conséquence.
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"");
}
Quelques caractéristiques intéressantes
Communication des WebViews
Nous devons communiquer les WebViews qui alimentent les onglets et l’interface utilisateur, afin que les interactions utilisateur dans le WebView d’un onglet aient l’effet souhaité dans l’autre WebView. WebView2Browser utilise un ensemble d’API WebView2 très utiles à cet effet, notamment PostWebMessageAsJson, add_WebMessageReceived et ICoreWebView2WebMessageReceivedEventHandler.
Côté JavaScript, nous utilisons l’objet exposé pour appeler la window.chrome.webview
postMessage
méthode et ajouter un listeur d’événements pour les messages reçus.
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);
}
Gestion des onglets
Un nouvel onglet est créé chaque fois que l’utilisateur clique sur le bouton Nouvel onglet situé à droite des onglets ouverts. Le WebView du contrôle publie un message à l’application hôte pour créer le WebView pour cet onglet et créer un objet qui suit son état.
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);
}
}
Côté application hôte, le ICoreWebView2WebMessageReceivedEventHandler inscrit intercepte le message et crée le WebView pour cet onglet.
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());
}
L’onglet inscrit tous les gestionnaires afin qu’il puisse transférer les mises à jour aux contrôles WebView lorsque des événements se déclenchent. L’onglet est prêt et s’affiche dans la zone de contenu du navigateur. Cliquez sur un onglet dans les contrôles WebView pour publier un message à l’application hôte, qui à son tour masque l’affichage web de l’onglet précédemment actif et affiche celui de l’onglet cliqué.
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;
}
Mise à jour de l’icône de sécurité
Nous utilisons CallDevToolsProtocolMethod pour activer l’écoute des événements de sécurité. Chaque fois qu’un securityStateChanged
événement est déclenché, nous utilisons le nouvel état pour mettre à jour l’icône de sécurité sur les contrôles 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;
Remplissage de l’historique
WebView2Browser utilise IndexedDB dans les contrôles WebView pour stocker les éléments d’historique, juste un exemple de la façon dont WebView2 vous permet d’accéder aux technologies web standard comme vous le feriez dans le navigateur. L’élément d’une navigation est créé dès que l’URI est mis à jour. Ces éléments sont ensuite récupérés par l’interface utilisateur de l’historique dans un onglet qui utilise window.chrome.postMessage
.
Dans ce cas, la plupart des fonctionnalités sont implémentées à l’aide de JavaScript aux deux extrémités (contrôle WebView et contenu WebView chargent l’interface utilisateur), de sorte que l’application hôte agit uniquement comme un répartiteur de messages pour communiquer ces fins.
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);
}
};
}
};
});
}
Gestion de JSON et d’URI
WebView2Browser utilise cpprestsdk (Casablanca) de Microsoft pour gérer tous les fichiers JSON du côté C++. IUri et CreateUri sont également utilisés pour analyser les chemins d’accès aux fichiers dans des URI et peuvent également être utilisés pour d’autres URI.