Win32-Beispiel für WebView2Browser

Dieses Beispiel, WebView2Browser, ist ein Webbrowser, der mit dem Microsoft Edge WebView2-Steuerelement erstellt wurde.

Dieses Beispiel verfügt über ein eigenes dediziertes Repository.

  • Beispielname: WebView2Browser
  • Repository: WebView2Browser
  • Projektmappendatei: WebViewBrowserApp.sln

Die WebView2Browser-Beispiel-App

WebView2Browser ist eine Windows-Beispiel-Desktopanwendung, 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 für die Kommunikation 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 eines Vorschaukanals von Microsoft Edge

Schritt 2: Installieren von Visual Studio

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

Die WebView2-Runtime wird für die Installation empfohlen. Die Mindestversion ist 86.0.622.38.

Schritt 3: Klonen des WebView2Samples-Repositorys

Schritt 4: Ö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 unter Projekteigenschaften > Konfigurationseigenschaften > Allgemeines > Plattformtoolset. 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 Windows-Versionen erstellen und ausführen möchten, bevor Windows 10, nehmen Sie die folgenden Änderungen vor. Dies ist erforderlich, da DPI in Windows 10 im Vergleich zu früheren Versionen von Windows behandelt wird.

  1. Wenn Sie den Browser in Windows-Versionen erstellen und ausführen möchten, bevor Windows 10: Ändern SetProcessDpiAwarenessContextSetProcessDPIAwareSie in WebViewBrowserApp.cppin :
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 Windows-Versionen erstellen und ausführen möchten, bevor Windows 10: Entfernen BrowserWindow.cppSie in den folgenden Aufruf von GetDpiForWindow, oder kommentieren Sie ihn aus:
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 5: Erstellen und Ausführen der App

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

  2. Erstellen Sie die Projektmappe.

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

  4. Schließen Sie die App.

Schritt 6: Aktualisieren des WebView2 SDK

  • 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 7: 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äche in eine Windows-Desktopanwendung zu integrieren. Dies ermöglicht es dem Browser, Standardwebtechnologien (HTML, CSS, JavaScript) zu verwenden, um die Benutzeroberfläche zu erleichtern, aber auch die App kann Favicons aus dem Web abrufen und IndexedDB zum Speichern von Favoriten und Verlauf verwenden.

Der Multi-WebView-Ansatz umfasst die Verwendung von zwei separaten WebView-Umgebungen (jede mit einem eigenen Benutzerdatenverzeichnis): eine für die Ui-WebViews und die andere für alle Inhaltswebviews. 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 viel Platz zum Experimentieren.

Das WebView2Browser-Beispiel implementiert die folgenden Features:

  • Zurück/Weiterleiten
  • Seite erneut laden
  • Navigation abbrechen
  • Mehrere Registerkarten
  • Verlauf
  • Favoriten
  • Suche über die Adressleiste
  • seitensicherheit status
  • Löschen von Cache und Cookies

WebView2-APIs

WebView2Browser verwendet einige der in WebView2 verfügbaren APIs. Weitere Informationen zu den APIs, die hier nicht verwendet werden, finden Sie in der Microsoft Edge WebView2-Referenz. Im Folgenden finden Sie eine Liste der interessantesten APIs, die WebView2Browser verwendet, und die features, die sie aktivieren.

