Win32-Beispiel: WebView2Browser

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

Die WebView2Browser-Beispiel-App

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

  1. Installieren Sie Visual Studio einschließlich C++-Unterstützung.

Schritt 2: Klonen des WebView2Samples-Repositorys

Schritt 3: Öffnen der Projektmappe in Visual Studio

  1. Ö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.

  2. 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.

  1. Wenn Sie den Browser in älteren Windows-Versionen als Windows 10 erstellen und ausführen möchten: In WebViewBrowserApp.cppändern Sie SetProcessDpiAwarenessContext zu SetProcessDPIAware:
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                      _In_opt_ HINSTANCE hPrevInstance,
                      _In_ LPWSTR    lpCmdLine,
                      _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // Call SetProcessDPIAware() instead when using Windows 7 or any version
    // below 1703 (Windows 10).
    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

    BrowserWindow::RegisterClass(hInstance);

    // ...
  1. 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

  1. Legen Sie das Ziel fest, das Sie erstellen möchten (z. B. Debuggen oder Release mit x86 oder x64).

  2. Erstellen Sie die Projektmappe.

  3. Führen Sie die App aus (oder debuggen).

  4. 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.

Browserlayout

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.

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.

Siehe auch