Revision control

++ // 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
++ // Qt-Security score:significant reason:default
++
++ #include "qwebview2webview_p.h"
++ #include <QtWebView/qwebviewloadinginfo.h>
++ #include <QtWebView/private/qwebviewfactory_p.h>
++ #include <QtCore/qfile.h>
++ #include <QtCore/qfileinfo.h>
++ #include <QtCore/qmimedatabase.h>
++ #include <QtCore/qmimetype.h>
++ #include <QtCore/private/qfunctions_win_p.h>
++ #include <QtWidgets/QtWidgets>
++
++ #pragma push_macro("__has_attribute") // https://github.com/MicrosoftEdge/WebView2Feedback/discussions/5317
++ #undef __has_attribute
++ #include <WebView2EnvironmentOptions.h>
++ #pragma pop_macro("__has_attribute")
++
++ using namespace Qt::StringLiterals;
++
++ 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()
++ {
++ }
++
++ 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::doTestAttribute(WebAttribute attribute) const
++ {
++ switch (attribute) {
++ case QWebViewSettings::WebAttribute::LocalStorageEnabled:
++ return localStorageEnabled();
++ case QWebViewSettings::WebAttribute::JavaScriptEnabled:
++ return javaScriptEnabled();
++ case QWebViewSettings::WebAttribute::AllowFileAccess:
++ return allowFileAccess();
++ case QWebViewSettings::WebAttribute::LocalContentCanAccessFileUrls:
++ return localContentCanAccessFileUrls();
++ }
++ return false;
++ }
++
++ void QWebview2WebViewSettingsPrivate::doSetAttribute(WebAttribute attribute, bool value)
++ {
++ switch (attribute) {
++ case QWebViewSettings::WebAttribute::LocalStorageEnabled:
++ setLocalStorageEnabled(value);
++ break;
++ case QWebViewSettings::WebAttribute::JavaScriptEnabled:
++ setJavaScriptEnabled(value);
++ break;
++ case QWebViewSettings::WebAttribute::AllowFileAccess:
++ setAllowFileAccess(value);
++ break;
++ case QWebViewSettings::WebAttribute::LocalContentCanAccessFileUrls:
++ setLocalContentCanAccessFileUrls(value);
++ break;
++ }
++ }
++
++ bool QWebview2WebViewSettingsPrivate::localStorageEnabled() const
++ {
++ return true;
++ }
++
++ bool QWebview2WebViewSettingsPrivate::javaScriptEnabled() const
++ {
++ return m_javaScriptEnabled;
++ }
++
++ 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)
++ {
++ m_javaScriptEnabled = 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(QWebView *view)
++ : QWebViewPrivate(view),
++ m_settings(new QWebview2WebViewSettingsPrivate()),
++ m_window(view),
++ m_isLoading(false),
++ m_progress(0)
++ {
++ // Create a QWindow without a parent
++ // This window is used for initializing the WebView2
++
++ m_window->setFlag(Qt::Tool);
++ m_window->setFlag(Qt::FramelessWindowHint); // No border
++ m_window->setFlag(Qt::WindowDoesNotAcceptFocus); // No focus
++
++ // create platform window
++ HWND hWnd = (HWND)m_window->winId();
++
++ QTimer::singleShot(0, this, [this, hWnd]() { emit initialize(hWnd); });
++ };
++
++ void QWebView2WebViewPrivate::initialize(HWND hWnd)
++ {
++ 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);
++
++ // Set up the qrc scheme
++ const WCHAR* allowedOrigins[1] = {L"https://*.qt.io"};
++ auto environmentOptions = Microsoft::WRL::Make<CoreWebView2EnvironmentOptions>();
++ ComPtr<ICoreWebView2EnvironmentOptions4> environmentOptions4;
++ HRESULT hr;
++ hr = environmentOptions.As(&environmentOptions4);
++ Q_ASSERT_SUCCEEDED(hr);
++ auto qrcSchemeRegistration =
++ Microsoft::WRL::Make<CoreWebView2CustomSchemeRegistration>(L"qrc");
++ qrcSchemeRegistration->put_TreatAsSecure(TRUE);
++ qrcSchemeRegistration->put_HasAuthorityComponent(TRUE);
++ qrcSchemeRegistration->SetAllowedOrigins(1, allowedOrigins);
++ ICoreWebView2CustomSchemeRegistration* schemeRegistrations[1] = {qrcSchemeRegistration.Get()};
++ hr = environmentOptions4->SetCustomSchemeRegistrations(1,
++ static_cast<ICoreWebView2CustomSchemeRegistration**>(schemeRegistrations));
++ Q_ASSERT_SUCCEEDED(hr);
++
++ QPointer<QWebView2WebViewPrivate> thisPtr = this;
++ const QString userDataFolder = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) % QDir::separator() % QLatin1StringView("WebView2");
++ using W2ControllerCallback = ICoreWebView2CreateCoreWebView2ControllerCompletedHandler;
++ auto controllerCallback = Microsoft::WRL::Callback<W2ControllerCallback>(
++ [thisPtr, this](HRESULT result, ICoreWebView2Controller *controller) -> HRESULT {
++ if (thisPtr.isNull())
++ return S_FALSE;
++
++ if (!controller)
++ return S_FALSE;
++ HRESULT hr;
++ m_webviewController = controller;
++ hr = m_webviewController->get_CoreWebView2(&m_webview);
++ Q_ASSERT_SUCCEEDED(hr);
++
++ ComPtr<ICoreWebView2_2> webview2;
++ 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(m_settings->javaScriptEnabled());
++ Q_ASSERT_SUCCEEDED(hr);
++ hr = settings->put_AreDefaultScriptDialogsEnabled(TRUE);
++ Q_ASSERT_SUCCEEDED(hr);
++ hr = settings->put_IsWebMessageEnabled(TRUE);
++ Q_ASSERT_SUCCEEDED(hr);
++
++ // 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.m_html.isEmpty()) {
++ hr = m_webview->NavigateToString((wchar_t *)m_initData.m_html.utf16());
++ Q_ASSERT_SUCCEEDED(hr);
++ }
++ if (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);
++ }
++ if (!m_initData.m_httpUserAgent.isEmpty()) {
++ ComPtr<ICoreWebView2Settings2> settings2;
++ hr = settings->QueryInterface(IID_PPV_ARGS(&settings2));
++ if (settings2) {
++ hr = settings2->put_UserAgent(
++ (wchar_t *)m_initData.m_httpUserAgent.utf16());
++ if (SUCCEEDED(hr))
++ QTimer::singleShot(0, thisPtr, [thisPtr] {
++ if (!thisPtr.isNull())
++ emit thisPtr->q_ptr->httpUserAgentStringChanged(
++ thisPtr->m_initData.m_httpUserAgent);
++ });
++ }
++ }
++
++ 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);
++
++ hr = m_webview->add_NewWindowRequested(
++ Microsoft::WRL::Callback<ICoreWebView2NewWindowRequestedEventHandler>(
++ [this](ICoreWebView2 *webview,
++ ICoreWebView2NewWindowRequestedEventArgs *args) -> HRESULT {
++ return this->onNewWindowRequested(webview, args);
++ })
++ .Get(),
++ &token);
++ Q_ASSERT_SUCCEEDED(hr);
++
++ ComPtr<ICoreWebView2_22> webview22;
++ hr = m_webview->QueryInterface(IID_PPV_ARGS(&webview22));
++ Q_ASSERT_SUCCEEDED(hr);
++ hr = webview22->AddWebResourceRequestedFilterWithRequestSourceKinds(
++ L"file://*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL,
++ COREWEBVIEW2_WEB_RESOURCE_REQUEST_SOURCE_KINDS_ALL);
++ Q_ASSERT_SUCCEEDED(hr);
++ hr = webview22->AddWebResourceRequestedFilterWithRequestSourceKinds(
++ L"qrc:*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL,
++ COREWEBVIEW2_WEB_RESOURCE_REQUEST_SOURCE_KINDS_ALL);
++ Q_ASSERT_SUCCEEDED(hr);
++ this->updateWindowGeometry();
++ return S_OK;
++ });
++ using W2EnvironmentCallback = ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler;
++ auto environmentCallback = Microsoft::WRL::Callback<W2EnvironmentCallback>(
++ [hWnd, thisPtr, controllerCallback, this](HRESULT result,
++ ICoreWebView2Environment *env) -> HRESULT {
++ env->CreateCoreWebView2Controller(hWnd, controllerCallback.Get());
++ return S_OK;
++ });
++ hr = CreateCoreWebView2EnvironmentWithOptions(nullptr, userDataFolder.toStdWString().c_str(),
++ environmentOptions.Get(), environmentCallback.Get());
++ Q_ASSERT_SUCCEEDED(hr);
++ }
++
++ QWebView2WebViewPrivate::~QWebView2WebViewPrivate()
++ {
++ m_webviewController = 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 "";
++ }
++ return m_initData.m_httpUserAgent;
++ }
++
++ 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());
++ if (SUCCEEDED(hr))
++ emit q_ptr->httpUserAgentStringChanged(userAgent);
++ return;
++ } else {
++ qWarning() << "No http user agent setting available.";
++ }
++ } else {
++ m_initData.m_httpUserAgent = userAgent;
++ }
++ }
++
++ QUrl QWebView2WebViewPrivate::url() const
++ {
++ return m_url;
++ }
++
++ 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)) {
++ m_isLoading = false;
++ m_progress = 100;
++ emit q_ptr->loadProgressChanged(100);
++ emit q_ptr->loadingChanged(QWebViewFactory::LoadingInfo::create(
++ url, QWebViewLoadingInfo::LoadStatus::Failed, QString()));
++ }
++ }
++ }
++
++ 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_progress;
++ }
++
++ bool QWebView2WebViewPrivate::isLoading() const
++ {
++ return m_isLoading;
++ }
++
++
++ QWindow* QWebView2WebViewPrivate::nativeWindow() const
++ {
++ return m_window;
++ }
++
++ void QWebView2WebViewPrivate::goBack()
++ {
++ if (m_webview) {
++ const HRESULT hr = m_webview->GoBack();
++ Q_ASSERT_SUCCEEDED(hr);
++ }
++ }
++
++ void QWebView2WebViewPrivate::goForward()
++ {
++ if (m_webview) {
++ const HRESULT hr = m_webview->GoForward();
++ Q_ASSERT_SUCCEEDED(hr);
++ }
++ }
++
++ void QWebView2WebViewPrivate::reload()
++ {
++ if (m_webview) {
++ const HRESULT hr = m_webview->Reload();
++ Q_ASSERT_SUCCEEDED(hr);
++ }
++ }
++
++ void QWebView2WebViewPrivate::stop()
++ {
++ if (m_webview) {
++ const HRESULT hr = m_webview->Stop();
++ Q_ASSERT_SUCCEEDED(hr);
++ }
++ }
++
++ 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) {
++ const HRESULT hr = m_webview->NavigateToString((wchar_t *)html.utf16());
++ Q_ASSERT_SUCCEEDED(hr);
++ } else {
++ 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 q_ptr->cookieAdded(domain, name);
++ }
++ } else {
++ m_initData.m_cookies.insert(domain + "/" + name, {domain, name, value});
++ }
++ }
++
++ void QWebView2WebViewPrivate::deleteCookie(const QString &domainName, const QString &cookieName)
++ {
++ if (m_webview) {
++ if (m_cookieManager) {
++ QString uri = domainName;
++ if (!uri.startsWith("http"))
++ uri = "https://" + uri;
++ HRESULT hr = m_cookieManager->GetCookies((wchar_t*)uri.utf16(),
++ Microsoft::WRL::Callback<ICoreWebView2GetCookiesCompletedHandler>(
++ [cookieName, domainName, 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 *domainPtr;
++ wchar_t *namePtr;
++ if (SUCCEEDED(cookie->get_Domain(&domainPtr)) &&
++ SUCCEEDED(cookie->get_Name(&namePtr))) {
++ QString domain(domainPtr);
++ QString name(namePtr);
++ CoTaskMemFree(namePtr);
++ CoTaskMemFree(domainPtr);
++ if (domainName == domain && cookieName == name) {
++ emit q_ptr->cookieRemoved(domain, cookieName);
++ return S_OK;
++ }
++ }
++ }
++ }
++ return S_OK;
++ }).Get());
++ Q_ASSERT_SUCCEEDED(hr);
++ hr = m_cookieManager->DeleteCookiesWithDomainAndPath((wchar_t*)cookieName.utf16(),
++ (wchar_t*)domainName.utf16(),
++ L"");
++ Q_ASSERT_SUCCEEDED(hr);
++
++
++ }
++ } else {
++ m_initData.m_cookies.remove(domainName + "/" + 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 q_ptr->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 {
++ 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.setUrl(QString(source));
++ emit q_ptr->urlChanged(m_url);
++ CoTaskMemFree(uri);
++ return S_OK;
++ }
++
++
++ HRESULT QWebView2WebViewPrivate::onNavigationCompleted(ICoreWebView2* webview, ICoreWebView2NavigationCompletedEventArgs* args)
++ {
++ m_isLoading = false;
++ m_progress = 100;
++
++ BOOL isSuccess;
++ HRESULT hr = args->get_IsSuccess(&isSuccess);
++ Q_ASSERT_SUCCEEDED(hr);
++ const QWebViewLoadingInfo::LoadStatus status = isSuccess
++ ? QWebViewLoadingInfo::LoadStatus::Succeeded
++ : QWebViewLoadingInfo::LoadStatus::Failed;
++
++ 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 q_ptr->titleChanged(title());
++ emit q_ptr->loadProgressChanged(100);
++ emit q_ptr->loadingChanged(QWebViewFactory::LoadingInfo::create(
++ m_url, status, errorStr));
++ } else {
++ emit q_ptr->loadingChanged(QWebViewFactory::LoadingInfo::create(
++ m_url, QWebViewLoadingInfo::LoadStatus::Stopped, QString()));
++ }
++ return S_OK;
++ }
++
++ HRESULT QWebView2WebViewPrivate::onWebResourceRequested(ICoreWebView2* sender, ICoreWebView2WebResourceRequestedEventArgs* args)
++ {
++ ComPtr<ICoreWebView2WebResourceRequest> request;
++ ComPtr<ICoreWebView2WebResourceResponse> response;
++ ComPtr<ICoreWebView2Environment> environment;
++ ComPtr<ICoreWebView2_2> webview2;
++ HRESULT hr = args->get_Request(&request);
++ Q_ASSERT_SUCCEEDED(hr);
++ hr = m_webview->QueryInterface(IID_PPV_ARGS(&webview2));
++ Q_ASSERT_SUCCEEDED(hr);
++ hr = webview2->get_Environment(&environment);
++ Q_ASSERT_SUCCEEDED(hr);
++
++ wchar_t *uri;
++ hr = request->get_Uri(&uri);
++ const QUrl url{QString(uri)};
++
++ if (url.scheme() == "file"_L1) {
++ if (!m_settings->allowFileAccess()) {
++ hr = environment->CreateWebResourceResponse(nullptr, 403, L"Access Denied", L"", &response);
++ Q_ASSERT_SUCCEEDED(hr)
++ hr = args->put_Response(response.Get());
++ Q_ASSERT_SUCCEEDED(hr)
++ }
++ } else if (url.scheme() == "qrc"_L1) {
++ QFile file(u':' + url.path());
++ bool isOpen = file.exists() && file.size() != 0 && file.open(QIODeviceBase::ReadOnly);
++ if (!isOpen) {
++ ComPtr<ICoreWebView2Environment> environment;
++ ComPtr<ICoreWebView2_2> webview2;
++ m_webview->QueryInterface(IID_PPV_ARGS(&webview2));
++ webview2->get_Environment(&environment);
++
++ hr = environment->CreateWebResourceResponse(nullptr, 404, L"Not Found", L"", &response);
++ Q_ASSERT_SUCCEEDED(hr)
++ hr = args->put_Response(response.Get());
++ Q_ASSERT_SUCCEEDED(hr)
++ } else {
++ QFileInfo fileInfo(file);
++ QMimeDatabase mimeDatabase;
++ QMimeType mimeType = mimeDatabase.mimeTypeForFile(fileInfo);
++
++ ComPtr<IStream> iStream;
++ hr = CreateStreamOnHGlobal(nullptr, TRUE, &iStream);
++ Q_ASSERT_SUCCEEDED(hr)
++
++ hr = environment->CreateWebResourceResponse(iStream.Get(), 200, L"OK", L"", &response);
++ Q_ASSERT_SUCCEEDED(hr)
++
++ ComPtr<ICoreWebView2HttpResponseHeaders> headers;
++ hr = response->get_Headers(&headers);
++ headers->AppendHeader(L"Content-Type", (LPCWSTR)(mimeType.name().utf16()));
++
++ QByteArray data = file.readAll();
++ hr = iStream->Write(data.data(), data.size(), nullptr);
++ Q_ASSERT_SUCCEEDED(hr)
++ Q_ASSERT(file.atEnd()); // Assumes files in the resource system will be read in one go
++ file.close();
++
++ hr = args->put_Response(response.Get());
++ Q_ASSERT_SUCCEEDED(hr)
++ }
++ }
++
++ CoTaskMemFree(uri);
++ return S_OK;
++ }
++
++ HRESULT QWebView2WebViewPrivate::onContentLoading(ICoreWebView2* webview, ICoreWebView2ContentLoadingEventArgs* args)
++ {
++ m_isLoading = true;
++ m_progress = 0;
++ emit q_ptr->loadingChanged(QWebViewFactory::LoadingInfo::create(
++ m_url, QWebViewLoadingInfo::LoadStatus::Started, QString()));
++ emit q_ptr->loadProgressChanged(0);
++ return S_OK;
++ }
++
++ HRESULT QWebView2WebViewPrivate::onNewWindowRequested(ICoreWebView2* webview, ICoreWebView2NewWindowRequestedEventArgs* args)
++ {
++ Q_UNUSED(webview);
++ // This blocks the spawning of new windows we don't control
++ // FIXME actually handle new windows when QWebView has the API for them
++ args->put_Handled(TRUE);
++ return S_OK;
++ }
++
++ void QWebView2WebViewPrivate::updateWindowGeometry()
++ {
++ if (m_webviewController) {
++ RECT bounds;
++ GetClientRect((HWND)m_window->winId(), &bounds);
++ const HRESULT hr = m_webviewController->put_Bounds(bounds);
++ Q_ASSERT_SUCCEEDED(hr);
++ }
++ }
++
++ void QWebView2WebViewPrivate::runJavaScript(
++ const QString &script, const std::function<void(const QVariant &)> &resultCallback)
++ {
++ if (m_webview) {
++ m_webview->ExecuteScript(
++ (wchar_t *)script.utf16(),
++ Microsoft::WRL::Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
++ [this, resultCallback](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();
++ }
++ }
++ QVariant r =
++ errorCode == S_OK ? resultVariant : qt_error_string(errorCode);
++ if (resultCallback)
++ resultCallback(r);
++ return errorCode;
++ })
++ .Get());
++ } else {
++ if (resultCallback)
++ resultCallback(QVariant());
++ }
++ }
++
++ QWebViewSettingsPrivate *QWebView2WebViewPrivate::settings() const
++ {
++ return m_settings;
++ }