API Features
CreateCoreWebView2EnvironmentWithOptions Wird zum Erstellen der Umgebungen für Benutzeroberflächen- und Inhaltswebansichten verwendet. Verschiedene Benutzerdatenverzeichnisse werden übergeben, um die Benutzeroberfläche von Webinhalten zu isolieren.
ICoreWebView2 Es gibt mehrere WebViews in WebView2Browser, und die meisten Features verwenden Member 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 Sperrsymbol in der Browser-Benutzeroberfläche zu aktualisieren.
ICoreWebView2DevToolsProtocolEventReceiver Wird zusammen mit add_DevToolsProtocolEventReceived verwendet, um auf CDP-Sicherheitsereignisse zu lauschen, um das Sperrsymbol in der Browser-Benutzeroberfläche zu aktualisieren.
ICoreWebView2ExecuteScriptCompletedHandler Wird zusammen mit ExecuteScript verwendet, um den Titel und das Favicon von der besuchten Seite abzurufen.
ICoreWebView2FocusChangedEventHandler Wird zusammen mit add_LostFocus verwendet, um die Dropdownliste für 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, von denen wir die zugeordneten WebViews abrufen.
ICoreWebView2NavigationCompletedEventHandler Wird zusammen mit add_NavigationCompleted verwendet, um die Schaltfläche "Neu laden" in der Browser-Benutzeroberfläche zu aktualisieren.
ICoreWebView2Settings Wird verwendet, um DevTools in der Browser-Benutzeroberfläche zu deaktivieren.
ICoreWebView2SourceChangedEventHandler Wird zusammen mit add_SourceChanged verwendet, um die Adressleiste in der Browser-Benutzeroberfläche zu aktualisieren.
ICoreWebView2WebMessageReceivedEventHandler Dies ist eine der wichtigsten APIs für WebView2Browser. Die meisten Funktionen, die die Kommunikation über WebViews umfassen, verwenden dies.
ICoreWebView2-API Features
add_NavigationStarting Wird verwendet, um die Schaltfläche zum Abbrechen der Navigation in den WebView-Steuerelementen anzuzeigen.
add_SourceChanged Wird verwendet, um die Adressleiste zu aktualisieren.
add_HistoryChanged Wird zum Aktualisieren von Zurück-/Vorwärtsschaltflächen verwendet.
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 Wird zum Kommunizieren von WebViews verwendet. Alle Nachrichten verwenden JSON, um erforderliche Parameter zu übergeben.
add_WebMessageReceived Wird verwendet, um Webnachrichten zu verarbeiten, die an die WebView gesendet werden.
CallDevToolsProtocolMethod Wird verwendet, um das Lauschen auf Sicherheitsereignisse zu ermöglichen, die über Sicherheits- status Änderungen in einem Dokument benachrichtigt werden.
ICoreWebView2Controller-API Feature(e)
get_CoreWebView2 Wird verwendet, um coreWebView2 abzurufen, das diesem CoreWebView2Controller zugeordnet ist.
add_LostFocus Wird verwendet, um die Dropdownliste der Optionen auszublenden, wenn der Benutzer darauf klickt.

Implementieren der Features

In den folgenden Abschnitten wird beschrieben, wie einige der Features in WebView2Browser implementiert wurden. Weitere Informationen dazu, wie alles funktioniert, finden Sie hier im Quellcode.

Inhalt:

Die Grundlagen

Einrichten der Umgebung, Erstellen einer WebView

Mit WebView2 können Sie Webinhalte in Ihrer Windows-App hosten. Es macht die globalen Elemente CreateCoreWebView2Environment und CreateCoreWebView2EnvironmentWithOptions verfügbar, aus denen 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 Ui-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 einige Dinge ein. Die ICoreWebView2Settings-Schnittstelle wird verwendet, um DevTools in der WebView zu deaktivieren, die die Browsersteuerelemente unterstützt. Außerdem fügen wir einen Handler für empfangene Webnachrichten hinzu. Dieser Handler ermöglicht es uns, etwas zu tun, wenn der Benutzer mit den Steuerelementen in dieser WebView interagiert.

Sie können zu einer Webseite navigieren, indem Sie den zugehörigen URI in die Adressleiste eingeben. Wenn Sie die EINGABETASTE drücken, senden die WebView-Steuerelemente eine Webnachricht an die Host-App, damit sie auf der aktiven Registerkarte zum angegebenen Speicherort navigieren kann. Der folgende Code zeigt, wie die Win32-Hostanwendung diese Nachricht 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, um Bing als Fallback zu durchsuchen.

Aktualisieren der Adressleiste

Die Adressleiste wird jedes Mal aktualisiert, wenn es eine Änderung in der Dokumentquelle der aktiven Registerkarte und zusammen mit anderen Steuerelementen beim Wechseln der Registerkarten gibt. Jede WebView löst ein Ereignis aus, wenn sich der Zustand des Dokuments ändert. Wir können dieses Ereignis verwenden, um die neue Quelle bei Updates abzurufen und die Änderung an die Steuerelemente WebView weiterzuleiten (wir werden auch die Schaltflächen "Zurück" und "Vorwärts" aktualisieren).

        // 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 dem URI an die WebView-Steuerelemente gesendet. Nun möchten wir diese Änderungen im Registerkartenzustand 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;

