summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYigit Akcay <yigit.akcay@qt.io>2023-02-15 20:43:25 +0100
committerYigit Akcay <yigit.akcay@qt.io>2023-03-10 17:13:32 +0100
commit5e257fb57a211f95556ec387fe6f262a60cbb6fe (patch)
tree330abbb7f84f8664fbd44b80d573265da4cc2bcb
parent28a2cfe4116f7218b33df811b79536c0593ddda6 (diff)
QWebEngineUrlResponseInterceptor: Implement new URL response interceptor
This patch adds the QWebEngineUrlResponseInterceptor abstract class, which, when implemented, allows a user to intercept response headers and modify them in any way they like. A response interceptor can be set via QWebEngineProfile::setUrlResponseInterceptor(), QQuickWebEngineProfile::setUrlResponseInterceptor() or QWebEnginePage::setUrlResponseInterceptor(). Also, the QWebEngineUrlResponseInfo class is implemented, which contains the request and response data to be used with the response interceptor. If a response interceptor is set in the profile and page, the one in the profile takes precedence. Fixes: QTBUG-61071 Change-Id: I484d14373ff597b1d531541c066f0102bae28c72 Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
-rw-r--r--src/core/api/CMakeLists.txt3
-rw-r--r--src/core/api/qwebenginepage.cpp20
-rw-r--r--src/core/api/qwebenginepage.h2
-rw-r--r--src/core/api/qwebengineprofile.cpp24
-rw-r--r--src/core/api/qwebengineprofile.h2
-rw-r--r--src/core/api/qwebengineurlresponseinfo.cpp79
-rw-r--r--src/core/api/qwebengineurlresponseinfo.h49
-rw-r--r--src/core/api/qwebengineurlresponseinfo_p.h46
-rw-r--r--src/core/api/qwebengineurlresponseinterceptor.h27
-rw-r--r--src/core/doc/src/qwebengineurlresponseinterceptor.qdoc45
-rw-r--r--src/core/net/proxying_url_loader_factory_qt.cpp67
-rw-r--r--src/core/profile_adapter.cpp10
-rw-r--r--src/core/profile_adapter.h5
-rw-r--r--src/core/web_contents_adapter.cpp10
-rw-r--r--src/core/web_contents_adapter.h3
-rw-r--r--src/webenginequick/api/qquickwebengineprofile.cpp13
-rw-r--r--src/webenginequick/api/qquickwebengineprofile.h2
-rw-r--r--tests/auto/core/CMakeLists.txt1
-rw-r--r--tests/auto/core/qwebengineurlresponseinterceptor/CMakeLists.txt12
-rw-r--r--tests/auto/core/qwebengineurlresponseinterceptor/tst_qwebengineurlresponseinterceptor.cpp140
20 files changed, 556 insertions, 4 deletions
diff --git a/src/core/api/CMakeLists.txt b/src/core/api/CMakeLists.txt
index 0ee286031..4be37d856 100644
--- a/src/core/api/CMakeLists.txt
+++ b/src/core/api/CMakeLists.txt
@@ -33,7 +33,8 @@ qt_internal_add_module(WebEngineCore
qwebenginescriptcollection.cpp qwebenginescriptcollection.h qwebenginescriptcollection_p.h
qwebenginesettings.cpp qwebenginesettings.h
qwebengineurlrequestinfo.cpp qwebengineurlrequestinfo.h qwebengineurlrequestinfo_p.h
- qwebengineurlrequestinterceptor.h
+ qwebengineurlrequestinterceptor.h qwebengineurlresponseinterceptor.h
+ qwebengineurlresponseinfo.cpp qwebengineurlresponseinfo.h qwebengineurlresponseinfo_p.h
qwebengineurlrequestjob.cpp qwebengineurlrequestjob.h
qwebengineurlscheme.cpp qwebengineurlscheme.h
qwebengineurlschemehandler.cpp qwebengineurlschemehandler.h
diff --git a/src/core/api/qwebenginepage.cpp b/src/core/api/qwebenginepage.cpp
index db795f846..21007f852 100644
--- a/src/core/api/qwebenginepage.cpp
+++ b/src/core/api/qwebenginepage.cpp
@@ -1730,6 +1730,26 @@ void QWebEnginePage::setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor *i
d->adapter->setRequestInterceptor(interceptor);
}
+/*!
+ \since 6.6
+
+ Registers the response interceptor \a interceptor to intercept URL response headers.
+
+ The page does not take ownership of the pointer. This interceptor is called
+ after any interceptors on the profile, and unlike profile interceptors, only
+ URL responses to this page are intercepted.
+
+ To unset the response interceptor, set a \c nullptr.
+
+ \sa QWebEngineUrlResponseInterceptor, QWebEngineProfile::setUrlResponseInterceptor()
+*/
+
+void QWebEnginePage::setUrlResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor)
+{
+ Q_D(QWebEnginePage);
+ d->adapter->setResponseInterceptor(interceptor);
+}
+
void QWebEnginePage::setFeaturePermission(const QUrl &securityOrigin, QWebEnginePage::Feature feature, QWebEnginePage::PermissionPolicy policy)
{
Q_D(QWebEnginePage);
diff --git a/src/core/api/qwebenginepage.h b/src/core/api/qwebenginepage.h
index e58cb3a84..e10b78d26 100644
--- a/src/core/api/qwebenginepage.h
+++ b/src/core/api/qwebenginepage.h
@@ -40,6 +40,7 @@ class QWebEngineRegisterProtocolHandlerRequest;
class QWebEngineScriptCollection;
class QWebEngineSettings;
class QWebEngineUrlRequestInterceptor;
+class QWebEngineUrlResponseInterceptor;
class Q_WEBENGINECORE_EXPORT QWebEnginePage : public QObject
{
@@ -287,6 +288,7 @@ public:
QWebEnginePage *devToolsPage() const;
void setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor);
+ void setUrlResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor);
LifecycleState lifecycleState() const;
void setLifecycleState(LifecycleState state);
diff --git a/src/core/api/qwebengineprofile.cpp b/src/core/api/qwebengineprofile.cpp
index ab873e345..3a4c899d9 100644
--- a/src/core/api/qwebengineprofile.cpp
+++ b/src/core/api/qwebengineprofile.cpp
@@ -62,9 +62,12 @@ using QtWebEngineCore::ProfileAdapter;
The default profile can be accessed by defaultProfile(). It is a built-in profile that all
web pages not specifically created with another profile belong to.
- Implementing the QWebEngineUrlRequestInterceptor interface and registering the interceptor on a
- profile by setUrlRequestInterceptor() enables intercepting, blocking, and modifying URL
- requests (QWebEngineUrlRequestInfo) before they reach the networking stack of Chromium.
+ You can implement interceptor interfaces for URL requests and responses and register them
+ on a profile to intercept, block, or modify URL requests before they reach the networking
+ stack of Chromium or response headers right after they come off the networking stack of
+ Chromium. For requests, implement QWebEngineUrlRequestInterceptor and register it via
+ setUrlRequestInterceptor(). For response headers, implement QWebEngineUrlResponseInterceptor
+ and register it via setUrlResponseInterceptor().
A QWebEngineUrlSchemeHandler can be registered for a profile by installUrlSchemeHandler()
to add support for custom URL schemes. Requests for the scheme are then issued to
@@ -603,6 +606,21 @@ void QWebEngineProfile::setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor
}
/*!
+ Registers a response interceptor singleton \a interceptor to intercept URL response headers.
+
+ The profile does not take ownership of the pointer.
+
+ \since 6.6
+ \sa QWebEngineUrlResponseInterceptor
+*/
+
+void QWebEngineProfile::setUrlResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor)
+{
+ Q_D(QWebEngineProfile);
+ d->profileAdapter()->setResponseInterceptor(interceptor);
+}
+
+/*!
Clears all links from the visited links database.
\sa clearVisitedLinks()
diff --git a/src/core/api/qwebengineprofile.h b/src/core/api/qwebengineprofile.h
index 9fb4c8e74..bd051c784 100644
--- a/src/core/api/qwebengineprofile.h
+++ b/src/core/api/qwebengineprofile.h
@@ -24,6 +24,7 @@ class QWebEngineProfilePrivate;
class QWebEngineSettings;
class QWebEngineScriptCollection;
class QWebEngineUrlRequestInterceptor;
+class QWebEngineUrlResponseInterceptor;
class QWebEngineUrlSchemeHandler;
class Q_WEBENGINECORE_EXPORT QWebEngineProfile : public QObject
@@ -74,6 +75,7 @@ public:
QWebEngineCookieStore *cookieStore();
void setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor);
+ void setUrlResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor);
void clearAllVisitedLinks();
void clearVisitedLinks(const QList<QUrl> &urls);
diff --git a/src/core/api/qwebengineurlresponseinfo.cpp b/src/core/api/qwebengineurlresponseinfo.cpp
new file mode 100644
index 000000000..8ec1024da
--- /dev/null
+++ b/src/core/api/qwebengineurlresponseinfo.cpp
@@ -0,0 +1,79 @@
+// Copyright (C) 2023 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 "qwebengineurlresponseinfo.h"
+#include "qwebengineurlresponseinfo_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QWebEngineUrlResponseInfo
+ \brief A utility type for the QWebEngineUrlResponseInterceptor.
+ \inmodule QtWebEngineCore
+ \since 6.6
+
+ Contains information about the request that has caused the response
+ intercepted by a QWebEngineUrlResponseInterceptor.
+
+ \sa QWebEngineUrlResponseInterceptor
+*/
+QWebEngineUrlResponseInfo::QWebEngineUrlResponseInfo(
+ const QUrl &requestUrl, const QHash<QByteArray, QByteArray> &requestHeaders,
+ const QHash<QByteArray, QByteArray> &responseHeaders, QObject *p)
+ : QObject(p)
+ , d_ptr(new QWebEngineUrlResponseInfoPrivate(requestUrl, requestHeaders, responseHeaders))
+{
+}
+
+/*!
+ \property QWebEngineUrlResponseInfo::requestUrl
+ \brief Holds the URL of the URL load request.
+*/
+QUrl QWebEngineUrlResponseInfo::requestUrl() const
+{
+ Q_D(const QWebEngineUrlResponseInfo);
+ return d->requestUrl;
+}
+
+/*!
+ \property QWebEngineUrlResponseInfo::requestHeaders
+ \brief Holds the request headers of the URL load request.
+*/
+QHash<QByteArray, QByteArray> QWebEngineUrlResponseInfo::requestHeaders() const
+{
+ Q_D(const QWebEngineUrlResponseInfo);
+ return d->requestHeaders;
+}
+
+/*!
+ \property QWebEngineUrlResponseInfo::responseHeaders
+ \brief Holds the response headers of the URL load request.
+*/
+QHash<QByteArray, QByteArray> QWebEngineUrlResponseInfo::responseHeaders() const
+{
+ Q_D(const QWebEngineUrlResponseInfo);
+ return d->responseHeaders;
+}
+
+/*!
+ \fn void QWebEngineUrlResponseInfo::setResponseHeaders(
+ const QHash<QByteArray, QByteArray> &newResponseHeaders)
+ \brief Sets the response headers to \a newResponseHeaders.
+
+ Sets the response headers to \a newResponseHeaders. If \a newResponseHeaders
+ differ from the current response headers then
+ QWebEngineUrlResponseInfo::isModified() will now return \c true.
+*/
+void QWebEngineUrlResponseInfo::setResponseHeaders(
+ const QHash<QByteArray, QByteArray> &newResponseHeaders)
+{
+ Q_D(QWebEngineUrlResponseInfo);
+ if (d->responseHeaders != newResponseHeaders) {
+ d->responseHeaders = newResponseHeaders;
+ d->isModified = true;
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qwebengineurlresponseinfo.cpp"
diff --git a/src/core/api/qwebengineurlresponseinfo.h b/src/core/api/qwebengineurlresponseinfo.h
new file mode 100644
index 000000000..e8286b313
--- /dev/null
+++ b/src/core/api/qwebengineurlresponseinfo.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2023 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 QWEBENGINEURLRESPONSEINFO_H
+#define QWEBENGINEURLRESPONSEINFO_H
+
+#include <QtWebEngineCore/qtwebenginecoreglobal.h>
+
+#include <QtCore/QUrl>
+#include <QtCore/QHash>
+#include <QtCore/QObject>
+
+namespace QtWebEngineCore {
+class InterceptedRequest;
+}
+
+QT_BEGIN_NAMESPACE
+
+class QWebEngineUrlResponseInfoPrivate;
+
+class Q_WEBENGINECORE_EXPORT QWebEngineUrlResponseInfo : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QUrl requestUrl READ requestUrl CONSTANT FINAL)
+ Q_PROPERTY(QHash<QByteArray,QByteArray> requestHeaders READ requestHeaders CONSTANT FINAL)
+ Q_PROPERTY(QHash<QByteArray,QByteArray> responseHeaders READ responseHeaders WRITE
+ setResponseHeaders)
+
+public:
+ QWebEngineUrlResponseInfo(const QUrl &requestUrl,
+ const QHash<QByteArray, QByteArray> &requestHeaders,
+ const QHash<QByteArray, QByteArray> &responseHeaders,
+ QObject *p = nullptr);
+
+ QUrl requestUrl() const;
+ QHash<QByteArray, QByteArray> requestHeaders() const;
+ QHash<QByteArray, QByteArray> responseHeaders() const;
+
+ void setResponseHeaders(const QHash<QByteArray, QByteArray> &newResponseHeaders);
+
+private:
+ friend class QtWebEngineCore::InterceptedRequest;
+ Q_DECLARE_PRIVATE(QWebEngineUrlResponseInfo)
+ QWebEngineUrlResponseInfoPrivate *d_ptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWEBENGINEURLRESPONSEINFO_H
diff --git a/src/core/api/qwebengineurlresponseinfo_p.h b/src/core/api/qwebengineurlresponseinfo_p.h
new file mode 100644
index 000000000..0e500e3f0
--- /dev/null
+++ b/src/core/api/qwebengineurlresponseinfo_p.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2023 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 QWEBENGINEURLRESPONSEINFO_P_H
+#define QWEBENGINEURLRESPONSEINFO_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qtwebenginecoreglobal_p.h"
+
+#include <QtCore/QHash>
+#include <QtCore/QUrl>
+
+QT_BEGIN_NAMESPACE
+
+class Q_WEBENGINECORE_PRIVATE_EXPORT QWebEngineUrlResponseInfoPrivate
+{
+public:
+ QWebEngineUrlResponseInfoPrivate(const QUrl &requestUrl,
+ const QHash<QByteArray, QByteArray> &requestHeaders,
+ const QHash<QByteArray, QByteArray> &responseHeaders)
+ : requestUrl(requestUrl)
+ , requestHeaders(requestHeaders)
+ , responseHeaders(responseHeaders)
+ , isModified(false)
+ {
+ }
+
+ QUrl requestUrl;
+ QHash<QByteArray, QByteArray> requestHeaders;
+ QHash<QByteArray, QByteArray> responseHeaders;
+ bool isModified;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWEBENGINEURLRESPONSEINFO_P_H
diff --git a/src/core/api/qwebengineurlresponseinterceptor.h b/src/core/api/qwebengineurlresponseinterceptor.h
new file mode 100644
index 000000000..505acce57
--- /dev/null
+++ b/src/core/api/qwebengineurlresponseinterceptor.h
@@ -0,0 +1,27 @@
+// Copyright (C) 2023 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 QWEBENGINEURLRESPONSEINTERCEPTOR_H
+#define QWEBENGINEURLRESPONSEINTERCEPTOR_H
+
+#include <QtWebEngineCore/qtwebenginecoreglobal.h>
+
+#include <QtCore/QObject>
+
+QT_BEGIN_NAMESPACE
+
+class QWebEngineUrlResponseInfo;
+
+class Q_WEBENGINECORE_EXPORT QWebEngineUrlResponseInterceptor : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(QWebEngineUrlResponseInterceptor)
+public:
+ explicit QWebEngineUrlResponseInterceptor(QObject *p = nullptr) : QObject(p) { }
+
+ virtual void interceptResponseHeaders(QWebEngineUrlResponseInfo &info) = 0;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWEBENGINEURLRESPONSEINTERCEPTOR_H
diff --git a/src/core/doc/src/qwebengineurlresponseinterceptor.qdoc b/src/core/doc/src/qwebengineurlresponseinterceptor.qdoc
new file mode 100644
index 000000000..cbbd88bcf
--- /dev/null
+++ b/src/core/doc/src/qwebengineurlresponseinterceptor.qdoc
@@ -0,0 +1,45 @@
+// Copyright (C) 2023 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
+
+/*!
+ \class QWebEngineUrlResponseInterceptor
+ \since 6.6
+ \brief The QWebEngineUrlResponseInterceptor class provides an abstract base class for
+ URL response interception.
+
+ \inmodule QtWebEngineCore
+
+ Implementing the \l{QWebEngineUrlResponseInterceptor} interface and installing the
+ interceptor on the profile or the page enables intercepting, blocking, and modifying
+ URL responses right as they come off the networking stack. Currently, only the response
+ headers can be accessed and modified.
+
+ You can install the interceptor on a profile via
+ QWebEngineProfile::setUrlResponseInterceptor() or
+ QQuickWebEngineProfile::setUrlResponseInterceptor() and on a page via
+ QWebEnginePage::setUrlResponseInterceptor().
+
+ \sa QWebEngineUrlResponseInterceptor::interceptResponseHeaders(),
+ QWebEngineProfile::setUrlResponseInterceptor(),
+ QQuickWebEngineProfile::setUrlResponseInterceptor(),
+ QWebEnginePage::setUrlResponseInterceptor()
+*/
+
+/*!
+ \fn QWebEngineUrlResponseInterceptor::QWebEngineUrlResponseInterceptor(QObject *p = nullptr)
+
+ Creates a new response interceptor object with \a p as parent.
+*/
+
+/*!
+ \fn void QWebEngineUrlResponseInterceptor::interceptResponseHeaders(
+ QWebEngineUrlResponseInfo &info)
+
+ Reimplement this virtual function to intercept URL
+ response headers. This method stalls the URL load until handled.
+
+ \a info contains the request and response data. Call
+ info.setResponseHeaders() to modify the response headers.
+
+ \sa QWebEngineUrlResponseInfo
+*/
diff --git a/src/core/net/proxying_url_loader_factory_qt.cpp b/src/core/net/proxying_url_loader_factory_qt.cpp
index 70ec61b34..aa333ae02 100644
--- a/src/core/net/proxying_url_loader_factory_qt.cpp
+++ b/src/core/net/proxying_url_loader_factory_qt.cpp
@@ -20,11 +20,14 @@
#include "url/url_util_qt.h"
#include "api/qwebengineurlrequestinfo_p.h"
+#include "api/qwebengineurlresponseinfo_p.h"
#include "type_conversion.h"
#include "web_contents_adapter.h"
#include "web_contents_adapter_client.h"
#include "web_contents_view_qt.h"
+#include <QtWebEngineCore/QWebEngineUrlResponseInfo>
+
// originally based on aw_proxying_url_loader_factory.cc:
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
@@ -148,6 +151,11 @@ private:
content::WebContents* webContents();
QWebEngineUrlRequestInterceptor* getProfileInterceptor();
QWebEngineUrlRequestInterceptor* getPageInterceptor();
+ QWebEngineUrlResponseInterceptor *getProfileResponseInterceptor();
+ QWebEngineUrlResponseInterceptor *getPageResponseInterceptor();
+
+ void interceptResponseHeaders(QWebEngineUrlResponseInterceptor *const interceptor,
+ net::HttpResponseHeaders *const responseHeadersPtr);
QPointer<ProfileAdapter> profile_adapter_;
const int frame_tree_node_id_;
@@ -252,6 +260,59 @@ QWebEngineUrlRequestInterceptor* InterceptedRequest::getPageInterceptor()
return nullptr;
}
+QWebEngineUrlResponseInterceptor* InterceptedRequest::getProfileResponseInterceptor()
+{
+ return profile_adapter_ ? profile_adapter_->responseInterceptor() : nullptr;
+}
+
+QWebEngineUrlResponseInterceptor* InterceptedRequest::getPageResponseInterceptor()
+{
+ if (auto wc = webContents()) {
+ auto view = static_cast<content::WebContentsImpl *>(wc)->GetView();
+ if (WebContentsAdapterClient *client = WebContentsViewQt::from(view)->client())
+ return client->webContentsAdapter()->responseInterceptor();
+ }
+ return nullptr;
+}
+
+void InterceptedRequest::interceptResponseHeaders(
+ QWebEngineUrlResponseInterceptor *const interceptor,
+ net::HttpResponseHeaders *const responseHeadersPtr)
+{
+ QHash<QByteArray, QByteArray> responseHeaders;
+ std::unordered_set<std::string> headersToRemove;
+ {
+ std::size_t iter = 0;
+ std::string name;
+ std::string value;
+ while (responseHeadersPtr->EnumerateHeaderLines(&iter, &name, &value)) {
+ responseHeaders.insert(QByteArray::fromStdString(name),
+ QByteArray::fromStdString(value));
+ headersToRemove.insert(name);
+ }
+ }
+
+ const QUrl requestUrl = QUrl::fromEncoded(QByteArray::fromStdString(request_.url.spec()));
+ const QHash<QByteArray, QByteArray> requestHeaders =
+ [](const net::HttpRequestHeaders &headers) {
+ QHash<QByteArray, QByteArray> result;
+ for (const auto &header : headers.GetHeaderVector()) {
+ result.insert(QByteArray::fromStdString(header.key),
+ QByteArray::fromStdString(header.value));
+ }
+ return result;
+ }(request_.headers);
+
+ QWebEngineUrlResponseInfo info(requestUrl, requestHeaders, responseHeaders);
+ interceptor->interceptResponseHeaders(info);
+
+ if (info.d_ptr->isModified) {
+ responseHeadersPtr->RemoveHeaders(headersToRemove);
+ for (auto it = info.responseHeaders().cbegin(); it != info.responseHeaders().cend(); ++it)
+ responseHeadersPtr->AddHeader(it.key().toStdString(), it.value().toStdString());
+ }
+}
+
void InterceptedRequest::Restart()
{
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -404,6 +465,12 @@ void InterceptedRequest::ContinueAfterIntercept()
void InterceptedRequest::OnReceiveResponse(network::mojom::URLResponseHeadPtr head, mojo::ScopedDataPipeConsumerHandle handle, absl::optional<mojo_base::BigBuffer> buffer)
{
+ QWebEngineUrlResponseInterceptor *const responseInterceptor = getProfileResponseInterceptor()
+ ? getProfileResponseInterceptor()
+ : getPageResponseInterceptor();
+ if (responseInterceptor)
+ interceptResponseHeaders(responseInterceptor, head->headers.get());
+
current_response_ = head.Clone();
target_client_->OnReceiveResponse(std::move(head), std::move(handle), std::move(buffer));
diff --git a/src/core/profile_adapter.cpp b/src/core/profile_adapter.cpp
index e9e9aaeda..1b71744c9 100644
--- a/src/core/profile_adapter.cpp
+++ b/src/core/profile_adapter.cpp
@@ -191,6 +191,16 @@ void ProfileAdapter::setRequestInterceptor(QWebEngineUrlRequestInterceptor *inte
m_requestInterceptor = interceptor;
}
+QWebEngineUrlResponseInterceptor *ProfileAdapter::responseInterceptor()
+{
+ return m_responseInterceptor.data();
+}
+
+void ProfileAdapter::setResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor)
+{
+ m_responseInterceptor = interceptor;
+}
+
void ProfileAdapter::addClient(ProfileAdapterClient *adapterClient)
{
m_clients.append(adapterClient);
diff --git a/src/core/profile_adapter.h b/src/core/profile_adapter.h
index ab4622a4b..a45a12e5c 100644
--- a/src/core/profile_adapter.h
+++ b/src/core/profile_adapter.h
@@ -27,6 +27,7 @@
#include <QtWebEngineCore/qwebengineclientcertificatestore.h>
#include <QtWebEngineCore/qwebenginecookiestore.h>
#include <QtWebEngineCore/qwebengineurlrequestinterceptor.h>
+#include <QtWebEngineCore/qwebengineurlresponseinterceptor.h>
#include <QtWebEngineCore/qwebengineurlschemehandler.h>
#include "net/qrc_url_scheme_handler.h"
@@ -64,6 +65,9 @@ public:
QWebEngineUrlRequestInterceptor* requestInterceptor();
void setRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor);
+ QWebEngineUrlResponseInterceptor *responseInterceptor();
+ void setResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor);
+
QList<ProfileAdapterClient*> clients() { return m_clients; }
void addClient(ProfileAdapterClient *adapterClient);
void removeClient(ProfileAdapterClient *adapterClient);
@@ -209,6 +213,7 @@ private:
QWebEngineClientCertificateStore *m_clientCertificateStore = nullptr;
#endif
QPointer<QWebEngineUrlRequestInterceptor> m_requestInterceptor;
+ QPointer<QWebEngineUrlResponseInterceptor> m_responseInterceptor;
QString m_dataPath;
QString m_downloadPath;
diff --git a/src/core/web_contents_adapter.cpp b/src/core/web_contents_adapter.cpp
index 5b880d31a..997019b03 100644
--- a/src/core/web_contents_adapter.cpp
+++ b/src/core/web_contents_adapter.cpp
@@ -1013,6 +1013,16 @@ QWebEngineUrlRequestInterceptor* WebContentsAdapter::requestInterceptor() const
return m_requestInterceptor;
}
+void WebContentsAdapter::setResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor)
+{
+ m_responseInterceptor = interceptor;
+}
+
+QWebEngineUrlResponseInterceptor *WebContentsAdapter::responseInterceptor() const
+{
+ return m_responseInterceptor;
+}
+
#if QT_CONFIG(accessibility)
QAccessibleInterface *WebContentsAdapter::browserAccessible()
{
diff --git a/src/core/web_contents_adapter.h b/src/core/web_contents_adapter.h
index 0a97fde56..1e1b8bdc4 100644
--- a/src/core/web_contents_adapter.h
+++ b/src/core/web_contents_adapter.h
@@ -210,6 +210,8 @@ public:
void updateRecommendedState();
void setRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor);
QWebEngineUrlRequestInterceptor* requestInterceptor() const;
+ void setResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor);
+ QWebEngineUrlResponseInterceptor *responseInterceptor() const;
private:
Q_DISABLE_COPY(WebContentsAdapter)
@@ -251,6 +253,7 @@ private:
LifecycleState m_recommendedState = LifecycleState::Active;
bool m_inspector = false;
QPointer<QWebEngineUrlRequestInterceptor> m_requestInterceptor;
+ QPointer<QWebEngineUrlResponseInterceptor> m_responseInterceptor;
};
} // namespace QtWebEngineCore
diff --git a/src/webenginequick/api/qquickwebengineprofile.cpp b/src/webenginequick/api/qquickwebengineprofile.cpp
index 8292da894..a830e969e 100644
--- a/src/webenginequick/api/qquickwebengineprofile.cpp
+++ b/src/webenginequick/api/qquickwebengineprofile.cpp
@@ -902,6 +902,19 @@ void QQuickWebEngineProfile::setUrlRequestInterceptor(QWebEngineUrlRequestInterc
d->profileAdapter()->setRequestInterceptor(interceptor);
}
+/*!
+ Registers a response interceptor singleton \a interceptor to intercept URL responses.
+
+ The profile does not take ownership of the pointer.
+
+ \sa QWebEngineUrlResponseInterceptor
+*/
+void QQuickWebEngineProfile::setUrlResponseInterceptor(
+ QWebEngineUrlResponseInterceptor *interceptor)
+{
+ Q_D(QQuickWebEngineProfile);
+ d->profileAdapter()->setResponseInterceptor(interceptor);
+}
/*!
Returns the custom URL scheme handler register for the URL scheme \a scheme.
diff --git a/src/webenginequick/api/qquickwebengineprofile.h b/src/webenginequick/api/qquickwebengineprofile.h
index 29d6ee0b2..43b02e45a 100644
--- a/src/webenginequick/api/qquickwebengineprofile.h
+++ b/src/webenginequick/api/qquickwebengineprofile.h
@@ -8,6 +8,7 @@
#include <QtCore/qobject.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qstring.h>
+#include <QtWebEngineCore/qwebengineurlresponseinterceptor.h>
#include <QtQml/qqmlregistration.h>
QT_BEGIN_NAMESPACE
@@ -90,6 +91,7 @@ public:
QWebEngineCookieStore *cookieStore() const;
void setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor);
+ void setUrlResponseInterceptor(QWebEngineUrlResponseInterceptor *interceptor);
const QWebEngineUrlSchemeHandler *urlSchemeHandler(const QByteArray &) const;
void installUrlSchemeHandler(const QByteArray &scheme, QWebEngineUrlSchemeHandler *);
diff --git a/tests/auto/core/CMakeLists.txt b/tests/auto/core/CMakeLists.txt
index 6ba6ffd92..9d131da56 100644
--- a/tests/auto/core/CMakeLists.txt
+++ b/tests/auto/core/CMakeLists.txt
@@ -5,6 +5,7 @@ add_subdirectory(qwebenginecookiestore)
add_subdirectory(qwebengineloadinginfo)
add_subdirectory(qwebenginesettings)
add_subdirectory(qwebengineurlrequestinterceptor)
+add_subdirectory(qwebengineurlresponseinterceptor)
add_subdirectory(qwebengineurlrequestjob)
add_subdirectory(origins)
add_subdirectory(devtools)
diff --git a/tests/auto/core/qwebengineurlresponseinterceptor/CMakeLists.txt b/tests/auto/core/qwebengineurlresponseinterceptor/CMakeLists.txt
new file mode 100644
index 000000000..08a5b9699
--- /dev/null
+++ b/tests/auto/core/qwebengineurlresponseinterceptor/CMakeLists.txt
@@ -0,0 +1,12 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+include(../../httpserver/httpserver.cmake)
+
+qt_internal_add_test(tst_qwebengineurlresponseinterceptor
+ SOURCES
+ tst_qwebengineurlresponseinterceptor.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+ Test::HttpServer
+)
diff --git a/tests/auto/core/qwebengineurlresponseinterceptor/tst_qwebengineurlresponseinterceptor.cpp b/tests/auto/core/qwebengineurlresponseinterceptor/tst_qwebengineurlresponseinterceptor.cpp
new file mode 100644
index 000000000..4d08853ef
--- /dev/null
+++ b/tests/auto/core/qwebengineurlresponseinterceptor/tst_qwebengineurlresponseinterceptor.cpp
@@ -0,0 +1,140 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QtTest/QtTest>
+#include <QtWebEngineCore/QWebEngineUrlResponseInterceptor>
+#include <QtWebEngineCore/QWebEngineProfile>
+#include <QtWebEngineCore/QWebEnginePage>
+#include <QtWebEngineCore/QWebEngineLoadingInfo>
+#include <QtWebEngineCore/QWebEngineUrlResponseInfo>
+
+#include <httpserver.h>
+#include <httpreqrep.h>
+
+class tst_QWebEngineUrlResponseInterceptor : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QWebEngineUrlResponseInterceptor() { }
+ ~tst_QWebEngineUrlResponseInterceptor() { }
+
+public Q_SLOTS:
+ void init() { }
+ void cleanup() { }
+
+private Q_SLOTS:
+ void initTestCase() { }
+ void cleanupTestCase() { }
+ void interceptRequest_data();
+ void interceptRequest();
+};
+
+Q_LOGGING_CATEGORY(lc, "qt.webengine.tests")
+
+class Interceptor : public QWebEngineUrlResponseInterceptor
+{
+ Q_OBJECT
+
+ QUrl m_receivedRequestUrl;
+ QHash<QByteArray, QByteArray> m_receivedRequestHeaders;
+
+public:
+ void interceptResponseHeaders(QWebEngineUrlResponseInfo &info) override
+ {
+ m_receivedRequestUrl = info.requestUrl();
+ m_receivedRequestHeaders = info.requestHeaders();
+ QHash<QByteArray, QByteArray> responseHeaders = info.responseHeaders();
+
+ responseHeaders.insert(QByteArrayLiteral("ADDEDHEADER"), QByteArrayLiteral("ADDEDVALUE"));
+ *(responseHeaders.find(QByteArrayLiteral("content-length"))) = QByteArrayLiteral("57");
+
+ info.setResponseHeaders(responseHeaders);
+ }
+
+ void getReceivedRequest(QUrl *receivedRequestUrl,
+ QHash<QByteArray, QByteArray> *receivedRequestHeaders)
+ {
+ *receivedRequestUrl = m_receivedRequestUrl;
+ *receivedRequestHeaders = m_receivedRequestHeaders;
+ }
+};
+
+void tst_QWebEngineUrlResponseInterceptor::interceptRequest_data()
+{
+ QTest::addColumn<bool>("withProfileInterceptor");
+ QTest::newRow("with profile interceptor") << true;
+ QTest::newRow("with page interceptor") << false;
+}
+
+void tst_QWebEngineUrlResponseInterceptor::interceptRequest()
+{
+ QFETCH(bool, withProfileInterceptor);
+
+ HttpServer httpServer;
+ QObject::connect(&httpServer, &HttpServer::newRequest, this, [&](HttpReqRep *rr) {
+ if (rr->requestPath() == QByteArrayLiteral("/okay.html")) {
+ rr->setResponseBody(QByteArrayLiteral(
+ "<html><script>console.log('hello world js!');</script></html>"));
+ rr->sendResponse();
+ }
+ });
+ QVERIFY(httpServer.start());
+
+ const QUrl requestUrl = httpServer.url(QStringLiteral("/okay.html"));
+
+ Interceptor interceptor;
+ QWebEngineProfile profile;
+ QWebEnginePage page(&profile);
+ if (withProfileInterceptor)
+ profile.setUrlResponseInterceptor(&interceptor);
+ else
+ page.setUrlResponseInterceptor(&interceptor);
+
+ QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
+
+ bool headersWereChanged = false;
+ QObject::connect(
+ &page, &QWebEnginePage::loadingChanged, this, [&](QWebEngineLoadingInfo loadingInfo) {
+ const QHash<QByteArray, QByteArray> responseHeaders = loadingInfo.responseHeaders();
+ bool contentLengthSizeChanged = false;
+ bool additionalHeaderAdded = false;
+ for (auto it = responseHeaders.constBegin(); it != responseHeaders.constEnd();
+ ++it) {
+ if (it.key() == QByteArrayLiteral("content-length")
+ && it.value() == QByteArrayLiteral("57"))
+ contentLengthSizeChanged = true;
+ if (it.key() == QByteArrayLiteral("ADDEDHEADER")
+ && it.value() == QByteArrayLiteral("ADDEDVALUE"))
+ additionalHeaderAdded = true;
+ }
+
+ if (contentLengthSizeChanged && additionalHeaderAdded)
+ headersWereChanged = true;
+ });
+
+ page.load(requestUrl);
+
+ QVERIFY(loadSpy.wait());
+
+ QUrl receivedRequestUrl;
+ QHash<QByteArray, QByteArray> receivedRequestHeaders;
+ interceptor.getReceivedRequest(&receivedRequestUrl, &receivedRequestHeaders);
+
+ bool receivedRequestHeadersContainsQtWebEngine = false;
+ for (auto it = receivedRequestHeaders.cbegin(); it != receivedRequestHeaders.cend(); ++it) {
+ if (it.value().contains("QtWebEngine/")) {
+ receivedRequestHeadersContainsQtWebEngine = true;
+ break;
+ }
+ }
+
+ QVERIFY(headersWereChanged);
+ QCOMPARE_EQ(receivedRequestUrl, requestUrl);
+ QVERIFY(receivedRequestHeaders.size() != 0);
+ QVERIFY(receivedRequestHeadersContainsQtWebEngine);
+ QVERIFY(httpServer.stop());
+}
+
+QTEST_MAIN(tst_QWebEngineUrlResponseInterceptor)
+#include "tst_qwebengineurlresponseinterceptor.moc"