Поделиться через


Пример Win32 WebView2Browser

Этот пример , WebView2Browser, представляет собой веб-браузер, созданный с помощью элемента управления WebView2 Microsoft Edge .

Этот пример имеет собственный выделенный репозиторий.

  • Имя примера: WebView2Browser
  • Репозиторий: WebView2Browser
  • Файл решения: WebViewBrowserApp.sln

Пример приложения WebView2Browser

WebView2Browser — это пример классического приложения Windows, демонстрирующий возможности элемента управления WebView2. Пример приложения WebView2Browser использует несколько экземпляров WebView2.

Этот пример создан как проект Win32 Visual Studio 2019 . Он использует C++ и JavaScript в среде WebView2.

WebView2Browser показывает некоторые из самых простых способов использования WebView2, например создание WebView и навигацию по ним, а также некоторые более сложные рабочие процессы, например использование API PostWebMessageAsJson для обмена данными между элементами управления WebView2 в отдельных средах. Это многофункциональный пример кода, демонстрирующий, как можно использовать API WebView2 для создания собственного приложения.

Шаг 1. Установка канала предварительной версии Microsoft Edge

Шаг 2. Установка Visual Studio

  1. Установите Visual Studio, включая поддержку C++.

Для установки рекомендуется среда выполнения WebView2 . Минимальная версия — 86.0.622.38.

Шаг 3. Клонирование репозитория WebView2Samples

Шаг 4. Открытие решения в Visual Studio

  1. Откройте решение в Visual Studio 2019. Пакет SDK для WebView2 уже включен в проект в качестве пакета NuGet. Если вы хотите использовать Visual Studio 2017, измените набор инструментов платформы проекта в разделе Свойства проекта > . Свойства конфигурации. > Общие > набор инструментов платформы. Вам также может потребоваться изменить Windows SDK на последнюю версию.

  2. Внесите перечисленные ниже изменения, если вы используете версию Windows ниже Windows 10.

Использование версий ниже Windows 10

Если вы хотите выполнить сборку и запуск браузера в версиях Windows до Windows 10, внесите следующие изменения. Это необходимо из-за того, как DPI обрабатывается в Windows 10 по сравнению с предыдущими версиями Windows.

  1. Если вы хотите выполнить сборку и запуск браузера в версиях Windows перед Windows 10: в WebViewBrowserApp.cppизмените SetProcessDpiAwarenessContext значение на 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. Если вы хотите выполнить сборку и запуск браузера в версиях Windows перед Windows 10: в BrowserWindow.cpp, удалите или закомментируйте следующий вызов GetDpiForWindow:
int BrowserWindow::GetDPIAwareBound(int bound)
{
    // Remove the GetDpiForWindow call when using Windows 7 or any version
    // below 1607 (Windows 10). You will also have to make sure the build
    // directory is clean before building again.
    return (bound * GetDpiForWindow(m_hWnd) / DEFAULT_DPI);
}

Шаг 5. Сборка и запуск приложения

  1. Задайте целевой объект для сборки (например, отладка или выпуск, предназначенный для x86 или x64).

  2. Построение решения.

  3. Запустите (или отладите) приложение.

  4. Закройте приложение.

Шаг 6. Обновление пакета SDK для WebView2

  • Обновите версию пакета SDK для WebView2 в Visual Studio. Для этого щелкните проект правой кнопкой мыши и выберите пункт Управление пакетами NuGet.

Шаг 7. Сборка и запуск приложения с обновленным пакетом SDK для WebView2

  • Выполните сборку и снова запустите приложение.

Макет браузера

Пример приложения WebView2Browser использует несколько экземпляров WebView2.

WebView2Browser поддерживает подход с несколькими webView для интеграции веб-содержимого и пользовательского интерфейса приложения в классическое приложение Windows. Это позволяет браузеру использовать стандартные веб-технологии (HTML, CSS, JavaScript) для освещения интерфейса, но также позволяет приложению получать favicons из Интернета и использовать IndexedDB для хранения избранного и журнала.

