Пример Win32: WebView2Browser

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

У этого образца есть собственный специализированный репозиторий.

  • Имя примера: 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. Установка Visual Studio

  1. Установите Visual Studio с поддержкой C++.

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

Шаг 3. Откройте решение в 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);
}

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

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

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

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

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

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

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

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

  • Создайте и снова запустите приложение.

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

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

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

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

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

Возможности

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

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

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

API WebView2

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

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

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

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

Основы

Настройка среды, создание веб-представления

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;

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

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

На стороне 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 генерирует NavigationCompleted событие, мы используем это событие для указания элементам управления 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 одной вкладки имели желаемый эффект в 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);
}

Работа с вкладками

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

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 перехватывает сообщение и создает веб-представление для этой вкладки.

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

        // 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 использует Microsoft cpprestsdk (Casablanca) для обработки всех JSON на стороне C++. IUri и CreateUri также используются для анализа путей к файлам в URI и могут применяться для других URI.

См. также