diff options
author | Morteza Jamshidi <[email protected]> | 2025-03-07 13:25:28 +0100 |
---|---|---|
committer | Michal Klocek <[email protected]> | 2025-05-30 15:53:35 +0200 |
commit | 81b6881cf042988f588959a0090253199a3546cf (patch) | |
tree | 56c73bc619de6efb112a40e85931fff3f174e1ef /src/plugins | |
parent | f7c6ad95b4cf7f6be7a90e2fdce3d0ad43b37f70 (diff) |
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.txt | 5 | ||||
-rw-r--r-- | src/plugins/windows/CMakeLists.txt | 24 | ||||
-rw-r--r-- | src/plugins/windows/qwebview2webview.cpp | 631 | ||||
-rw-r--r-- | src/plugins/windows/qwebview2webview_p.h | 110 | ||||
-rw-r--r-- | src/plugins/windows/qwebview2webviewplugin.cpp | 23 | ||||
-rw-r--r-- | src/plugins/windows/windows.json | 3 |
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"] +} |