Подход с несколькими WebView предполагает использование двух отдельных сред WebView (каждая с собственным каталогом данных пользователя): одна для веб-представлений пользовательского интерфейса, а другая — для всех веб-представлений содержимого. Веб-представления пользовательского интерфейса (элементы управления и раскрывающийся список параметров) используют среду пользовательского интерфейса, а веб-содержимое WebView (по одному на вкладку) — среду содержимого.

Макет браузера

Возможности

Пример WebView2Browser предоставляет все функциональные возможности для создания базового веб-браузера, но вам достаточно места для игры.

В примере WebView2Browser реализованы следующие функции:

  • Назад/пересылка
  • Перезагрузить страницу
  • Отмена навигации
  • Несколько вкладок
  • Журнал
  • Избранное
  • Поиск из адресной строки
  • Состояние безопасности страницы
  • Очистка кэша и файлов cookie

API WebView2

WebView2Browser использует несколько API, доступных в WebView2. Дополнительные сведения о API, не используемых здесь, см. в справочнике по Microsoft Edge WebView2. Ниже приведен список наиболее интересных API, которые использует WebView2Browser, а также доступные функции.

API Возможности
CreateCoreWebView2EnvironmentWithOptions Используется для создания сред для веб-представлений пользовательского интерфейса и содержимого. Различные каталоги данных пользователя передаются для изоляции пользовательского интерфейса от веб-содержимого.
ICoreWebView2 В WebView2Browser есть несколько веб-представлений, и большинство функций используют члены в этом интерфейсе. В таблице ниже показано, как они используются.
ICoreWebView2DevToolsProtocolEventReceivedEventHandler Используется вместе с add_DevToolsProtocolEventReceived для прослушивания событий безопасности CDP для обновления значка блокировки в пользовательском интерфейсе браузера.
ICoreWebView2DevToolsProtocolEventReceiver Используется вместе с add_DevToolsProtocolEventReceived для прослушивания событий безопасности CDP для обновления значка блокировки в пользовательском интерфейсе браузера.
ICoreWebView2ExecuteScriptCompletedHandler Используется вместе с ExecuteScript для получения заголовка и favicon с посещенной страницы.
ICoreWebView2FocusChangedEventHandler Используется вместе с add_LostFocus для скрытия раскрывающегося списка параметров браузера при потере фокуса.
ICoreWebView2HistoryChangedEventHandler Используется вместе с add_HistoryChanged для обновления кнопок навигации в пользовательском интерфейсе браузера.
ICoreWebView2Controller В WebView2Browser есть несколько webViewControllerser, и мы извлекаем из них связанные webView.
ICoreWebView2NavigationCompletedEventHandler Используется вместе с add_NavigationCompleted для обновления кнопки перезагрузки в пользовательском интерфейсе браузера.
ICoreWebView2Settings Используется для отключения средств разработки в пользовательском интерфейсе браузера.
ICoreWebView2SourceChangedEventHandler Используется вместе с add_SourceChanged для обновления адресной строки в пользовательском интерфейсе браузера.
ICoreWebView2WebMessageReceivedEventHandler Это один из наиболее важных API-интерфейсов WebView2Browser. Большинство функций, связанных с взаимодействием между WebViews, используют это.
ICoreWebView2 API Возможности
add_NavigationStarting Используется для отображения кнопки отмены навигации в элементах управления WebView.
add_SourceChanged Используется для обновления адресной строки.
add_HistoryChanged Используется для обновления кнопок перехода назад и вперед.
add_NavigationCompleted Используется для отображения кнопки перезагрузки после завершения навигации.
ExecuteScript Используется для получения заголовка и favicon посещенной страницы.
PostWebMessageAsJson Используется для обмена данными с WebView. Все сообщения используют JSON для передачи необходимых параметров.
add_WebMessageReceived Используется для обработки веб-сообщений, размещенных в WebView.
CallDevToolsProtocolMethod Используется для включения прослушивания событий безопасности, которые уведомляют об изменениях состояния безопасности в документе.
ICoreWebView2Controller API Функции
get_CoreWebView2 Используется для получения CoreWebView2, связанного с этим CoreWebView2Controller.
add_LostFocus Используется для скрытия раскрывающегося списка параметров, когда пользователь щелкает его.

Реализация функций

