summaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
authorMorteza Jamshidi <[email protected]>2025-03-07 13:25:28 +0100
committerMichal Klocek <[email protected]>2025-05-30 15:53:35 +0200
commit81b6881cf042988f588959a0090253199a3546cf (patch)
tree56c73bc619de6efb112a40e85931fff3f174e1ef /src/plugins
parentf7c6ad95b4cf7f6be7a90e2fdce3d0ad43b37f70 (diff)
Implement WebView2 windows plugin for qt webviewHEADdev
with this plugin qt webview uses webview2 in the backend for windows Task-number: QTBUG-75747 Change-Id: Iab91b95386be6b32c7acfb97f029ed49a6560745 Reviewed-by: Allan Sandfeld Jensen <[email protected]>
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/CMakeLists.txt5
-rw-r--r--src/plugins/windows/CMakeLists.txt24
-rw-r--r--src/plugins/windows/qwebview2webview.cpp631
-rw-r--r--src/plugins/windows/qwebview2webview_p.h110
-rw-r--r--src/plugins/windows/qwebview2webviewplugin.cpp23
-rw-r--r--src/plugins/windows/windows.json3
6 files changed, 795 insertions, 1 deletions
diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt
index 510604a..3cbc620 100644
--- a/src/plugins/CMakeLists.txt
+++ b/src/plugins/CMakeLists.txt
@@ -8,7 +8,10 @@ elseif(IOS OR MACOS)
elseif(WASM)
add_subdirectory(wasm)
endif()
-if(WINRT)
+
+if(QT_FEATURE_webview_webview2)
+ add_subdirectory(windows)
+elseif(WINRT)
add_subdirectory(winrt)
endif()
if(TARGET Qt::WebEngineCore)
diff --git a/src/plugins/windows/CMakeLists.txt b/src/plugins/windows/CMakeLists.txt
new file mode 100644
index 0000000..3a25095
--- /dev/null
+++ b/src/plugins/windows/CMakeLists.txt
@@ -0,0 +1,24 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## QWebview2Webview Plugin:
+#####################################################################
+
+qt_internal_add_plugin(QWebView2WebViewPlugin
+ OUTPUT_NAME qtwebview_webview2
+ PLUGIN_TYPE webview
+ SOURCES
+ qwebview2webview.cpp qwebview2webview_p.h
+ qwebview2webviewplugin.cpp
+ INCLUDE_DIRECTORIES
+ ${WEBVIEW2_INCLUDE_DIR}
+ LIBRARIES
+ urlmon
+ Qt::Core
+ Qt::Gui
+ Qt::WebViewPrivate
+ WebView2::WebView2
+ NO_PCH_SOURCES
+ "qwebview2webview.cpp"
+)
diff --git a/src/plugins/windows/qwebview2webview.cpp b/src/plugins/windows/qwebview2webview.cpp
new file mode 100644
index 0000000..255c5ab
--- /dev/null
+++ b/src/plugins/windows/qwebview2webview.cpp
@@ -0,0 +1,631 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwebview2webview_p.h"
+#include <private/qwebviewloadrequest_p.h>
+#include <QtCore/private/qfunctions_win_p.h>
+#include <QtWidgets/QtWidgets>
+
+QString WebErrorStatusToString(COREWEBVIEW2_WEB_ERROR_STATUS status)
+{
+ switch (status)
+ {
+#define STATUS_ENTRY(statusValue) \
+ case statusValue: \
+ return QString(L#statusValue);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_UNKNOWN);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_COMMON_NAME_IS_INCORRECT);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_EXPIRED);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_CLIENT_CERTIFICATE_CONTAINS_ERRORS);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_REVOKED);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_IS_INVALID);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_SERVER_UNREACHABLE);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_TIMEOUT);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_ERROR_HTTP_INVALID_SERVER_RESPONSE);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_CONNECTION_ABORTED);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_CONNECTION_RESET);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_DISCONNECTED);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_CANNOT_CONNECT);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_HOST_NAME_NOT_RESOLVED);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_OPERATION_CANCELED);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_REDIRECT_FAILED);
+ STATUS_ENTRY(COREWEBVIEW2_WEB_ERROR_STATUS_UNEXPECTED_ERROR);
+
+#undef STATUS_ENTRY
+ case COREWEBVIEW2_WEB_ERROR_STATUS_VALID_AUTHENTICATION_CREDENTIALS_REQUIRED:
+ case COREWEBVIEW2_WEB_ERROR_STATUS_VALID_PROXY_AUTHENTICATION_REQUIRED:
+ break;
+ }
+
+ return QString(L"ERROR");
+}
+
+QWebview2WebViewSettingsPrivate::QWebview2WebViewSettingsPrivate(QObject *p)
+ : QAbstractWebViewSettings(p)
+{
+}
+
+void QWebview2WebViewSettingsPrivate::init(ICoreWebView2Controller* viewController)
+{
+ if (viewController != nullptr) {
+ m_webviewController = viewController;
+ HRESULT hr = m_webviewController->get_CoreWebView2(&m_webview);
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+}
+
+bool QWebview2WebViewSettingsPrivate::localStorageEnabled() const
+{
+ return true;
+}
+
+bool QWebview2WebViewSettingsPrivate::javaScriptEnabled() const
+{
+ if (!m_webview)
+ return false;
+ ComPtr<ICoreWebView2Settings> settings;
+ HRESULT hr = m_webview->get_Settings(&settings);
+ Q_ASSERT_SUCCEEDED(hr);
+ BOOL isEnabled;
+ hr = settings->get_IsScriptEnabled(&isEnabled);
+ Q_ASSERT_SUCCEEDED(hr);
+ return isEnabled;
+}
+
+bool QWebview2WebViewSettingsPrivate::localContentCanAccessFileUrls() const
+{
+ return m_allowFileAccess;
+}
+
+bool QWebview2WebViewSettingsPrivate::allowFileAccess() const
+{
+ return m_allowFileAccess;
+}
+
+void QWebview2WebViewSettingsPrivate::setLocalContentCanAccessFileUrls(bool enabled)
+{
+ Q_UNUSED(enabled);
+ qWarning("setLocalContentCanAccessFileUrls() not supported on this platform");
+}
+
+void QWebview2WebViewSettingsPrivate::setJavaScriptEnabled(bool enabled)
+{
+ if (!m_webview)
+ return;
+
+ ComPtr<ICoreWebView2Settings> settings;
+ HRESULT hr = m_webview->get_Settings(&settings);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = settings->put_IsScriptEnabled(enabled);
+ Q_ASSERT_SUCCEEDED(hr);
+}
+
+void QWebview2WebViewSettingsPrivate::setLocalStorageEnabled(bool enabled)
+{
+ Q_UNUSED(enabled);
+ qWarning("setLocalStorageEnabled() not supported on this platform");
+}
+
+void QWebview2WebViewSettingsPrivate::setAllowFileAccess(bool enabled)
+{
+ m_allowFileAccess = enabled;
+}
+
+QWebView2WebViewPrivate::QWebView2WebViewPrivate(QObject *parent)
+ : QAbstractWebView(parent),
+ m_settings(new QWebview2WebViewSettingsPrivate(this)),
+ m_isLoading(false)
+{
+ // Create a QWindow without a parent
+ // This window is used for initializing the WebView2
+ QWindow *hiddenWindow = new QWindow();
+ hiddenWindow->setFlag(Qt::Tool);
+ hiddenWindow->setFlag(Qt::FramelessWindowHint); // No border
+ hiddenWindow->setFlag(Qt::WindowDoesNotAcceptFocus); // No focus
+ hiddenWindow->setGeometry(0, 0, 1, 1);
+ hiddenWindow->setOpacity(0);
+ hiddenWindow->show();
+ m_webViewWindow = hiddenWindow;
+ HWND hWnd = (HWND)m_webViewWindow->winId();
+
+ // This is the container for WebView2 window after initialization
+ // is finished (WebView2 window initialization seems to be very
+ // sensitive, so we put it in the container after making sure that
+ // the initialization is done successfully).
+ // This needs reconsideration later because it might be the same
+ // kind of problem with this bug (QTBUG-137230)
+ m_window = new QWindow();
+ connect(m_window, &QWindow::widthChanged, this,
+ &QWebView2WebViewPrivate::updateWindowGeometry, Qt::QueuedConnection);
+ connect(m_window, &QWindow::heightChanged, this,
+ &QWebView2WebViewPrivate::updateWindowGeometry, Qt::QueuedConnection);
+ connect(m_window, &QWindow::screenChanged, this,
+ &QWebView2WebViewPrivate::updateWindowGeometry, Qt::QueuedConnection);
+
+ CreateCoreWebView2EnvironmentWithOptions(nullptr, nullptr, nullptr,
+ Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
+ [hWnd, this](HRESULT result, ICoreWebView2Environment* env) -> HRESULT {
+ env->CreateCoreWebView2Controller(hWnd,
+ Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
+ [this](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT {
+ HRESULT hr;
+ if (controller != nullptr) {
+ m_webviewController = controller;
+ hr = m_webviewController->get_CoreWebView2(&m_webview);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ ComPtr<ICoreWebView2_2> webview2;
+ HRESULT hr = m_webview->QueryInterface(IID_PPV_ARGS(&webview2));
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = webview2->get_CookieManager(&m_cookieManager);
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+
+ m_settings->init(m_webviewController.Get());
+
+ // Add a few settings for the webview
+ ComPtr<ICoreWebView2Settings> settings;
+ hr = m_webview->get_Settings(&settings);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = settings->put_IsScriptEnabled(TRUE);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = settings->put_AreDefaultScriptDialogsEnabled(TRUE);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = settings->put_IsWebMessageEnabled(TRUE);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ QMetaObject::invokeMethod(this,"updateWindowGeometry", Qt::QueuedConnection);
+
+ //Schedule an async task to navigate to the url
+ //Because this is a callback and it might be triggered with a delay
+ if (!m_url.isEmpty() && m_url.isValid() && !m_url.scheme().isEmpty()) {
+ hr = m_webview->Navigate((wchar_t*)m_url.toString().utf16());
+ Q_ASSERT_SUCCEEDED(hr);
+ } else if (m_initData != nullptr && !m_initData->m_html.isEmpty()) {
+ hr = m_webview->NavigateToString((wchar_t*)m_initData->m_html.utf16());
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+ if (m_initData != nullptr && m_initData->m_cookies.size() > 0) {
+ for (auto it = m_initData->m_cookies.constBegin();
+ it != m_initData->m_cookies.constEnd(); ++it)
+ setCookie(it->domain, it->name, it.value().value);
+ }
+ m_initData.release();
+
+ EventRegistrationToken token;
+ hr = m_webview->add_NavigationStarting(
+ Microsoft::WRL::Callback<ICoreWebView2NavigationStartingEventHandler>(
+ [this](ICoreWebView2* webview,
+ ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT {
+ return this->onNavigationStarting(webview, args);
+ }).Get(), &token);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ hr = m_webview->add_NavigationCompleted(
+ Microsoft::WRL::Callback<ICoreWebView2NavigationCompletedEventHandler>(
+ [this](ICoreWebView2* webview,
+ ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT {
+ return this->onNavigationCompleted(webview, args);
+ }).Get(), &token);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ m_webview->add_WebResourceRequested(
+ Microsoft::WRL::Callback<ICoreWebView2WebResourceRequestedEventHandler>(
+ [this](ICoreWebView2* webview,
+ ICoreWebView2WebResourceRequestedEventArgs* args) -> HRESULT {
+ return this->onWebResourceRequested(webview, args);
+ }).Get(), &token);
+
+ hr = m_webview->add_ContentLoading(
+ Microsoft::WRL::Callback<ICoreWebView2ContentLoadingEventHandler>(
+ [this](ICoreWebView2* webview,
+ ICoreWebView2ContentLoadingEventArgs* args) -> HRESULT {
+ return this->onContentLoading(webview, args);
+ }).Get(), &token);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ ComPtr<ICoreWebView2_22> webview2;
+ hr = m_webview->QueryInterface(IID_PPV_ARGS(&webview2));
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = webview2->AddWebResourceRequestedFilterWithRequestSourceKinds(
+ L"file://*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL,
+ COREWEBVIEW2_WEB_RESOURCE_REQUEST_SOURCE_KINDS_ALL);
+ Q_ASSERT_SUCCEEDED(hr);
+ return S_OK;
+ }).Get());
+ return S_OK;
+ }).Get());
+}
+
+QWebView2WebViewPrivate::~QWebView2WebViewPrivate()
+{
+ m_webViewWindow->destroy();
+ m_window->destroy();
+ m_webviewController = nullptr;
+ m_webViewWindow = nullptr;
+ m_webview = nullptr;
+}
+
+QString QWebView2WebViewPrivate::httpUserAgent() const
+{
+ if (m_webviewController) {
+ ComPtr<ICoreWebView2Settings> settings;
+ HRESULT hr = m_webview->get_Settings(&settings);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<ICoreWebView2Settings2> settings2;
+ hr = settings->QueryInterface(IID_PPV_ARGS(&settings2));
+
+ if (settings2) {
+ wchar_t *userAgent;
+ hr = settings2->get_UserAgent(&userAgent);
+ Q_ASSERT_SUCCEEDED(hr);
+ QString userAgentString(userAgent);
+ CoTaskMemFree(userAgent);
+ return userAgentString;
+ }
+ }
+ qWarning() << "No http user agent available.";
+ return "";
+}
+
+void QWebView2WebViewPrivate::setHttpUserAgent(const QString &userAgent)
+{
+ if (m_webviewController) {
+ ComPtr<ICoreWebView2Settings> settings;
+ HRESULT hr = m_webview->get_Settings(&settings);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ ComPtr<ICoreWebView2Settings2> settings2;
+ hr = settings->QueryInterface(IID_PPV_ARGS(&settings2));
+ if (settings2) {
+ hr = settings2->put_UserAgent((wchar_t*)userAgent.utf16());
+ Q_ASSERT_SUCCEEDED(hr);
+ emit httpUserAgentChanged(userAgent);
+ return;
+ }
+ }
+ qWarning() << "No http user agent setting available.";
+}
+
+void QWebView2WebViewPrivate::setUrl(const QUrl &url)
+{
+ m_url = url;
+ if (m_webview) {
+ HRESULT hr = m_webview->Navigate((wchar_t*)url.toString().utf16());
+ if (FAILED(hr)) {
+ emit loadingChanged(QWebViewLoadRequestPrivate(url,
+ QWebView::LoadFailedStatus,
+ QString()));
+ emit loadProgressChanged(100);
+ }
+ }
+}
+
+bool QWebView2WebViewPrivate::canGoBack() const
+{
+ BOOL canGoBack = false;
+ if (m_webviewController) {
+ HRESULT hr = m_webview->get_CanGoBack(&canGoBack);
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+ return canGoBack;
+}
+
+bool QWebView2WebViewPrivate::canGoForward() const
+{
+ BOOL canGoForward = false;
+ if (m_webviewController) {
+ HRESULT hr = m_webview->get_CanGoForward(&canGoForward);
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+ return canGoForward;
+}
+
+QString QWebView2WebViewPrivate::title() const
+{
+ if (m_webviewController) {
+ wchar_t *title;
+ HRESULT hr = m_webview->get_DocumentTitle(&title);
+ Q_ASSERT_SUCCEEDED(hr);
+ QString titleString(title);
+ CoTaskMemFree(title);
+ return titleString;
+ }
+ return QString();
+}
+
+int QWebView2WebViewPrivate::loadProgress() const
+{
+ return m_isLoading ? 0 : 100;
+}
+
+bool QWebView2WebViewPrivate::isLoading() const
+{
+ return m_isLoading;
+}
+
+
+QWindow* QWebView2WebViewPrivate::nativeWindow() const
+{
+ return m_window;
+}
+
+void QWebView2WebViewPrivate::goBack()
+{
+ if (m_webview)
+ Q_ASSERT_SUCCEEDED(m_webview->GoBack());
+}
+
+void QWebView2WebViewPrivate::goForward()
+{
+ if (m_webview)
+ Q_ASSERT_SUCCEEDED(m_webview->GoForward());
+}
+
+void QWebView2WebViewPrivate::reload()
+{
+ if (m_webview)
+ Q_ASSERT_SUCCEEDED(m_webview->Reload());
+}
+
+void QWebView2WebViewPrivate::stop()
+{
+ if (m_webview)
+ Q_ASSERT_SUCCEEDED(m_webview->Stop());
+}
+
+void QWebView2WebViewPrivate::loadHtml(const QString &html, const QUrl &baseUrl)
+{
+ if (!baseUrl.isEmpty())
+ qWarning("Base URLs for loadHtml() are not supported on this platform. The document will be treated as coming from 'about:blank'.");
+
+ QByteArray encoded("data:text/html;charset=UTF-8,");
+ encoded.append(html.toUtf8().toPercentEncoding());
+ m_url = QUrl(encoded);
+
+ if (m_webview) {
+ Q_ASSERT_SUCCEEDED(m_webview->NavigateToString((wchar_t*)html.utf16()));
+ } else {
+ if (m_initData == nullptr)
+ m_initData = std::make_unique<QWebViewInitData>();
+ m_initData->m_html = html;
+ }
+}
+
+void QWebView2WebViewPrivate::setCookie(const QString &domain,
+ const QString &name,
+ const QString &value)
+{
+ if (m_webview) {
+ if (m_cookieManager) {
+ ComPtr<ICoreWebView2Cookie> cookie;
+ HRESULT hr = m_cookieManager->CreateCookie((wchar_t*)name.utf16(),
+ (wchar_t*)value.utf16(),
+ (wchar_t*)domain.utf16(),
+ L"",
+ &cookie);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = m_cookieManager->AddOrUpdateCookie(cookie.Get());
+ Q_ASSERT_SUCCEEDED(hr);
+ emit cookieAdded(domain, name);
+ }
+ } else {
+ if (m_initData == nullptr)
+ m_initData = std::make_unique<QWebViewInitData>();
+ m_initData->m_cookies.insert(domain + "/" + name, {domain, name, value});
+ }
+}
+
+void QWebView2WebViewPrivate::deleteCookie(const QString &domain, const QString &cookieName)
+{
+ if (m_webview) {
+ if (m_cookieManager) {
+ QString uri = domain;
+ if (!uri.startsWith("http"))
+ uri = "https://" + uri;
+ HRESULT hr = m_cookieManager->GetCookies((wchar_t*)uri.utf16(),
+ Microsoft::WRL::Callback<ICoreWebView2GetCookiesCompletedHandler>(
+ [cookieName, domain, this](HRESULT result, ICoreWebView2CookieList* cookieList)->HRESULT
+ {
+ UINT count = 0;
+ cookieList->get_Count(&count);
+ bool cookieFound = false;
+
+ for (UINT i = 0; i < count; ++i) {
+ ComPtr<ICoreWebView2Cookie> cookie;
+ if (SUCCEEDED(cookieList->GetValueAtIndex(i, &cookie))) {
+ wchar_t *namePtr;
+ if (SUCCEEDED(cookie->get_Name(&namePtr))) {
+ QString name(namePtr);
+ CoTaskMemFree(namePtr);
+ if (cookieName == name) {
+ cookieFound = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (cookieFound) {
+ HRESULT hr = m_cookieManager->DeleteCookiesWithDomainAndPath((wchar_t*)cookieName.utf16(),
+ (wchar_t*)domain.utf16(),
+ L"");
+ Q_ASSERT_SUCCEEDED(hr);
+ emit cookieRemoved(domain, cookieName);
+ }
+
+ return S_OK;
+ }).Get());
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+ } else if (m_initData != nullptr) {
+ m_initData->m_cookies.remove(domain + "/" + cookieName);
+ }
+}
+
+void QWebView2WebViewPrivate::deleteAllCookies()
+{
+ if (m_webview) {
+ if (m_cookieManager) {
+ HRESULT hr = m_cookieManager->GetCookies(L"",
+ Microsoft::WRL::Callback<ICoreWebView2GetCookiesCompletedHandler>(
+ [this](HRESULT result, ICoreWebView2CookieList* cookieList) -> HRESULT
+ {
+ UINT count = 0;
+ cookieList->get_Count(&count);
+ for (UINT i = 0; i < count; ++i) {
+ ComPtr<ICoreWebView2Cookie> cookie;
+ if (SUCCEEDED(cookieList->GetValueAtIndex(i, &cookie))) {
+ wchar_t *domain;
+ wchar_t *name;
+ if (SUCCEEDED(cookie->get_Domain(&domain))) {
+ if (SUCCEEDED(cookie->get_Name(&name))) {
+ emit cookieRemoved(QString(domain), QString(name));
+ CoTaskMemFree(name);
+ }
+ CoTaskMemFree(domain);
+ }
+ }
+ }
+ return S_OK;
+ }).Get());
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = m_cookieManager->DeleteAllCookies();
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+ } else if (m_initData != nullptr) {
+ m_initData->m_cookies.clear();
+ }
+}
+
+HRESULT QWebView2WebViewPrivate::onNavigationStarting(ICoreWebView2* webview, ICoreWebView2NavigationStartingEventArgs* args)
+{
+ wchar_t *uri;
+ HRESULT hr = args->get_Uri(&uri);
+ Q_ASSERT_SUCCEEDED(hr);
+ std::wstring_view source(uri);
+ m_url = QString(source);
+ emit urlChanged(m_url);
+ CoTaskMemFree(uri);
+ return S_OK;
+}
+
+
+HRESULT QWebView2WebViewPrivate::onNavigationCompleted(ICoreWebView2* webview, ICoreWebView2NavigationCompletedEventArgs* args)
+{
+ m_isLoading = false;
+
+ BOOL isSuccess;
+ HRESULT hr = args->get_IsSuccess(&isSuccess);
+ Q_ASSERT_SUCCEEDED(hr);
+ const QWebView::LoadStatus status = isSuccess ?
+ QWebView::LoadSucceededStatus :
+ QWebView::LoadFailedStatus;
+
+ COREWEBVIEW2_WEB_ERROR_STATUS errorStatus;
+ hr = args->get_WebErrorStatus(&errorStatus);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (errorStatus != COREWEBVIEW2_WEB_ERROR_STATUS_OPERATION_CANCELED) {
+ const QString errorStr = isSuccess ? "" : WebErrorStatusToString(errorStatus);
+ emit loadingChanged(QWebViewLoadRequestPrivate(m_url,
+ status,
+ errorStr));
+ emit titleChanged(title());
+ emit loadProgressChanged(100);
+ } else {
+ emit loadingChanged(QWebViewLoadRequestPrivate(m_url,
+ QWebView::LoadStoppedStatus,
+ QString()));
+ }
+ return S_OK;
+}
+
+HRESULT QWebView2WebViewPrivate::onWebResourceRequested(ICoreWebView2* sender, ICoreWebView2WebResourceRequestedEventArgs* args)
+{
+ ComPtr<ICoreWebView2WebResourceRequest> request;
+ ComPtr<ICoreWebView2WebResourceResponse> response;
+ HRESULT hr = args->get_Request(&request);
+ Q_ASSERT_SUCCEEDED(hr);
+ wchar_t *uri;
+ hr = request->get_Uri(&uri);
+ std::wstring_view source(uri);
+
+ if (!m_settings->allowFileAccess()) {
+ ComPtr<ICoreWebView2Environment> environment;
+ ComPtr<ICoreWebView2_2> webview2;
+ m_webview->QueryInterface(IID_PPV_ARGS(&webview2));
+ webview2->get_Environment(&environment);
+ Q_ASSERT_SUCCEEDED(
+ environment->CreateWebResourceResponse(
+ nullptr, 403, L"Access Denied", L"", &response));
+ Q_ASSERT_SUCCEEDED(args->put_Response(response.Get()));
+ }
+
+ CoTaskMemFree(uri);
+ return S_OK;
+}
+
+HRESULT QWebView2WebViewPrivate::onContentLoading(ICoreWebView2* webview, ICoreWebView2ContentLoadingEventArgs* args)
+{
+ m_isLoading = true;
+ emit loadingChanged(QWebViewLoadRequestPrivate(m_url,
+ QWebView::LoadStartedStatus,
+ QString()));
+ emit loadProgressChanged(0);
+ return S_OK;
+}
+
+void QWebView2WebViewPrivate::updateWindowGeometry()
+{
+ if (!m_webviewController)
+ return;
+ if (m_webViewWindow->opacity() <= 0) {
+ m_webViewWindow->setFlag(Qt::WindowDoesNotAcceptFocus, false);
+ m_webViewWindow->setParent(m_window);
+ m_webViewWindow->setOpacity(1);
+ }
+ RECT bounds;
+ GetClientRect((HWND)m_window->winId(), &bounds);
+ Q_ASSERT_SUCCEEDED(m_webviewController->put_Bounds(bounds));
+ m_webViewWindow->setGeometry(0, 0, m_window->width(), m_window->height());
+}
+
+void QWebView2WebViewPrivate::runJavaScriptPrivate(const QString &script, int callbackId)
+{
+ QEventLoop loop;
+ if (m_webview)
+ Q_ASSERT_SUCCEEDED(m_webview->ExecuteScript((wchar_t*)script.utf16(),
+ Microsoft::WRL::Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
+ [&loop, this, &callbackId](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT {
+ QString resultStr = QString::fromWCharArray(resultObjectAsJson);
+
+ QJsonParseError parseError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(resultStr.toUtf8(), &parseError);
+
+ QVariant resultVariant;
+ if (parseError.error == QJsonParseError::NoError) {
+ resultVariant = jsonDoc.toVariant();
+ } else {
+ QString wrapped = QString("{\"value\":%1}").arg(resultStr);
+ jsonDoc = QJsonDocument::fromJson(wrapped.toUtf8(), &parseError);
+ if (parseError.error == QJsonParseError::NoError) {
+ resultVariant = jsonDoc.object().value("value").toVariant();
+ } else {
+ QJsonValue val = QJsonValue::fromVariant(resultStr);
+ resultVariant = val.toVariant();
+ }
+ }
+ if (errorCode != S_OK)
+ emit javaScriptResult(callbackId, qt_error_string(errorCode));
+ else
+ emit javaScriptResult(callbackId, resultVariant);
+ loop.quit();
+ return errorCode;
+ }).Get()));
+ loop.exec();
+}
+
+QAbstractWebViewSettings *QWebView2WebViewPrivate::getSettings() const
+{
+ return m_settings;
+}
diff --git a/src/plugins/windows/qwebview2webview_p.h b/src/plugins/windows/qwebview2webview_p.h
new file mode 100644
index 0000000..89442b3
--- /dev/null
+++ b/src/plugins/windows/qwebview2webview_p.h
@@ -0,0 +1,110 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QWEBVIEW2WEBVIEW_P_H
+#define QWEBVIEW2WEBVIEW_P_H
+
+#include <private/qabstractwebview_p.h>
+
+#include <QMap>
+#include <QPointer>
+#include <webview2.h>
+#include <wrl.h>
+#include <wrl/client.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Microsoft::WRL;
+
+class QWebview2WebViewSettingsPrivate final : public QAbstractWebViewSettings
+{
+ Q_OBJECT
+public:
+ explicit QWebview2WebViewSettingsPrivate(QObject *p = nullptr);
+
+ void init(ICoreWebView2Controller* viewController);
+
+ bool localStorageEnabled() const final;
+ bool javaScriptEnabled() const final;
+ bool localContentCanAccessFileUrls() const final;
+ bool allowFileAccess() const final;
+
+public Q_SLOTS:
+ void setLocalContentCanAccessFileUrls(bool enabled) final;
+ void setJavaScriptEnabled(bool enabled) final;
+ void setLocalStorageEnabled(bool enabled) final;
+ void setAllowFileAccess(bool enabled) final;
+
+private:
+ ComPtr<ICoreWebView2Controller> m_webviewController;
+ ComPtr<ICoreWebView2> m_webview;
+ bool m_allowFileAccess = false;
+ bool m_localContentCanAccessFileUrls = false;
+};
+
+// This is used to store informations before webview2 is initialized
+// Because WebView2 initialization is async
+struct QWebViewInitData{
+ QString m_html;
+ struct CookieData{
+ QString domain;
+ QString name;
+ QString value;
+ };
+ QMap<QString, CookieData > m_cookies;
+};
+
+class QWebView2WebViewPrivate : public QAbstractWebView
+{
+ Q_OBJECT
+public:
+ explicit QWebView2WebViewPrivate(QObject *parent = nullptr);
+ ~QWebView2WebViewPrivate() override;
+
+ QString httpUserAgent() const override;
+ void setHttpUserAgent(const QString &userAgent) override;
+ void setUrl(const QUrl &url) override;
+ bool canGoBack() const override;
+ bool canGoForward() const override;
+ QString title() const override;
+ int loadProgress() const override;
+ bool isLoading() const override;
+
+ QWindow* nativeWindow() const override;
+
+public Q_SLOTS:
+ void goBack() override;
+ void goForward() override;
+ void reload() override;
+ void stop() override;
+ void loadHtml(const QString &html, const QUrl &baseUrl = QUrl()) override;
+ void setCookie(const QString &domain, const QString &name, const QString &value) override;
+ void deleteCookie(const QString &domain, const QString &name) override;
+ void deleteAllCookies() override;
+
+private Q_SLOTS:
+ HRESULT onNavigationStarting(ICoreWebView2* webview, ICoreWebView2NavigationStartingEventArgs* args);
+ HRESULT onNavigationCompleted(ICoreWebView2* webview, ICoreWebView2NavigationCompletedEventArgs* args);
+ HRESULT onWebResourceRequested(ICoreWebView2* sender, ICoreWebView2WebResourceRequestedEventArgs* args);
+ HRESULT onContentLoading(ICoreWebView2* webview, ICoreWebView2ContentLoadingEventArgs* args);
+ void updateWindowGeometry();
+
+protected:
+ void runJavaScriptPrivate(const QString &script, int callbackId) override;
+ QAbstractWebViewSettings *getSettings() const override;
+
+private:
+ ComPtr<ICoreWebView2Controller> m_webviewController;
+ ComPtr<ICoreWebView2> m_webview;
+ ComPtr<ICoreWebView2CookieManager> m_cookieManager;
+ QWebview2WebViewSettingsPrivate *m_settings;
+ QPointer<QWindow> m_window;
+ QPointer<QWindow> m_webViewWindow;
+ bool m_isLoading;
+ QUrl m_url;
+ std::unique_ptr<QWebViewInitData> m_initData = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWEBVIEW2WEBVIEW_P_H
diff --git a/src/plugins/windows/qwebview2webviewplugin.cpp b/src/plugins/windows/qwebview2webviewplugin.cpp
new file mode 100644
index 0000000..29f31f5
--- /dev/null
+++ b/src/plugins/windows/qwebview2webviewplugin.cpp
@@ -0,0 +1,23 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwebview2webview_p.h"
+#include <QtWebView/private/qwebviewplugin_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QWebView2WebViewPlugin : public QWebViewPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID QWebViewPluginInterface_iid FILE "windows.json")
+
+public:
+ QAbstractWebView *create(const QString &key) const override
+ {
+ return (key == QLatin1String("webview")) ? new QWebView2WebViewPrivate() : nullptr;
+ }
+};
+
+QT_END_NAMESPACE
+
+#include "qwebview2webviewplugin.moc"
diff --git a/src/plugins/windows/windows.json b/src/plugins/windows/windows.json
new file mode 100644
index 0000000..9f65fd4
--- /dev/null
+++ b/src/plugins/windows/windows.json
@@ -0,0 +1,3 @@
+{
+ "Keys": ["native"]
+}