Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Dieses Beispiel, WebView2Browser, ist ein Webbrowser, der mit dem Microsoft Edge WebView2-Steuerelement erstellt wurde.
Für dieses Beispiel gibt es ein eigenes dediziertes Repository.
- Beispielname: WebView2Browser
- Repository: WebView2Browser
- Lösungsdatei:
WebViewBrowserApp.sln
WebView2Browser ist eine Windows-Beispieldesktopanwendung, die die Funktionen des WebView2-Steuerelements veranschaulicht. Die WebView2Browser-Beispiel-App verwendet mehrere WebView2-Instanzen.
Dieses Beispiel wurde als Win32 Visual Studio 2019-Projekt erstellt. Es verwendet C++ und JavaScript in der WebView2-Umgebung.
WebView2Browser zeigt einige der einfachsten Verwendungsmöglichkeiten von WebView2, z. B. das Erstellen und Navigieren in einer WebView, aber auch einige komplexere Workflows wie die Verwendung der PostWebMessageAsJson-API zum Kommunizieren zwischen WebView2-Steuerelementen in separaten Umgebungen. Dies ist ein umfangreiches Codebeispiel, um zu veranschaulichen, wie Sie WebView2-APIs verwenden können, um Ihre eigene App zu erstellen.
Schritt 1: Installieren von Visual Studio
- Installieren Sie Visual Studio einschließlich C++-Unterstützung.
Schritt 2: Klonen des WebView2Samples-Repositorys
- Klonen Sie das WebView2Samples-Repository (oder laden Sie es als
.zipherunter). Weitere Informationen finden Sie unter Klonen des WebView2Samples-Repositorys in Einrichten Ihrer Entwicklungsumgebung für WebView2.
Schritt 3: Öffnen der Projektmappe in Visual Studio
Öffnen Sie die Projektmappe in Visual Studio 2019. Das WebView2-SDK ist bereits als NuGet-Paket im Projekt enthalten. Wenn Sie Visual Studio 2017 verwenden möchten, ändern Sie das Plattformtoolset des Projekts in den Projekteigenschaften > Konfigurationseigenschaften > Allgemeines > Plattform-Toolset. Möglicherweise müssen Sie auch das Windows SDK auf die neueste Version ändern.
Nehmen Sie die unten aufgeführten Änderungen vor, wenn Sie eine Windows-Version unter Windows 10 verwenden.
Verwenden von Versionen unter Windows 10
Wenn Sie den Browser in älteren Windows-Versionen als Windows 10 erstellen und ausführen möchten, nehmen Sie die folgenden Änderungen vor. Dies ist erforderlich, weil DPI in Windows 10 im Vergleich zu früheren Versionen von Windows behandelt wird.
- Wenn Sie den Browser in älteren Windows-Versionen als Windows 10 erstellen und ausführen möchten: In
WebViewBrowserApp.cppändern SieSetProcessDpiAwarenessContextzuSetProcessDPIAware:
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);
// ...
- Wenn Sie den Browser in älteren Windows-Versionen als Windows 10 erstellen und ausführen möchten: In
BrowserWindow.cpp, entfernen oder kommentieren Sie den folgenden Aufruf ausGetDpiForWindow:
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);
}
Schritt 4: Erstellen und Ausführen der App
Legen Sie das Ziel fest, das Sie erstellen möchten (z. B. Debuggen oder Release mit x86 oder x64).
Erstellen Sie die Projektmappe.
Führen Sie die App aus (oder debuggen).
Schließen Sie die App.
Schritt 5: Aktualisieren des WebView2-SDKs
- Aktualisieren Sie die Version des WebView2-SDK in Visual Studio. Klicken Sie dazu mit der rechten Maustaste auf das Projekt, und klicken Sie dann auf NuGet-Pakete verwalten.
Schritt 6: Erstellen und Ausführen der App mit dem aktualisierten WebView2-SDK
- Erstellen Sie die App, und führen Sie sie erneut aus.
Browserlayout
Die WebView2Browser-Beispiel-App verwendet mehrere WebView2-Instanzen.
WebView2Browser verfügt über einen Multi-WebView-Ansatz, um Webinhalte und Anwendungsbenutzeroberflächen in eine Windows-Desktopanwendung zu integrieren. Dies ermöglicht dem Browser die Verwendung von Standard-Webtechnologien (HTML, CSS, JavaScript), um die Benutzeroberfläche zu beleuchten, ermöglicht es der App aber auch, Favicons aus dem Web abzurufen und IndexedDB zum Speichern von Favoriten und Verlauf zu verwenden.
Der Multi-WebView-Ansatz umfasst die Verwendung von zwei separaten WebView-Umgebungen (jede mit einem eigenen Benutzerdatenverzeichnis): eine für die Benutzeroberfläche WebViews und die andere für alle WebViews-Inhalte. UI-WebViews (Dropdownliste für Steuerelemente und Optionen) verwenden die Benutzeroberflächenumgebung, während Webinhalts-WebViews (eine pro Registerkarte) die Inhaltsumgebung verwenden.
Features
Das WebView2Browser-Beispiel bietet alle Funktionen, um einen einfachen Webbrowser zu erstellen, aber es gibt auch viel Platz für Sie, um herumzuspielen.
Im WebView2Browser-Beispiel werden die folgenden Features implementiert:
- Zurück/Vorwärts
- Seite neu laden
- Navigation abbrechen
- Mehrere Registerkarten
- Verlauf
- Favoriten
- Suchen über die Adressleiste
- Status der Seitensicherheit
- Löschen von Cache und Cookies
WebView2-APIs
WebView2Browser verwendet eine Handvoll der in WebView2 verfügbaren APIs. Für die hier nicht verwendeten APIs finden Sie weitere Informationen in der Microsoft Edge WebView2-Referenz. Im Folgenden finden Sie eine Liste der interessantesten APIs, die WebView2Browser verwendet, und der Features, die sie ermöglichen.
| API | Features |
|---|---|
CreateCoreWebView2EnvironmentWithOptions |
Wird zum Erstellen der Umgebungen für Benutzeroberflächen- und Inhalts-WebViews verwendet. Verschiedene Benutzerdatenverzeichnisse werden übergeben, um die Benutzeroberfläche von Webinhalten zu isolieren. |
ICoreWebView2 |
Es gibt mehrere WebViews in WebView2Browser und die meisten Funktionen verwenden Mitglieder in dieser Schnittstelle, die folgende Tabelle zeigt, wie sie verwendet werden. |
ICoreWebView2DevToolsProtocolEventReceivedEventHandler |
Wird zusammen mit add_DevToolsProtocolEventReceived verwendet, um auf CDP-Sicherheitsereignisse zu lauschen, um das Schlosssymbol in der Browser-Benutzeroberfläche zu aktualisieren. |
ICoreWebView2DevToolsProtocolEventReceiver |
Wird zusammen mit add_DevToolsProtocolEventReceived verwendet, um auf CDP-Sicherheitsereignisse zu lauschen, um das Schlosssymbol in der Browser-Benutzeroberfläche zu aktualisieren. |
ICoreWebView2ExecuteScriptCompletedHandler |
Wird zusammen mit ExecuteScript verwendet, um den Titel und das Favicon der besuchten Seite abzurufen. |
ICoreWebView2FocusChangedEventHandler |
Wird zusammen mit add_LostFocus verwendet, um die Dropdownliste mit den Browseroptionen auszublenden, wenn sie den Fokus verliert. |
ICoreWebView2HistoryChangedEventHandler |
Wird zusammen mit add_HistoryChanged verwendet, um die Navigationsschaltflächen in der Browser-Benutzeroberfläche zu aktualisieren. |
ICoreWebView2Controller |
Es gibt mehrere WebViewController in WebView2Browser und wir holen die zugehörigen WebViews von ihnen. |
ICoreWebView2NavigationCompletedEventHandler |
Wird zusammen mit add_NavigationCompleted verwendet, um die Schaltfläche "Neu laden" in der Browser-Benutzeroberfläche zu aktualisieren. |
ICoreWebView2Settings |
Verwendet zum Deaktivieren von DevTools in der Browserbenutzeroberfläche. |
ICoreWebView2SourceChangedEventHandler |
Wird zusammen mit add_SourceChanged zum Aktualisieren der Adressleiste in der Browser-Benutzeroberfläche verwendet. |
ICoreWebView2WebMessageReceivedEventHandler |
Dies ist eine der wichtigsten APIs für WebView2Browser. Die meisten Funktionen, die die Kommunikation über WebViews beinhalten, verwenden dies. |
ICoreWebView2-API |
Features |
|---|---|
add_NavigationStarting |
Wird verwendet, um die Schaltfläche "Navigation abbrechen" in den Steuerelementen WebView anzuzeigen. |
add_SourceChanged |
Verwendet zum Aktualisieren der Adressleiste. |
add_HistoryChanged |
Verwendet zum Aktualisieren der Zurück-/Vorwärts-Schaltflächen. |
add_NavigationCompleted |
Wird verwendet, um die Schaltfläche "Neu laden" anzuzeigen, sobald eine Navigation abgeschlossen ist. |
ExecuteScript |
Wird verwendet, um den Titel und das Favicon einer besuchten Seite abzurufen. |
PostWebMessageAsJson |
Verwendet zum Kommunizieren von WebViews. Alle Nachrichten verwenden JSON, um die erforderlichen Parameter zu übergeben. |
add_WebMessageReceived |
Verwendet für die Verarbeitung von Webnachrichten, die in WebView gepostet werden. |
CallDevToolsProtocolMethod |
Wird verwendet, um das Lauschen auf Sicherheitsereignisse zu aktivieren, die über Sicherheits- status Änderungen in einem Dokument benachrichtigen. |
ICoreWebView2Controller-API |
Features |
|---|---|
get_CoreWebView2 |
Wird verwendet, um den CoreWebView2 abzurufen, der diesem .CoreWebView2Controller |
add_LostFocus |
Wird verwendet, um die Dropdownliste "Optionen" auszublenden, wenn der Benutzer darauf klickt. |
Implementieren der Features
In den folgenden Abschnitten wird beschrieben, wie einige der Features in WebView2Browser implementiert wurden. Im Quellcode findest du weitere Details dazu, wie alles funktioniert. Gliederung:
Die Grundlagen
Richten Sie die Umgebung ein, erstellen Sie eine WebView
Mit WebView2 können Sie Webinhalte in Ihrer Windows-App hosten. Es macht die globalen CreateCoreWebView2Environment und CreateCoreWebView2EnvironmentWithOptions verfügbar, von denen aus wir die beiden separaten Umgebungen für die Benutzeroberfläche und den Inhalt des Browsers erstellen können.
// 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());
}
Wir verwenden den ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler , um die Benutzeroberfläche WebViews zu erstellen, sobald die Umgebung bereit ist.
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());
}
Wir richten hier ein paar Dinge ein. Die ICoreWebView2Settings-Schnittstelle wird verwendet, um DevTools in der WebView zu deaktivieren, die die Browsersteuerelemente unterstützen. Wir fügen auch einen Handler für empfangene Webnachrichten hinzu. Mit diesem Handler können wir etwas tun, wenn der Benutzer mit den Steuerelementen in dieser WebView interagiert.
Zur Webseite navigieren
Sie können zu einer Webseite navigieren, indem Sie deren URI in die Adressleiste eingeben. Wenn Sie die EINGABETASTE drücken, sendet die Steuerelement-WebAnsicht eine Webnachricht an die Host-App, damit diese auf der aktiven Registerkarte zur angegebenen Position navigieren kann. Der folgende Code zeigt, wie die Win32-Hostanwendung diese Meldung behandelt.
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 überprüft den URI anhand von Browserseiten (d. h. Favoriten, Einstellungen, Verlauf) und navigiert zum angeforderten Speicherort oder verwendet den bereitgestellten URI für die Suche in Bing als Fallback.
Aktualisieren der Adressleiste
Die Adressleiste wird jedes Mal aktualisiert, wenn die Dokumentquelle der aktiven Registerkarte geändert wird, sowie zusammen mit anderen Steuerelementen beim Wechseln der Registerkarten. Jede WebView löst ein Ereignis aus, wenn sich der Status des Dokuments ändert. Wir können dieses Ereignis verwenden, um die neue Quelle für Updates abzurufen und die Änderung an die Steuerelemente WebView weiterzuleiten (wir aktualisieren auch die Schaltflächen "Zurück" und "Vorwärts").
// 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;
}
Wir haben die MG_UPDATE_URI Nachricht zusammen mit der URI an die Steuerelemente WebView gesendet. Jetzt wollen wir diese Änderungen im Registerkartenstatus widerspiegeln und die Benutzeroberfläche bei Bedarf aktualisieren.
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;
Rückwärts, vorwärts
Jede WebView speichert einen Verlauf für die von ihr durchgeführten Navigationen, sodass wir nur die Browser-Benutzeroberfläche mit den entsprechenden Methoden verbinden müssen. Wenn die WebAnsicht der aktiven Registerkarte rückwärts/vorwärts navigiert werden kann, senden die Schaltflächen beim Klicken eine Webnachricht an die Hostanwendung.
Die JavaScript-Seite:
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);
}
});
Die Hostanwendungsseite:
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;
Neuladen, Navigation beenden
Wir verwenden das Ereignis, das NavigationStarting von einer Inhalts-WebView ausgelöst wird, um den zugehörigen Registerkartenladestatus in den Steuerelementen WebView zu aktualisieren. Wenn eine WebView das NavigationCompleted Ereignis auslöst, verwenden wir dieses Ereignis, um die Steuerelemente WebView anzuweisen, den Registerkartenstatus zu aktualisieren. Der Status der aktiven Registerkarte in der Steuerelemente WebView bestimmt, ob die Schaltfläche "Neu laden" oder "Abbrechen" angezeigt wird. Jedes dieser Elemente sendet beim Klicken eine Nachricht an die Hostanwendung zurück, sodass die WebView für diese Registerkarte neu geladen werden kann oder die Navigation entsprechend abgebrochen wird.
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"");
}
Einige interessante Funktionen
Kommunizieren der WebViews
Wir müssen die WebViews kommunizieren, die die Registerkarten und die Benutzeroberfläche unterstützen, damit Benutzerinteraktionen in der WebView eines Tabs den gewünschten Effekt in der anderen WebView haben. WebView2Browser verwendet zu diesem Zweck eine Reihe sehr nützlicher WebView2-APIs, darunter PostWebMessageAsJson, add_WebMessageReceived und ICoreWebView2WebMessageReceivedEventHandler.
Auf der JavaScript-Seite verwenden wir das window.chrome.webview exponierte Objekt, um die postMessage Methode aufzurufen und eine Ereignisliste für empfangene Nachrichten hinzuzufügen.
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);
}
Handhabung von Registerkarten
Immer wenn der Benutzer auf die Schaltfläche "Neue Registerkarte" rechts neben den geöffneten Registerkarten klickt, wird eine neue Registerkarte erstellt. Die WebView des Steuerelements sendet eine Nachricht an die Hostanwendung, um die WebView für diese Registerkarte zu erstellen und ein Objekt zu erstellen, das ihren Zustand verfolgt.
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);
}
}
Auf der Host-App-Seite fängt der registrierte ICoreWebView2WebMessageReceivedEventHandler die Nachricht ab und erstellt die WebView für diese Registerkarte.
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());
}
Der Tab registriert alle Handler, damit er beim Auslösen von Ereignissen Updates an die Steuerelemente WebView weiterleiten kann. Die Registerkarte ist bereit und wird im Inhaltsbereich des Browsers angezeigt. Durch Klicken auf eine Registerkarte in den Steuerelementen WebView wird eine Nachricht an die Hostanwendung gesendet, die wiederum die WebView für die zuvor aktive Registerkarte ausblendet und die für die angeklickte Registerkarte anzeigt.
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;
}
Aktualisieren des Sicherheitssymbols
Wir verwenden CallDevToolsProtocolMethod, um das Lauschen auf Sicherheitsereignisse zu ermöglichen. Immer wenn ein securityStateChanged Ereignis ausgelöst wird, verwenden wir den neuen Status, um das Sicherheitssymbol in der WebView-Steuerelemente zu aktualisieren.
// 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;
Auffüllen des Verlaufs
WebView2Browser verwendet IndexedDB in den Steuerelementen WebView, um Verlaufselemente zu speichern, nur ein Beispiel dafür, wie WebView2 Ihnen den Zugriff auf Standardwebtechnologien ermöglicht, wie Sie es im Browser tun würden. Das Element für eine Navigation wird erstellt, sobald die URI aktualisiert wird. Diese Elemente werden dann von der Verlaufsbenutzeroberfläche in einer Registerkarte abgerufen, die .window.chrome.postMessage
In diesem Fall werden die meisten Funktionen mithilfe von JavaScript an beiden Enden implementiert (steuert WebView und Inhalts-WebView, die die Benutzeroberfläche laden), sodass die Hostanwendung nur als Nachrichtenbroker fungiert, um diese Enden zu kommunizieren.
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);
}
};
}
};
});
}
Umgang mit JSON und URIs
WebView2Browser verwendet Microsofts cpprestsdk (Casablanca), um das gesamte JSON auf der C++-Seite der Dinge zu verarbeiten. IUri und CreateUri werden auch verwendet, um Dateipfade in URIs zu analysieren, und können auch für andere URIs verwendet werden.