В следующих разделах описывается реализация некоторых функций WebView2Browser. Дополнительные сведения о том, как все работает, см. в исходном коде.

Содержимое:

Основы

Настройка среды, создание WebView

WebView2 позволяет размещать веб-содержимое в приложении для Windows. Он предоставляет глобальные возможности CreateCoreWebView2Environment и CreateCoreWebView2EnvironmentWithOptions , из которых можно создать две отдельные среды для пользовательского интерфейса и содержимого браузера.

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

Мы используем ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler для создания веб-представлений пользовательского интерфейса после того, как среда будет готова.

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

Мы настроим здесь несколько вещей. Интерфейс ICoreWebView2Settings используется для отключения средств разработки в WebView, питающих элементы управления браузера. Мы также добавляем обработчик для полученных веб-сообщений. Этот обработчик позволит нам выполнять действия, когда пользователь взаимодействует с элементами управления в этом WebView.

Вы можете перейти на веб-страницу, введя ее URI в адресной строке. При нажатии клавиши ВВОД элементы управления WebView опубликует веб-сообщение в ведущем приложении, чтобы перейти по активной вкладке в указанное расположение. В приведенном ниже коде показано, как хост-приложение Win32 будет обрабатывать это сообщение.

        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 будет проверка URI на страницах браузера (избранное, параметры, журнал) и перейти в запрошенное расположение или использовать предоставленный URI для поиска Bing в качестве резервного варианта.

Обновление адресной строки

Адресная строка обновляется при каждом изменении источника документа активной вкладки и других элементов управления при переключении вкладок. При изменении состояния документа каждое webView будет вызывать событие. Это событие можно использовать для получения нового источника при обновлении и перенаправления изменений в элементы управления WebView (мы также обновим кнопки "Вернуть назад" и "Перейти вперед").

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

Мы отправили MG_UPDATE_URI сообщение вместе с универсальным кодом ресурса (URI) в элементы управления WebView. Теперь мы хотим отразить эти изменения в состоянии вкладки и при необходимости обновить пользовательский интерфейс.

        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;

Назад, вперед

Каждое представление WebView будет хранить журнал выполненных навигаций, поэтому нам нужно только подключить пользовательский интерфейс браузера с помощью соответствующих методов. Если webView активной вкладки можно перемещать назад или вперед, кнопки будут отправлять веб-сообщение в ведущему приложению при щелчке.

На стороне JavaScript:

    document.querySelector('#btn-forward').addEventListener('click', function(e) {
        if (document.getElementById('btn-forward').className === 'btn') {
            var message = {
                message: commands.MG_GO_FORWARD,
                args: {}
            };
            window.chrome.webview.postMessage(message);
        }
    });

    document.querySelector('#btn-back').addEventListener('click', function(e) {
        if (document.getElementById('btn-back').className === 'btn') {
            var message = {
                message: commands.MG_GO_BACK,
                args: {}
            };
            window.chrome.webview.postMessage(message);
        }
    });

На стороне ведущего приложения:

        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;

Перезагрузка, остановка навигации

Мы используем событие, вызванное NavigationStarting содержимым WebView, для обновления связанного состояния загрузки вкладки в элементах управления WebView. Аналогичным образом, когда WebView запускает NavigationCompleted событие, мы используем это событие, чтобы указать элементам управления WebView обновить состояние вкладки. Состояние активной вкладки в элементах управления WebView определяет, будет ли отображаться кнопка перезагрузки или отмены. Каждый из них будет отправлять сообщение обратно в ведущее приложение при щелчке, чтобы webView для этой вкладки можно было перезагрузить или соответственно отменить навигацию.

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

Некоторые интересные функции

Обмен веб-представлениями

Нам нужно сообщить о веб-представлениях, которые питают вкладки и пользовательский интерфейс, чтобы взаимодействие с пользователем в WebView одной вкладки могло иметь желаемый эффект в другом WebView. WebView2Browser использует для этой цели набор очень полезных API WebView2, включая PostWebMessageAsJson, add_WebMessageReceived и ICoreWebView2WebMessageReceivedEventHandler.