Zurück in die Zukunft

Jede WebView behält einen Verlauf für die Navigationen, die sie ausgeführt hat, sodass wir nur die Browser-Ui mit den entsprechenden Methoden verbinden müssen. Wenn die WebView der aktiven Registerkarte rückwärts/vorwärts navigiert werden kann, senden die Schaltflächen eine Webnachricht an die Hostanwendung, wenn darauf geklickt wird.

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;

Neu laden, Navigation beenden

Wir verwenden das Ereignis, das NavigationStarting von einer Inhalts-WebView ausgelöst wird, um den zugehörigen Tabladezustand in den WebView-Steuerelementen zu aktualisieren. Wenn ein WebView-Ereignis NavigationCompleted ausgelöst wird, verwenden wir dieses Ereignis, um die WebView-Steuerelemente anzuweisen, den Registerkartenzustand zu aktualisieren. Der aktive Registerkartenstatus in den WebView-Steuerelementen bestimmt, ob die Schaltfläche "Neu laden" oder "Abbrechen" angezeigt werden soll. Jeder dieser Elemente sendet eine Nachricht zurück an die Hostanwendung, wenn darauf geklickt wird, sodass die WebView für diese Registerkarte erneut geladen oder die Navigation abgebrochen werden kann.

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 Features

Kommunizieren der WebViews

Wir müssen die WebViews kommunizieren, die die Registerkarten und die Benutzeroberfläche verwenden, damit Benutzerinteraktionen in der WebView einer Registerkarte die gewünschte Wirkung in der anderen WebView haben. WebView2Browser verwendet zu diesem Zweck eine Reihe von sehr nützlichen WebView2-APIs, einschließlich PostWebMessageAsJson, add_WebMessageReceived und ICoreWebView2WebMessageReceivedEventHandler.

Auf der JavaScript-Seite verwenden wir das -Objekt, das window.chrome.webview verfügbar gemacht wird, um die postMessage -Methode aufzurufen und einen Ereignislistener 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);
}

Registerkartenbehandlung

Eine neue Registerkarte wird erstellt, wenn der Benutzer rechts neben den geöffneten Registerkarten auf die Schaltfläche "Neue Registerkarte" klickt. 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 seinen Zustand nachverfolgt.

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);
    }
}

Aufseiten der Host-App 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());
}

Die Registerkarte registriert alle Handler, damit sie Updates an die WebView-Steuerelemente weiterleiten kann, wenn Ereignisse ausgelöst werden. Die Registerkarte ist bereit und wird im Inhaltsbereich des Browsers angezeigt. Wenn Sie auf eine Registerkarte in den WebView-Steuerelementen klicken, 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 die CallDevToolsProtocolMethod , um das Lauschen auf Sicherheitsereignisse zu ermöglichen. Wenn ein securityStateChanged Ereignis ausgelöst wird, verwenden wir den neuen Zustand, um das Sicherheitssymbol in den WebView-Steuerelementen 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 WebView-Steuerelementen zum Speichern von Verlaufselementen. Dies ist nur ein Beispiel dafür, wie WebView2 Ihnen den Zugriff auf Standard-Webtechnologien ermöglicht, wie Sie es im Browser würden. Das Element für eine Navigation wird erstellt, sobald der URI aktualisiert wird. Diese Elemente werden dann von der Verlaufs-Ui auf einer Registerkarte abgerufen, auf der verwendet window.chrome.postMessagewird.

In diesem Fall wird die meiste Funktionalität mit JavaScript an beiden Enden implementiert (steuert WebView und InhaltswebView, die die Benutzeroberfläche lädt), 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);
                    }
                };
            }
        };

    });
}

Behandeln von JSON und URIs

WebView2Browser verwendet cpprestsdk (Casablanca) von Microsoft, um den gesamten JSON-Code auf der C++-Seite zu verarbeiten. IUri und CreateUri werden auch zum Analysieren von Dateipfaden in URIs verwendet und können auch für andere URIs verwendet werden.

Siehe auch