На стороне JavaScript мы используем объект , предоставляемый window.chrome.webview для вызова postMessage метода и добавления списка событий для полученных сообщений.

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

Обработка вкладок

Новая вкладка будет создаваться всякий раз, когда пользователь нажимает кнопку "Создать вкладку " справа от открытых вкладок. WebView элемента управления опубликует сообщение в ведущему приложению, чтобы создать WebView для этой вкладки и создать объект, отслеживающий его состояние.

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

На стороне ведущего приложения зарегистрированный ICoreWebView2WebMessageReceivedEventHandler перехватывает сообщение и создает WebView для этой вкладки.

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

Вкладка регистрирует все обработчики, чтобы она могла пересылать обновления в элементы управления WebView при срабатывании событий. Вкладка готова и будет отображаться в области содержимого браузера. Если щелкнуть вкладку в элементах управления WebView, ведущему приложению будет отправлено сообщение, которое, в свою очередь, скроет WebView для ранее активной вкладки и отобразит ее для вкладки.

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

Обновление значка безопасности

Мы используем CallDevToolsProtocolMethod для включения прослушивания событий безопасности. При каждом securityStateChanged запуске события мы будем использовать новое состояние для обновления значка безопасности в элементах управления WebView.

        // Enable listening for security events to update secure icon
        RETURN_IF_FAILED(m_contentWebView->CallDevToolsProtocolMethod(L"Security.enable", L"{}", nullptr));

        BrowserWindow::CheckFailure(m_contentWebView->GetDevToolsProtocolEventReceiver(L"Security.securityStateChanged", &m_securityStateChangedReceiver), L"");

        // Forward security status updates to browser
        RETURN_IF_FAILED(m_securityStateChangedReceiver->add_DevToolsProtocolEventReceived(Callback<ICoreWebView2DevToolsProtocolEventReceivedEventHandler>(
            [this, browserWindow](ICoreWebView2* webview, ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -> HRESULT
        {
            BrowserWindow::CheckFailure(browserWindow->HandleTabSecurityUpdate(m_tabId, webview, args), L"Can't update security icon");
            return S_OK;
        }).Get(), &m_securityUpdateToken));
HRESULT BrowserWindow::HandleTabSecurityUpdate(size_t tabId, ICoreWebView2* webview, ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args)
{
    wil::unique_cotaskmem_string jsonArgs;
    RETURN_IF_FAILED(args->get_ParameterObjectAsJson(&jsonArgs));
    web::json::value securityEvent = web::json::value::parse(jsonArgs.get());

    web::json::value jsonObj = web::json::value::parse(L"{}");
    jsonObj[L"message"] = web::json::value(MG_SECURITY_UPDATE);
    jsonObj[L"args"] = web::json::value::parse(L"{}");
    jsonObj[L"args"][L"tabId"] = web::json::value::number(tabId);
    jsonObj[L"args"][L"state"] = securityEvent.at(L"securityState");

    return PostJsonToWebView(jsonObj, m_controlsWebView.Get());
}
        case commands.MG_SECURITY_UPDATE:
            if (isValidTabId(args.tabId)) {
                const tab = tabs.get(args.tabId);
                tab.securityState = args.state;

                if (args.tabId == activeTabId) {
                    updateNavigationUI(message);
                }
            }
            break;

Заполнение журнала

WebView2Browser использует IndexedDB в элементах управления WebView для хранения элементов журнала, что является примером того, как WebView2 позволяет получить доступ к стандартным веб-технологиям, как в браузере. Элемент для навигации будет создан сразу после обновления URI. Затем эти элементы извлекаются пользовательским интерфейсом журнала на вкладке с использованием window.chrome.postMessage.

В этом случае большая часть функциональных возможностей реализуется с помощью JavaScript на обоих концах (элементы управления WebView и содержимое WebView, загружающие пользовательский интерфейс), поэтому ведущее приложение выступает в качестве брокера сообщений для передачи этих целей.

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

    });
}

Обработка JSON и URI

WebView2Browser использует cpprestsdk (Casablanca) Корпорации Майкрософт для обработки всего JSON на стороне C++. IUri и CreateUri также используются для анализа путей к файлам в URI, а также для других URI.

См. также