summaryrefslogtreecommitdiffstats
path: root/tests/auto/core
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/core')
-rw-r--r--tests/auto/core/CMakeLists.txt25
-rw-r--r--tests/auto/core/certificateerror/BLACKLIST2
-rw-r--r--tests/auto/core/certificateerror/CMakeLists.txt26
-rw-r--r--tests/auto/core/certificateerror/resources/server.key27
-rw-r--r--tests/auto/core/certificateerror/resources/server.pem41
-rw-r--r--tests/auto/core/certificateerror/tst_certificateerror.cpp134
-rw-r--r--tests/auto/core/core.pro9
-rw-r--r--tests/auto/core/devtools/CMakeLists.txt10
-rw-r--r--tests/auto/core/devtools/tst_devtools.cpp69
-rw-r--r--tests/auto/core/getdomainandregistry/CMakeLists.txt12
-rw-r--r--tests/auto/core/getdomainandregistry/tst_getdomainandregistry.cpp30
-rw-r--r--tests/auto/core/origins/CMakeLists.txt52
-rw-r--r--tests/auto/core/origins/resources/createObjectURL.html11
-rw-r--r--tests/auto/core/origins/resources/dedicatedWorker.html19
-rw-r--r--tests/auto/core/origins/resources/dedicatedWorker.js1
-rw-r--r--tests/auto/core/origins/resources/fetchApi.html14
-rw-r--r--tests/auto/core/origins/resources/link.html13
-rw-r--r--tests/auto/core/origins/resources/media.html15
-rw-r--r--tests/auto/core/origins/resources/mixedSchemes.html42
-rw-r--r--tests/auto/core/origins/resources/mixedSchemesWithCsp.html32
-rw-r--r--tests/auto/core/origins/resources/mixedSchemes_frame.html15
-rw-r--r--tests/auto/core/origins/resources/mixedXHR.html19
-rw-r--r--tests/auto/core/origins/resources/mixedXHR.txt1
-rw-r--r--tests/auto/core/origins/resources/red.pngbin0 -> 146 bytes
-rw-r--r--tests/auto/core/origins/resources/redirect.css3
-rw-r--r--tests/auto/core/origins/resources/redirect.html17
-rw-r--r--tests/auto/core/origins/resources/serviceWorker.html18
-rw-r--r--tests/auto/core/origins/resources/serviceWorker.js1
-rw-r--r--tests/auto/core/origins/resources/sharedWorker.html19
-rw-r--r--tests/auto/core/origins/resources/sharedWorker.js6
-rw-r--r--tests/auto/core/origins/resources/subdir/frame2.html10
-rw-r--r--tests/auto/core/origins/resources/subdir/index.html26
-rw-r--r--tests/auto/core/origins/resources/subdir_frame1.html10
-rw-r--r--tests/auto/core/origins/resources/viewSource.html9
-rw-r--r--tests/auto/core/origins/resources/websocket.html23
-rw-r--r--tests/auto/core/origins/resources/websocket2.html23
-rw-r--r--tests/auto/core/origins/tst_origins.cpp1730
-rw-r--r--tests/auto/core/qtversion/CMakeLists.txt10
-rw-r--r--tests/auto/core/qtversion/tst_qtversion.cpp34
-rw-r--r--tests/auto/core/qwebengineclientcertificatestore/CMakeLists.txt80
-rw-r--r--tests/auto/core/qwebengineclientcertificatestore/qwebengineclientcertificatestore.pro1
-rw-r--r--tests/auto/core/qwebengineclientcertificatestore/resources/ca.pem24
-rw-r--r--tests/auto/core/qwebengineclientcertificatestore/resources/client.key27
-rw-r--r--tests/auto/core/qwebengineclientcertificatestore/resources/client.pem22
-rw-r--r--tests/auto/core/qwebengineclientcertificatestore/resources/client2.key27
-rw-r--r--tests/auto/core/qwebengineclientcertificatestore/resources/client2.p12bin0 -> 2710 bytes
-rw-r--r--tests/auto/core/qwebengineclientcertificatestore/resources/client2.pem22
-rw-r--r--tests/auto/core/qwebengineclientcertificatestore/resources/server.key27
-rw-r--r--tests/auto/core/qwebengineclientcertificatestore/resources/server.pem22
-rw-r--r--tests/auto/core/qwebengineclientcertificatestore/tst_qwebengineclientcertificatestore.cpp133
-rw-r--r--tests/auto/core/qwebengineclientcertificatestore/tst_qwebengineclientcertificatestore.qrc8
-rw-r--r--tests/auto/core/qwebenginecookiestore/CMakeLists.txt26
-rw-r--r--tests/auto/core/qwebenginecookiestore/qwebenginecookiestore.pro2
-rw-r--r--tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp199
-rw-r--r--tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.qrc6
-rw-r--r--tests/auto/core/qwebengineglobalsettings/CMakeLists.txt30
-rw-r--r--tests/auto/core/qwebengineglobalsettings/cert/RootCA.pem20
-rw-r--r--tests/auto/core/qwebengineglobalsettings/cert/localhost.crt22
-rw-r--r--tests/auto/core/qwebengineglobalsettings/cert/localhost.key28
-rw-r--r--tests/auto/core/qwebengineglobalsettings/tst_qwebengineglobalsettings.cpp130
-rw-r--r--tests/auto/core/qwebengineloadinginfo/CMakeLists.txt10
-rw-r--r--tests/auto/core/qwebengineloadinginfo/tst_qwebengineloadinginfo.cpp93
-rw-r--r--tests/auto/core/qwebenginesettings/BLACKLIST5
-rw-r--r--tests/auto/core/qwebenginesettings/CMakeLists.txt13
-rw-r--r--tests/auto/core/qwebenginesettings/tst_qwebenginesettings.cpp307
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/CMakeLists.txt49
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/qwebengineurlrequestinterceptor.pro2
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/resources/content.html3
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/resources/content2.html6
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/resources/content3.html6
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/resources/postBodyFile.txt3
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/resources/sw.html39
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/resources/sw.js15
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp799
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.qrc24
-rw-r--r--tests/auto/core/qwebengineurlrequestjob/CMakeLists.txt22
-rw-r--r--tests/auto/core/qwebengineurlrequestjob/additionalResponseHeadersScript.html12
-rw-r--r--tests/auto/core/qwebengineurlrequestjob/requestBodyScript.html12
-rw-r--r--tests/auto/core/qwebengineurlrequestjob/tst_qwebengineurlrequestjob.cpp187
-rw-r--r--tests/auto/core/tests.pri17
-rw-r--r--tests/auto/core/webenginedriver/CMakeLists.txt15
-rw-r--r--tests/auto/core/webenginedriver/browser/CMakeLists.txt13
-rw-r--r--tests/auto/core/webenginedriver/browser/main.cpp21
-rw-r--r--tests/auto/core/webenginedriver/resources/input.html5
-rw-r--r--tests/auto/core/webenginedriver/test/CMakeLists.txt26
-rw-r--r--tests/auto/core/webenginedriver/tst_webenginedriver.cpp631
86 files changed, 5330 insertions, 419 deletions
diff --git a/tests/auto/core/CMakeLists.txt b/tests/auto/core/CMakeLists.txt
new file mode 100644
index 000000000..5908756b4
--- /dev/null
+++ b/tests/auto/core/CMakeLists.txt
@@ -0,0 +1,25 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+add_subdirectory(qwebenginecookiestore)
+add_subdirectory(qwebengineloadinginfo)
+add_subdirectory(qwebenginesettings)
+if(QT_FEATURE_ssl)
+ # only tests doh, and requires ssl
+ add_subdirectory(qwebengineglobalsettings)
+endif()
+add_subdirectory(qwebengineurlrequestinterceptor)
+add_subdirectory(qwebengineurlrequestjob)
+add_subdirectory(origins)
+add_subdirectory(devtools)
+add_subdirectory(getdomainandregistry)
+add_subdirectory(qtversion)
+
+if(QT_FEATURE_ssl)
+ add_subdirectory(qwebengineclientcertificatestore)
+ add_subdirectory(certificateerror)
+endif()
+
+if(QT_FEATURE_webenginedriver)
+ add_subdirectory(webenginedriver)
+endif()
diff --git a/tests/auto/core/certificateerror/BLACKLIST b/tests/auto/core/certificateerror/BLACKLIST
new file mode 100644
index 000000000..a8fd16bf3
--- /dev/null
+++ b/tests/auto/core/certificateerror/BLACKLIST
@@ -0,0 +1,2 @@
+[fatalError]
+*
diff --git a/tests/auto/core/certificateerror/CMakeLists.txt b/tests/auto/core/certificateerror/CMakeLists.txt
new file mode 100644
index 000000000..6223ca25c
--- /dev/null
+++ b/tests/auto/core/certificateerror/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+include(../../httpserver/httpserver.cmake)
+include(../../util/util.cmake)
+
+qt_internal_add_test(tst_certificateerror
+ SOURCES
+ tst_certificateerror.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+ Test::HttpServer
+ Test::Util
+)
+
+set(tst_certificateerror_resource_files
+ "resources/server.pem"
+ "resources/server.key"
+)
+
+qt_internal_add_resource(tst_certificateerror "tst_certificateerror"
+ PREFIX
+ "/"
+ FILES
+ ${tst_certificateerror_resource_files}
+)
diff --git a/tests/auto/core/certificateerror/resources/server.key b/tests/auto/core/certificateerror/resources/server.key
new file mode 100644
index 000000000..9bf87aee3
--- /dev/null
+++ b/tests/auto/core/certificateerror/resources/server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAqAygFPG5ILLb3G51D0OIN4Kpm5t3Oh1nByTnvi1kMz+sCBBd
+CSugt4NnKkB6kiGtMEsrEm1/xg8Bkfbpet3v3+jAidRpjvCISqy3Z9D1cgCFM46h
+iob/AvLZpqITiAgsU4fJ4auuIKhFplIGrIKMv2gK8haoBGBoRUD1RM+irwjEr6TN
+XTQt2Ap+Ouxs53NLPhAOgumpfzzRR8/Umbhen+G5MhH+XTzzreiClz2V6A79ePJj
+y1uQ8NJ79feOOWBDRizRDWwxsnNd24GjkpvcaTwafiK6Vdqeub+XTtiB5RPal2on
+Cj0TQDcnaacecl/zmUWsIFNkNJWDcd3/vEdyOQIDAQABAoIBAQCW93icOCdim6tu
+FIDu7HEjxSsPUpPCToWu4lWaAHcinxGx0NlzkpD4K4DzcSdrvfszBmQ0UtBVokd7
+1IAdU+HZmePWLk+CDM2zoAPHrO3Cs3r2PS0cIHhZMsearcG0E/uWMseHB08PoXuo
+lcnPEhzVGueyYe4guGcTx+5PGeUBLf+fJcEc3rIQnT2LYulM2aqBZSQM3jRUaPYs
+F0awDpCNwajW/Bt2VB14Pr+H5MJ+WSznFCqW7SolBkqDGfKckXPSHgX6xZ0y7VCI
+MM8vwlVI4mPkaHvSQMSI8vS4Qh+SGQCSs/AuuNLjjPoz1YotV3Ih4YbLj6BjFP2g
+CrqzT6VNAoGBANOHmsqE0nRkLzonTDrMdla5b0TjTxwtNM5DjLgJa6UBBqPe+1Lv
+JFoBP9bIfYDRWZOZrxXItfMmM43nK/ST6Xqgx1IpHUCLKVr2pA9RXrP+m4oawfgn
+frW212fHibeOYiLy+DaQXQ0VRFxsc/VbwKVyVlMEcNg3N93x2E67M7vjAoGBAMtg
+7wDa+5gjwuyNr7LKkp5VDTmtKQhoDtg4sw6MSQSMF6fJT9Z4kGTZ23+G85/LsM/k
+iXbceabGJ0CQJvGn6oW4dI2Ut2c2nCNVbQCxJ6Nyn/yW7bRLShMnwXvbGAVxVUax
+5ohJPZGJ8ar2CP76A0bkvm2Nwylq2gp6Y8h7+iwzAoGBAKizwfQ6sk45iKDsrpNG
+dir8gY2DbJigRTksDpLIkJ1skAspz295YpiV3oBCLjYKwVJCg6zwAo0FrqBB+oB5
+ZwByMgWI3NeZJUZy5q2Ay/Lp4MroRELR3PC3/lu6fE90szgEZ4m84TmJ+Jdtt527
+q41H/yj+pbELePb95vIDw2LZAoGBAJBZ+MmupCzUFSI5Xp+UUIS48W4ijaE92mt1
+swF8aMcleBTLOjOL11D9oGHfs0OUG6czGq6WxnGs62dT6ZBUEo1e4rsq9xH3HNOn
+anq3Qt8sGIn7xjPVzHnUGeyDEYWrb0+CLZJGCcEnG7SwdKolYfYLnW281Oysvp35
+SKGf/W0pAoGAa2+sZmhb1mpGAf6Bi4z+uym/6qOJmG6CnrBSM9e/r8nujwFVkCYF
+3iz48qx3GbuliO6za8aM1drX2u8KWp1uP5KzwYvtW5SfpQ1eusFblHEYQQNRcKLT
+j/wZBXnU961eMKkkTe2XsPirO8rVhVmxuFLqT/aEPffcragQFFIGOEQ=
+-----END RSA PRIVATE KEY-----
diff --git a/tests/auto/core/certificateerror/resources/server.pem b/tests/auto/core/certificateerror/resources/server.pem
new file mode 100644
index 000000000..a201ed08e
--- /dev/null
+++ b/tests/auto/core/certificateerror/resources/server.pem
@@ -0,0 +1,41 @@
+-----BEGIN CERTIFICATE-----
+MIIDezCCAmOgAwIBAgIUFZEIIzeR7lEA10rb14w7MfhP87MwDQYJKoZIhvcNAQEL
+BQAwWjELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVy
+bGluMRUwEwYDVQQKDAxUaGVRdENvbXBhbnkxEjAQBgNVBAsMCXdlYmVuZ2luZTAe
+Fw0yMTA1MTAyMTM1MTJaFw0yMjA1MTAyMTM1MTJaMGAxCzAJBgNVBAYTAkRFMQ8w
+DQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEVMBMGA1UECgwMVGhlUXRD
+b21wYW55MRgwFgYDVQQDDA93ZWJlbmdpbmUucXQuaW8wggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQCoDKAU8bkgstvcbnUPQ4g3gqmbm3c6HWcHJOe+LWQz
+P6wIEF0JK6C3g2cqQHqSIa0wSysSbX/GDwGR9ul63e/f6MCJ1GmO8IhKrLdn0PVy
+AIUzjqGKhv8C8tmmohOICCxTh8nhq64gqEWmUgasgoy/aAryFqgEYGhFQPVEz6Kv
+CMSvpM1dNC3YCn467Gznc0s+EA6C6al/PNFHz9SZuF6f4bkyEf5dPPOt6IKXPZXo
+Dv148mPLW5Dw0nv19445YENGLNENbDGyc13bgaOSm9xpPBp+IrpV2p65v5dO2IHl
+E9qXaicKPRNANydppx5yX/OZRawgU2Q0lYNx3f+8R3I5AgMBAAGjMzAxMBoGA1Ud
+EQQTMBGCD3dlYmVuZ2luZS5xdC5pbzATBgNVHSUEDDAKBggrBgEFBQcDATANBgkq
+hkiG9w0BAQsFAAOCAQEAjThKpP0sBv1vEmaqBc1wTu//7RHmFcoStTt3scADzb2C
+9gjOVC4NzxBneLkv01444Z1p/Iiu/ZZ+VKu7aJElJgnBWEisYwJ09t3cdZRA0UY7
+XRvTVAqV0OlsB1Jn0afE+aTLGjWo+jSYzua0O+NK74e23p9jkdSmXxH9w0FB/oyM
+FGIOFnnfP0+QR4ZVvAGk2H60tBHQKmCM6b87TiD4GQIfOghCQWH+qJYSuyGu4hkE
+uis+n1KHHhed3GIJOHpm7gt1C9qtjcp1nOpv0ycQjfc9CGvr02BcQjhMeO65hX0A
+TvCgKN9/XMFv5jwwjjPCL12GBhwnN2k9hM/tEYpe2A==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDOzCCAiMCFDwWg4NZxCplj3qyBxAUTi1wmj4jMA0GCSqGSIb3DQEBCwUAMFox
+CzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEV
+MBMGA1UECgwMVGhlUXRDb21wYW55MRIwEAYDVQQLDAl3ZWJlbmdpbmUwHhcNMjEw
+NTEwMjEzMTE4WhcNMjIwNTEwMjEzMTE4WjBaMQswCQYDVQQGEwJERTEPMA0GA1UE
+CAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xFTATBgNVBAoMDFRoZVF0Q29tcGFu
+eTESMBAGA1UECwwJd2ViZW5naW5lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAuc/8xVrfSzOsI6kYul+o1QIPBh1I86eQm1PhTBDMAAPHuzyPaEMgBkn2
+XAUmvkynGpNioaJDU2ndV2fBHvsoeQCdNNmjFTe1rKYjrN6U2X5KoYSzN93TOYzK
+aR38fEFx+w4qV76nnxSjYtGNe9z74GrfWFMdDQ0NJKzvaO4gaZ+OOg0OzWy4MJQ0
+aINo3UV55Y7Nt92AxFweiuHucKu+rjf3BX7n0Af/Tcs2c84f0R3HA7euReSibVvX
+f33eHLRKwu2bvDjXiUzOdkxBn9GTo6Q09LyY6wDG0ZdWnyCKj3NBQKBVrq+bs3Q0
+ATsWhj/PvYlZhhZh4EOlqYOhCpwv4wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCC
+pLSFGJcG0zhHW+2A6ogmpn2tA8gKUZx7f0J1nwgPEoAXQqWQv/299ZtmWfMKHUkk
+ygG4u80C87wWPH42XWXo/KDrP9iYzoqAvtqbRuPG9PAxefQ/JUSnuhikA51g9+Mu
+IDKKKSI+y/JW9u0Qo77fp/5n2DaFn5B+pBYvn/xLfaEa9bRdJMTEMsElGbPBzMZd
+I/7X6B78X6Ow5TuRKSeZA7E1AZ/+e5A4Hj65bLAugoSKz3zaS0dV26LwAo18c2zP
+TqtwHyIVj4QCoI6Z694q9KH4Pkml3fz8VSkk+MvZMWapvUhHu/DneTgqGbp9POYg
+nx6oWME6idhnvN6DljxB
+-----END CERTIFICATE-----
diff --git a/tests/auto/core/certificateerror/tst_certificateerror.cpp b/tests/auto/core/certificateerror/tst_certificateerror.cpp
new file mode 100644
index 000000000..67e2d8ae4
--- /dev/null
+++ b/tests/auto/core/certificateerror/tst_certificateerror.cpp
@@ -0,0 +1,134 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include <httpsserver.h>
+#include <util.h>
+
+#include <QWebEngineCertificateError>
+#include <QWebEnginePage>
+#include <QWebEngineSettings>
+
+#include <QtTest/QtTest>
+
+class tst_CertificateError : public QObject
+{
+ Q_OBJECT
+public:
+ tst_CertificateError() { }
+
+private Q_SLOTS:
+ void handleError_data();
+ void handleError();
+ void fatalError();
+};
+
+struct PageWithCertificateErrorHandler : QWebEnginePage
+{
+ Q_OBJECT
+
+public:
+ PageWithCertificateErrorHandler(bool defer, bool accept, QObject *p = nullptr)
+ : QWebEnginePage(p), deferError(defer), acceptCertificate(accept)
+ , loadSpy(this, &QWebEnginePage::loadFinished)
+ {
+ connect(this, &PageWithCertificateErrorHandler::certificateError,
+ this, &PageWithCertificateErrorHandler::onCertificateError);
+ }
+
+ bool deferError, acceptCertificate;
+
+ QSignalSpy loadSpy;
+ QScopedPointer<QWebEngineCertificateError> error;
+
+public Q_SLOTS:
+ void onCertificateError(QWebEngineCertificateError e)
+ {
+ error.reset(new QWebEngineCertificateError(e));
+ if (deferError) {
+ error->defer();
+ return;
+ }
+
+ if (acceptCertificate)
+ error->acceptCertificate();
+ else
+ error->rejectCertificate();
+ }
+};
+
+void tst_CertificateError::handleError_data()
+{
+ QTest::addColumn<bool>("deferError");
+ QTest::addColumn<bool>("acceptCertificate");
+ QTest::addColumn<QString>("expectedContent");
+ QTest::addRow("Reject") << false << false << QString();
+ QTest::addRow("DeferReject") << true << false << QString();
+ QTest::addRow("DeferAccept") << true << true << "TEST";
+}
+
+void tst_CertificateError::handleError()
+{
+ HttpsServer server(":/resources/server.pem", ":/resources/server.key", "");
+ server.setExpectError(false);
+ QVERIFY(server.start());
+
+ connect(&server, &HttpsServer::newRequest, [&] (HttpReqRep *rr) {
+ rr->setResponseBody(QByteArrayLiteral("<html><body>TEST</body></html>"));
+ rr->sendResponse();
+ });
+
+ QFETCH(bool, deferError);
+ QFETCH(bool, acceptCertificate);
+ QFETCH(QString, expectedContent);
+
+ PageWithCertificateErrorHandler page(deferError, acceptCertificate);
+ page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+
+ page.setUrl(server.url());
+ QTRY_VERIFY(page.error);
+ QVERIFY(page.error->isOverridable());
+ auto chain = page.error->certificateChain();
+ QCOMPARE(chain.size(), 2);
+ QCOMPARE(chain[0].serialNumber(), "15:91:08:23:37:91:ee:51:00:d7:4a:db:d7:8c:3b:31:f8:4f:f3:b3");
+ QCOMPARE(chain[1].serialNumber(), "3c:16:83:83:59:c4:2a:65:8f:7a:b2:07:10:14:4e:2d:70:9a:3e:23");
+
+ if (deferError) {
+ QCOMPARE(page.loadSpy.size(), 0);
+ QCOMPARE(toPlainTextSync(&page), QString());
+
+ if (acceptCertificate)
+ page.error->acceptCertificate();
+ else
+ page.error->rejectCertificate();
+
+ page.error.reset();
+ }
+ QTRY_COMPARE_WITH_TIMEOUT(page.loadSpy.size(), 1, 30000);
+ QCOMPARE(page.loadSpy.takeFirst().value(0).toBool(), acceptCertificate);
+ QCOMPARE(toPlainTextSync(&page), expectedContent);
+ QVERIFY(server.stop());
+}
+
+void tst_CertificateError::fatalError()
+{
+ PageWithCertificateErrorHandler page(false, false);
+ page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+ QSignalSpy loadFinishedSpy(&page, &QWebEnginePage::loadFinished);
+
+ page.setUrl(QUrl("https://revoked.badssl.com"));
+ if (!loadFinishedSpy.wait(10000)) {
+ QVERIFY2(!page.error, "There shouldn't be any certificate error if not loaded due to missing internet access!");
+ QSKIP("Couldn't load page from network, skipping test.");
+ }
+
+ // revoked certificate might not be reported as invalid by chromium and the load will silently succeed
+ bool failed = !loadFinishedSpy.first().first().toBool(), hasError = bool(page.error);
+ QCOMPARE(failed, hasError);
+ if (hasError) {
+ QVERIFY(!page.error->isOverridable());
+ // Fatal certificate errors are implicitly rejected. But second call should not cause crash.
+ page.error->rejectCertificate();
+ }
+}
+
+QTEST_MAIN(tst_CertificateError)
+#include <tst_certificateerror.moc>
diff --git a/tests/auto/core/core.pro b/tests/auto/core/core.pro
deleted file mode 100644
index 1dc5e052c..000000000
--- a/tests/auto/core/core.pro
+++ /dev/null
@@ -1,9 +0,0 @@
-TEMPLATE = subdirs
-QT_FOR_CONFIG += network-private
-
-SUBDIRS += \
- qwebenginecookiestore \
- qwebengineurlrequestinterceptor
-
-qtConfig(ssl): SUBDIRS += qwebengineclientcertificatestore
-
diff --git a/tests/auto/core/devtools/CMakeLists.txt b/tests/auto/core/devtools/CMakeLists.txt
new file mode 100644
index 000000000..efde75240
--- /dev/null
+++ b/tests/auto/core/devtools/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_internal_add_test(tst_devtools
+ SOURCES
+ tst_devtools.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+)
+
diff --git a/tests/auto/core/devtools/tst_devtools.cpp b/tests/auto/core/devtools/tst_devtools.cpp
new file mode 100644
index 000000000..57a2b83a3
--- /dev/null
+++ b/tests/auto/core/devtools/tst_devtools.cpp
@@ -0,0 +1,69 @@
+// Copyright (C) 2016 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 <qwebenginepage.h>
+
+class tst_DevTools : public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+ void attachAndDestroyPageFirst();
+ void attachAndDestroyInspectorFirst();
+};
+
+void tst_DevTools::attachAndDestroyPageFirst()
+{
+ // External inspector + manual destruction of page first
+ QWebEnginePage* page = new QWebEnginePage();
+ QWebEnginePage* inspector = new QWebEnginePage();
+
+ QSignalSpy spy(page, &QWebEnginePage::loadFinished);
+ page->load(QUrl("data:text/plain,foobarbaz"));
+ QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 12000);
+
+ // shouldn't do anything until page is set
+ page->triggerAction(QWebEnginePage::InspectElement);
+
+ inspector->setInspectedPage(page);
+ page->triggerAction(QWebEnginePage::InspectElement);
+
+ // This is deliberately racy:
+ QTest::qWait(10);
+
+ delete page;
+ delete inspector;
+}
+
+void tst_DevTools::attachAndDestroyInspectorFirst()
+{
+ // External inspector + manual destruction of inspector first
+ QWebEnginePage* page = new QWebEnginePage();
+
+ // shouldn't do anything until page is set
+ page->triggerAction(QWebEnginePage::InspectElement);
+
+ QWebEnginePage* inspector = new QWebEnginePage();
+ inspector->setInspectedPage(page);
+
+ QSignalSpy spy(page, &QWebEnginePage::loadFinished);
+ page->setHtml(QStringLiteral("<body><h1>FOO BAR!</h1></body>"));
+ QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 12000);
+
+ page->triggerAction(QWebEnginePage::InspectElement);
+
+ delete inspector;
+
+ page->triggerAction(QWebEnginePage::InspectElement);
+
+ // This is deliberately racy:
+ QTest::qWait(10);
+
+ delete page;
+}
+
+
+QTEST_MAIN(tst_DevTools)
+
+#include "tst_devtools.moc"
diff --git a/tests/auto/core/getdomainandregistry/CMakeLists.txt b/tests/auto/core/getdomainandregistry/CMakeLists.txt
new file mode 100644
index 000000000..c2b65f9dc
--- /dev/null
+++ b/tests/auto/core/getdomainandregistry/CMakeLists.txt
@@ -0,0 +1,12 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+include(../../util/util.cmake)
+
+qt_internal_add_test(tst_getdomainandregistry
+ SOURCES
+ tst_getdomainandregistry.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+ Test::Util
+)
diff --git a/tests/auto/core/getdomainandregistry/tst_getdomainandregistry.cpp b/tests/auto/core/getdomainandregistry/tst_getdomainandregistry.cpp
new file mode 100644
index 000000000..e9e0bf105
--- /dev/null
+++ b/tests/auto/core/getdomainandregistry/tst_getdomainandregistry.cpp
@@ -0,0 +1,30 @@
+// Copyright (C) 2022 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/qtwebenginecoreglobal.h>
+
+class tst_GetDomainAndRegistry final : public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+ void getDomainAndRegistry();
+};
+
+void tst_GetDomainAndRegistry::getDomainAndRegistry() {
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"http://www.google.com/"}), QString("google.com"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"http://www.google.co.uk/"}), QString("google.co.uk"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"http://127.0.0.1/"}), QString());
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://qt.io/"}), QString("qt.io"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://download.qt.io/"}), QString("qt.io"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://foo.fr/"}), QString("foo.fr"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://foo.gouv.fr/"}), QString("foo.gouv.fr"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://bar.foo.gouv.fr/"}), QString("foo.gouv.fr"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://fr.wikipedia.org/wiki/%C3%89l%C3%A9phant"}), QString("wikipedia.org"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://foo.günstigbestellen.de/"}), QString("foo.xn--gnstigbestellen-zvb.de"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://foo.g\u00fcnstigbestellen.de/"}), QString("foo.xn--gnstigbestellen-zvb.de"));
+}
+
+
+QTEST_MAIN(tst_GetDomainAndRegistry)
+#include "tst_getdomainandregistry.moc"
diff --git a/tests/auto/core/origins/CMakeLists.txt b/tests/auto/core/origins/CMakeLists.txt
new file mode 100644
index 000000000..306074994
--- /dev/null
+++ b/tests/auto/core/origins/CMakeLists.txt
@@ -0,0 +1,52 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+include(../../httpserver/httpserver.cmake)
+include(../../util/util.cmake)
+
+qt_internal_add_test(tst_origins
+ SOURCES
+ tst_origins.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+ Qt::WebEngineWidgets
+ Test::HttpServer
+ Test::Util
+)
+
+set(tst_origins_resource_files
+ "resources/createObjectURL.html"
+ "resources/dedicatedWorker.html"
+ "resources/dedicatedWorker.js"
+ "resources/mixedSchemes.html"
+ "resources/mixedSchemesWithCsp.html"
+ "resources/mixedSchemes_frame.html"
+ "resources/mixedXHR.html"
+ "resources/mixedXHR.txt"
+ "resources/serviceWorker.html"
+ "resources/serviceWorker.js"
+ "resources/sharedWorker.html"
+ "resources/sharedWorker.js"
+ "resources/subdir/frame2.html"
+ "resources/subdir/index.html"
+ "resources/subdir_frame1.html"
+ "resources/viewSource.html"
+ "resources/websocket.html"
+ "resources/websocket2.html"
+ "resources/red.png"
+ "resources/fetchApi.html"
+)
+
+qt_internal_add_resource(tst_origins "tst_origins"
+ PREFIX
+ "/"
+ FILES
+ ${tst_origins_resource_files}
+)
+
+qt_internal_extend_target(tst_origins CONDITION QT_FEATURE_webengine_webchannel AND TARGET Qt::WebSockets
+ DEFINES
+ WEBSOCKETS
+ LIBRARIES
+ Qt::WebSockets
+)
diff --git a/tests/auto/core/origins/resources/createObjectURL.html b/tests/auto/core/origins/resources/createObjectURL.html
new file mode 100644
index 000000000..133f636bb
--- /dev/null
+++ b/tests/auto/core/origins/resources/createObjectURL.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>createObjectURL</title>
+ <script>
+ const blob = new Blob(['foo']);
+ const result = URL.createObjectURL(blob);
+ </script>
+ </head>
+ <body></body>
+</html>
diff --git a/tests/auto/core/origins/resources/dedicatedWorker.html b/tests/auto/core/origins/resources/dedicatedWorker.html
new file mode 100644
index 000000000..cb4f14e73
--- /dev/null
+++ b/tests/auto/core/origins/resources/dedicatedWorker.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>dedicatedWorker</title>
+ <script>
+ var done = false;
+ var result;
+ var error;
+ try {
+ let worker = new Worker("dedicatedWorker.js");
+ worker.onmessage = (e) => { done = true; result = e.data; };
+ worker.postMessage(41);
+ } catch (e) {
+ done = true; error = e.message;
+ }
+ </script>
+ </head>
+ <body></body>
+</html>
diff --git a/tests/auto/core/origins/resources/dedicatedWorker.js b/tests/auto/core/origins/resources/dedicatedWorker.js
new file mode 100644
index 000000000..2631939d7
--- /dev/null
+++ b/tests/auto/core/origins/resources/dedicatedWorker.js
@@ -0,0 +1 @@
+onmessage = (e) => { postMessage(e.data + 1); };
diff --git a/tests/auto/core/origins/resources/fetchApi.html b/tests/auto/core/origins/resources/fetchApi.html
new file mode 100644
index 000000000..7b54416fd
--- /dev/null
+++ b/tests/auto/core/origins/resources/fetchApi.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+<body>
+ <script type="text/javascript">
+ const queryString = window.location.search;
+ const urlParams = new URLSearchParams(queryString);
+ const url = urlParams.get('url');
+ const f = fetch(url);
+ if (urlParams.get('printRes') == 'true') {
+ f.then((r) => r.text()).then((p) => console.log(p));
+ }
+ </script>
+</body>
+</html>
diff --git a/tests/auto/core/origins/resources/link.html b/tests/auto/core/origins/resources/link.html
new file mode 100644
index 000000000..297b9b273
--- /dev/null
+++ b/tests/auto/core/origins/resources/link.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Link</title>
+ </head>
+ <body>
+ <a id="link" href="">Link</a>
+ <script>
+ const urlParams = new URLSearchParams(window.location.search);
+ document.getElementById("link").href = urlParams.get('linkLocation');
+ </script>
+ </body>
+</html>
diff --git a/tests/auto/core/origins/resources/media.html b/tests/auto/core/origins/resources/media.html
new file mode 100644
index 000000000..091485b61
--- /dev/null
+++ b/tests/auto/core/origins/resources/media.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Media</title>
+ <script>
+ function addAudio(src) {
+ let aud = document.createElement('audio')
+ aud.src = src
+ document.getElementsByTagName("body")[0].appendChild(aud)
+ }
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/tests/auto/core/origins/resources/mixedSchemes.html b/tests/auto/core/origins/resources/mixedSchemes.html
new file mode 100644
index 000000000..3e50c2c3b
--- /dev/null
+++ b/tests/auto/core/origins/resources/mixedSchemes.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Mixed</title>
+ <script>
+ var result;
+ var canary;
+
+ function setIFrameUrl(frameUrl,imgUrl) {
+ result = undefined;
+ canary = undefined;
+ let img = document.createElement('img');
+ img.onerror = function() {
+ console.log("TEST:cannotLoad");
+ console.log("TEST:done");
+ };
+ img.onload = function() {
+ document.getElementById("iframe").setAttribute("src", frameUrl);
+ };
+ img.src = imgUrl
+ }
+
+ addEventListener("load", function() {
+ document.getElementById("iframe").addEventListener("load", function() {
+ if (canary && window[0].canary) {
+ console.log("TEST:canLoadAndAccess");
+ console.log("TEST:done");
+ } else {
+ console.log("TEST:canLoadButNotAccess");
+ console.log("TEST:done");
+ }
+ });
+ });
+ window.onerror = function(message, url, line, col, errorObj) {
+ return true;
+ };
+ </script>
+ </head>
+ <body>
+ <iframe id="iframe"></iframe>
+ </body>
+</html>
diff --git a/tests/auto/core/origins/resources/mixedSchemesWithCsp.html b/tests/auto/core/origins/resources/mixedSchemesWithCsp.html
new file mode 100644
index 000000000..ad7cbeeb7
--- /dev/null
+++ b/tests/auto/core/origins/resources/mixedSchemesWithCsp.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Security-Policy" content="frame-src 'none'">
+ <title>Mixed</title>
+ <script>
+ var result;
+ var canary;
+
+ function setIFrameUrl(url) {
+ result = undefined;
+ canary = undefined;
+ document.getElementById("iframe").setAttribute("src", url);
+ // Early fire is OK unless the test is expecting cannotLoad.
+ // If timeout is too short then a false positive is possible.
+ setTimeout(() => { result = result || "cannotLoad"; }, 500);
+ }
+
+ addEventListener("load", function() {
+ document.getElementById("iframe").addEventListener("load", function() {
+ if (canary && window[0].canary)
+ result = "canLoadAndAccess";
+ else
+ result = "canLoadButNotAccess";
+ });
+ });
+ </script>
+ </head>
+ <body>
+ <iframe id="iframe"></iframe>
+ </body>
+</html>
diff --git a/tests/auto/core/origins/resources/mixedSchemes_frame.html b/tests/auto/core/origins/resources/mixedSchemes_frame.html
new file mode 100644
index 000000000..9499caa1f
--- /dev/null
+++ b/tests/auto/core/origins/resources/mixedSchemes_frame.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Mixed - Frame</title>
+ <script>
+ try{
+ var canary = true;
+ parent.canary = true;
+ }catch(exception){
+ };
+
+ </script>
+ </head>
+ <body></body>
+</html>
diff --git a/tests/auto/core/origins/resources/mixedXHR.html b/tests/auto/core/origins/resources/mixedXHR.html
new file mode 100644
index 000000000..3dfd90006
--- /dev/null
+++ b/tests/auto/core/origins/resources/mixedXHR.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Mixed</title>
+ <script>
+ var result;
+ function sendXHR(url) {
+ result = undefined;
+ let req = new XMLHttpRequest();
+ req.addEventListener("load", () => { result = req.responseText });
+ req.addEventListener("error", () => { result = "error"; });
+ req.open("GET", url);
+ req.send();
+ }
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/tests/auto/core/origins/resources/mixedXHR.txt b/tests/auto/core/origins/resources/mixedXHR.txt
new file mode 100644
index 000000000..b5754e203
--- /dev/null
+++ b/tests/auto/core/origins/resources/mixedXHR.txt
@@ -0,0 +1 @@
+ok \ No newline at end of file
diff --git a/tests/auto/core/origins/resources/red.png b/tests/auto/core/origins/resources/red.png
new file mode 100644
index 000000000..5ae85192b
--- /dev/null
+++ b/tests/auto/core/origins/resources/red.png
Binary files differ
diff --git a/tests/auto/core/origins/resources/redirect.css b/tests/auto/core/origins/resources/redirect.css
new file mode 100644
index 000000000..a6a03d8f5
--- /dev/null
+++ b/tests/auto/core/origins/resources/redirect.css
@@ -0,0 +1,3 @@
+body {
+ font-family: serif;
+}
diff --git a/tests/auto/core/origins/resources/redirect.html b/tests/auto/core/origins/resources/redirect.html
new file mode 100644
index 000000000..603cb76f0
--- /dev/null
+++ b/tests/auto/core/origins/resources/redirect.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>redirect</title>
+ <script>
+ function addStylesheetLink(src) {
+ let link = document.createElement('link');
+ link.rel = 'stylesheet';
+ link.href = src;
+ document.getElementsByTagName("head")[0].appendChild(link);
+ }
+ </script>
+ </head>
+ <body>
+ Text
+ </body>
+</html>
diff --git a/tests/auto/core/origins/resources/serviceWorker.html b/tests/auto/core/origins/resources/serviceWorker.html
new file mode 100644
index 000000000..27890c98f
--- /dev/null
+++ b/tests/auto/core/origins/resources/serviceWorker.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>serviceWorker</title>
+ <script>
+ var done = false;
+ var error;
+ try {
+ navigator.serviceWorker.register("serviceWorker.js")
+ .then((r) => { done = true; })
+ .catch((e) => { done = true; error = e.message; });
+ } catch (e) {
+ done = true; error = e.message;
+ }
+ </script>
+ </head>
+ <body></body>
+</html>
diff --git a/tests/auto/core/origins/resources/serviceWorker.js b/tests/auto/core/origins/resources/serviceWorker.js
new file mode 100644
index 000000000..40a8c178f
--- /dev/null
+++ b/tests/auto/core/origins/resources/serviceWorker.js
@@ -0,0 +1 @@
+/* empty */
diff --git a/tests/auto/core/origins/resources/sharedWorker.html b/tests/auto/core/origins/resources/sharedWorker.html
new file mode 100644
index 000000000..8b5a0a794
--- /dev/null
+++ b/tests/auto/core/origins/resources/sharedWorker.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>sharedWorker</title>
+ <script>
+ var done;
+ var result;
+ var error;
+ try {
+ let worker = new SharedWorker("sharedWorker.js");
+ worker.port.onmessage = (e) => { done = true; result = e.data; };
+ worker.port.postMessage(41);
+ } catch (e) {
+ done = true; error = e.message;
+ }
+ </script>
+ </head>
+ <body></body>
+</html>
diff --git a/tests/auto/core/origins/resources/sharedWorker.js b/tests/auto/core/origins/resources/sharedWorker.js
new file mode 100644
index 000000000..60ef93a5f
--- /dev/null
+++ b/tests/auto/core/origins/resources/sharedWorker.js
@@ -0,0 +1,6 @@
+onconnect = function(e) {
+ let port = e.ports[0];
+ port.onmessage = function(e) {
+ port.postMessage(e.data + 1);
+ };
+};
diff --git a/tests/auto/core/origins/resources/subdir/frame2.html b/tests/auto/core/origins/resources/subdir/frame2.html
new file mode 100644
index 000000000..3a2f664ca
--- /dev/null
+++ b/tests/auto/core/origins/resources/subdir/frame2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Subdir - Frame 2</title>
+ <script>
+ parent.msg[1] = "world";
+ </script>
+ </head>
+ <body></body>
+</html>
diff --git a/tests/auto/core/origins/resources/subdir/index.html b/tests/auto/core/origins/resources/subdir/index.html
new file mode 100644
index 000000000..9c5d5d782
--- /dev/null
+++ b/tests/auto/core/origins/resources/subdir/index.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Subdir</title>
+
+ <script>
+ var msg = [];
+ </script>
+
+ <!-- for manual testing -->
+ <script>
+ window.addEventListener("load", () => {
+ for (let i of [0, 1]) {
+ let p = document.createElement("p");
+ p.appendChild(document.createTextNode(`frame ${i+1} says: ${msg[i]}`));
+ document.body.insertBefore(p, null);
+ }
+ });
+ </script>
+
+ </head>
+ <body>
+ <iframe src="../subdir_frame1.html"></iframe>
+ <iframe src="frame2.html"></iframe>
+ </body>
+</html>
diff --git a/tests/auto/core/origins/resources/subdir_frame1.html b/tests/auto/core/origins/resources/subdir_frame1.html
new file mode 100644
index 000000000..63973f2f4
--- /dev/null
+++ b/tests/auto/core/origins/resources/subdir_frame1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Subdir - Frame 1</title>
+ <script>
+ parent.msg[0] = "hello";
+ </script>
+ </head>
+ <body></body>
+</html>
diff --git a/tests/auto/core/origins/resources/viewSource.html b/tests/auto/core/origins/resources/viewSource.html
new file mode 100644
index 000000000..977074c74
--- /dev/null
+++ b/tests/auto/core/origins/resources/viewSource.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>viewSource</title>
+ </head>
+ <body>
+ <p>viewSource</p>
+ </body>
+</html>
diff --git a/tests/auto/core/origins/resources/websocket.html b/tests/auto/core/origins/resources/websocket.html
new file mode 100644
index 000000000..31db66571
--- /dev/null
+++ b/tests/auto/core/origins/resources/websocket.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>WebSocket</title>
+ <script src="qrc:/qtwebchannel/qwebchannel.js"></script>
+ <script>
+ var result;
+ new QWebChannel(qt.webChannelTransport, channel => {
+ const ws = new WebSocket(channel.objects.echoServer.url);
+ ws.addEventListener("open", event => {
+ ws.send("ok");
+ });
+ ws.addEventListener("message", event => {
+ result = event.data;
+ });
+ ws.addEventListener("close", event => {
+ result = event.code;
+ });
+ })
+ </script>
+ </head>
+ <body></body>
+</html>
diff --git a/tests/auto/core/origins/resources/websocket2.html b/tests/auto/core/origins/resources/websocket2.html
new file mode 100644
index 000000000..7365143de
--- /dev/null
+++ b/tests/auto/core/origins/resources/websocket2.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>WebSocket</title>
+ <script src="../qtwebchannel/qwebchannel.js"></script>
+ <script>
+ var result;
+ new QWebChannel(qt.webChannelTransport, channel => {
+ const ws = new WebSocket(channel.objects.echoServer.url);
+ ws.addEventListener("open", event => {
+ ws.send("ok");
+ });
+ ws.addEventListener("message", event => {
+ result = event.data;
+ });
+ ws.addEventListener("close", event => {
+ result = event.code;
+ });
+ })
+ </script>
+ </head>
+ <body></body>
+</html>
diff --git a/tests/auto/core/origins/tst_origins.cpp b/tests/auto/core/origins/tst_origins.cpp
new file mode 100644
index 000000000..81385701f
--- /dev/null
+++ b/tests/auto/core/origins/tst_origins.cpp
@@ -0,0 +1,1730 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <util.h>
+#include "httpserver.h"
+
+#include <QtCore/qfile.h>
+#include <QtTest/QtTest>
+#include <QtWebEngineCore/qwebengineurlrequestinterceptor.h>
+#include <QtWebEngineCore/qwebengineurlrequestjob.h>
+#include <QtWebEngineCore/qwebengineurlscheme.h>
+#include <QtWebEngineCore/qwebengineurlschemehandler.h>
+#include <QtWebEngineCore/qwebenginesettings.h>
+#include <QtWebEngineCore/qwebengineprofile.h>
+#include <QtWebEngineCore/qwebenginepage.h>
+#include <QtWebEngineWidgets/qwebengineview.h>
+
+#if defined(WEBSOCKETS)
+#include <QtWebSockets/qwebsocket.h>
+#include <QtWebSockets/qwebsocketserver.h>
+#include <QtWebChannel/qwebchannel.h>
+#endif
+#include <qaction.h>
+
+#define QSL QStringLiteral
+#define QBAL QByteArrayLiteral
+
+Q_LOGGING_CATEGORY(lc, "qt.webengine.tests")
+
+void registerSchemes()
+{
+ {
+ QWebEngineUrlScheme scheme(QBAL("PathSyntax"));
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+
+ {
+ QWebEngineUrlScheme scheme(QBAL("PathSyntax-Secure"));
+ scheme.setFlags(QWebEngineUrlScheme::SecureScheme);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+
+ {
+ QWebEngineUrlScheme scheme(QBAL("PathSyntax-Secure-ServiceWorkersAllowed"));
+ scheme.setFlags(QWebEngineUrlScheme::SecureScheme | QWebEngineUrlScheme::ServiceWorkersAllowed);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+
+ {
+ QWebEngineUrlScheme scheme(QBAL("PathSyntax-Local"));
+ scheme.setFlags(QWebEngineUrlScheme::LocalScheme);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+
+ {
+ QWebEngineUrlScheme scheme(QBAL("PathSyntax-LocalAccessAllowed"));
+ scheme.setFlags(QWebEngineUrlScheme::LocalAccessAllowed);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+
+ {
+ QWebEngineUrlScheme scheme(QBAL("PathSyntax-NoAccessAllowed"));
+ scheme.setFlags(QWebEngineUrlScheme::NoAccessAllowed);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+
+ {
+ QWebEngineUrlScheme scheme(QBAL("PathSyntax-ServiceWorkersAllowed"));
+ scheme.setFlags(QWebEngineUrlScheme::ServiceWorkersAllowed);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+
+ {
+ QWebEngineUrlScheme scheme(QBAL("PathSyntax-ViewSourceAllowed"));
+ scheme.setFlags(QWebEngineUrlScheme::ViewSourceAllowed);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+
+ {
+ QWebEngineUrlScheme scheme(QBAL("HostSyntax"));
+ scheme.setSyntax(QWebEngineUrlScheme::Syntax::Host);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+
+ {
+ QWebEngineUrlScheme scheme(QBAL("HostSyntax-ContentSecurityPolicyIgnored"));
+ scheme.setSyntax(QWebEngineUrlScheme::Syntax::Host);
+ scheme.setFlags(QWebEngineUrlScheme::ContentSecurityPolicyIgnored);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+
+ {
+ QWebEngineUrlScheme scheme(QBAL("HostAndPortSyntax"));
+ scheme.setSyntax(QWebEngineUrlScheme::Syntax::HostAndPort);
+ scheme.setDefaultPort(42);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+
+ {
+ QWebEngineUrlScheme scheme(QBAL("HostPortAndUserInformationSyntax"));
+ scheme.setSyntax(QWebEngineUrlScheme::Syntax::HostPortAndUserInformation);
+ scheme.setDefaultPort(42);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+
+ {
+ QWebEngineUrlScheme scheme(QBAL("redirect"));
+ scheme.setFlags(QWebEngineUrlScheme::CorsEnabled);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+
+ {
+ QWebEngineUrlScheme scheme(QBAL("redirect-secure"));
+ scheme.setFlags(QWebEngineUrlScheme::SecureScheme);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+
+ {
+ QWebEngineUrlScheme scheme(QBAL("redirect-local"));
+ scheme.setFlags(QWebEngineUrlScheme::LocalScheme | QWebEngineUrlScheme::LocalAccessAllowed);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+
+ {
+ QWebEngineUrlScheme scheme(QBAL("cors"));
+ scheme.setFlags(QWebEngineUrlScheme::CorsEnabled);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+ {
+ QWebEngineUrlScheme scheme(QBAL("secure-cors"));
+ scheme.setFlags(QWebEngineUrlScheme::SecureScheme | QWebEngineUrlScheme::CorsEnabled);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+ {
+ QWebEngineUrlScheme scheme(QBAL("localaccess"));
+ scheme.setFlags(QWebEngineUrlScheme::LocalAccessAllowed);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+ {
+ QWebEngineUrlScheme scheme(QBAL("local"));
+ scheme.setFlags(QWebEngineUrlScheme::LocalScheme);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+ {
+ QWebEngineUrlScheme scheme(QBAL("local-localaccess"));
+ scheme.setFlags(QWebEngineUrlScheme::LocalScheme | QWebEngineUrlScheme::LocalAccessAllowed);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+ {
+ QWebEngineUrlScheme scheme(QBAL("local-cors"));
+ scheme.setFlags(QWebEngineUrlScheme::LocalScheme | QWebEngineUrlScheme::CorsEnabled);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+ {
+ QWebEngineUrlScheme scheme("fetchapi-allowed");
+ scheme.setFlags(QWebEngineUrlScheme::CorsEnabled | QWebEngineUrlScheme::FetchApiAllowed);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+ {
+ QWebEngineUrlScheme scheme("fetchapi-not-allowed");
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+}
+Q_CONSTRUCTOR_FUNCTION(registerSchemes)
+
+class TstUrlSchemeHandler final : public QWebEngineUrlSchemeHandler {
+ Q_OBJECT
+
+public:
+ TstUrlSchemeHandler(QWebEngineProfile *profile)
+ {
+ profile->installUrlSchemeHandler(QBAL("tst"), this);
+
+ profile->installUrlSchemeHandler(QBAL("PathSyntax"), this);
+ profile->installUrlSchemeHandler(QBAL("PathSyntax-Secure"), this);
+ profile->installUrlSchemeHandler(QBAL("PathSyntax-Secure-ServiceWorkersAllowed"), this);
+ profile->installUrlSchemeHandler(QBAL("PathSyntax-Local"), this);
+ profile->installUrlSchemeHandler(QBAL("PathSyntax-LocalAccessAllowed"), this);
+ profile->installUrlSchemeHandler(QBAL("PathSyntax-NoAccessAllowed"), this);
+ profile->installUrlSchemeHandler(QBAL("PathSyntax-ServiceWorkersAllowed"), this);
+ profile->installUrlSchemeHandler(QBAL("PathSyntax-ViewSourceAllowed"), this);
+ profile->installUrlSchemeHandler(QBAL("HostSyntax"), this);
+ profile->installUrlSchemeHandler(QBAL("HostSyntax-ContentSecurityPolicyIgnored"), this);
+ profile->installUrlSchemeHandler(QBAL("HostAndPortSyntax"), this);
+ profile->installUrlSchemeHandler(QBAL("HostPortAndUserInformationSyntax"), this);
+ profile->installUrlSchemeHandler(QBAL("redirect"), this);
+ profile->installUrlSchemeHandler(QBAL("redirect-secure"), this);
+ profile->installUrlSchemeHandler(QBAL("redirect-local"), this);
+ profile->installUrlSchemeHandler(QBAL("cors"), this);
+ profile->installUrlSchemeHandler(QBAL("secure-cors"), this);
+ profile->installUrlSchemeHandler(QBAL("localaccess"), this);
+ profile->installUrlSchemeHandler(QBAL("local"), this);
+ profile->installUrlSchemeHandler(QBAL("local-localaccess"), this);
+ profile->installUrlSchemeHandler(QBAL("local-cors"), this);
+ }
+
+ QList<QUrl> &requests() { return m_requests; }
+
+private:
+ void requestStarted(QWebEngineUrlRequestJob *job) override
+ {
+ QUrl url = job->requestUrl();
+ m_requests << url;
+
+ if (url.scheme().startsWith("redirect")) {
+ QString path = url.path();
+ int idx = path.indexOf(QChar('/'));
+ if (idx > 0) {
+ url.setScheme(path.first(idx));
+ url.setPath(path.mid(idx, -1));
+ job->redirect(url);
+ return;
+ }
+ }
+
+ QString pathPrefix = QDir(QT_TESTCASE_SOURCEDIR).canonicalPath();
+ if (url.path().startsWith("/qtwebchannel/"))
+ pathPrefix = QSL(":");
+ QString pathSuffix = url.path();
+ auto file = std::make_unique<QFile>(pathPrefix + pathSuffix, job);
+ if (!file->open(QIODevice::ReadOnly)) {
+ qWarning() << "Failed to read data for:" << url << file->errorString();
+ job->fail(QWebEngineUrlRequestJob::RequestFailed);
+ return;
+ }
+ QByteArray mimeType = QBAL("text/html");
+ if (pathSuffix.endsWith(QSL(".js")))
+ mimeType = QBAL("application/javascript");
+ else if (pathSuffix.endsWith(QSL(".css")))
+ mimeType = QBAL("text/css");
+ job->reply(mimeType, file.release());
+ }
+
+ QList<QUrl> m_requests;
+};
+
+class TestRequestInterceptor : public QWebEngineUrlRequestInterceptor
+{
+public:
+ TestRequestInterceptor() = default;
+ void interceptRequest(QWebEngineUrlRequestInfo &info) override
+ {
+ qCDebug(lc) << this << "Type:" << info.resourceType() << info.requestMethod() << "Navigation:" << info.navigationType()
+ << info.requestUrl() << "Initiator:" << info.initiator();
+
+ QUrl url = info.requestUrl();
+ requests << url;
+ if (url.scheme().startsWith("redirect")) {
+ QString path = url.path();
+ int idx = path.indexOf(QChar('/'));
+ if (idx > 0) {
+ url.setScheme(path.first(idx));
+ url.setPath(path.mid(idx, -1));
+ info.redirect(url);
+ }
+ }
+ }
+ QList<QUrl> requests;
+};
+
+class TestPage : public QWebEnginePage
+{
+public:
+ TestPage(QWebEngineProfile *profile) : QWebEnginePage(profile, nullptr)
+ {
+ }
+ void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel,
+ const QString &message, int,
+ const QString &) override
+ {
+ messages << message;
+ qCDebug(lc) << message;
+ }
+
+ bool logContainsDoneMarker() const { return messages.contains("TEST:done"); }
+
+ QString findResultInLog() const
+ {
+ // make sure we do not have some extra logs from blink
+ for (auto message : messages) {
+ QStringList s = message.split(':');
+ if (s.size() > 1 && s[0] == "TEST")
+ return s[1];
+ }
+ return QString();
+ }
+
+ void clearLog() { messages.clear(); }
+
+private:
+ QStringList messages;
+};
+
+class tst_Origins final : public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void jsUrlCanon();
+ void jsUrlRelative();
+ void jsUrlOrigin();
+ void subdirWithAccess();
+ void subdirWithoutAccess();
+ void fileAccessRemoteUrl_data();
+ void fileAccessRemoteUrl();
+ void fileAccessLocalUrl_data();
+ void fileAccessLocalUrl();
+ void mixedSchemes_data();
+ void mixedSchemes();
+ void mixedSchemesWithCsp();
+ void mixedXHR_data();
+ void mixedXHR();
+ void mixedContent_data();
+ void mixedContent();
+ void localMediaBlock_data();
+ void localMediaBlock();
+#if defined(WEBSOCKETS)
+ void webSocket();
+#endif
+ void dedicatedWorker();
+ void sharedWorker();
+ void serviceWorker();
+ void viewSource();
+ void createObjectURL();
+ void redirectScheme();
+ void redirectSchemeLocal();
+ void redirectSchemeSecure();
+ void redirectInterceptor();
+ void redirectInterceptorLocal();
+ void redirectInterceptorSecure();
+ void redirectInterceptorFile();
+ void redirectInterceptorHttp();
+ void fetchApiCustomUrl_data();
+ void fetchApiCustomUrl();
+ void fetchApiHttpUrl();
+
+private:
+ bool verifyLoad(const QUrl &url)
+ {
+ QSignalSpy spy(m_page, &QWebEnginePage::loadFinished);
+ m_page->load(url);
+ [&spy]() { QTRY_VERIFY_WITH_TIMEOUT(!spy.isEmpty(), 90000); }();
+ return !spy.isEmpty() && spy.front().value(0).toBool();
+ }
+
+ QVariant eval(const QString &code)
+ {
+ return evaluateJavaScriptSync(m_page, code);
+ }
+
+ QWebEngineProfile m_profile;
+ TestPage *m_page = nullptr;
+ TstUrlSchemeHandler *m_handler = nullptr;
+};
+
+void tst_Origins::initTestCase()
+{
+ QTest::ignoreMessage(
+ QtWarningMsg,
+ QRegularExpression("Please register the custom scheme 'tst'.*"));
+
+ m_handler = new TstUrlSchemeHandler(&m_profile);
+}
+
+void tst_Origins::cleanupTestCase()
+{
+ QVERIFY(!m_page);
+ delete m_handler;
+}
+
+void tst_Origins::init()
+{
+ m_page = new TestPage(&m_profile);
+}
+
+void tst_Origins::cleanup()
+{
+ delete m_page;
+ m_page = nullptr;
+ m_handler->requests().clear();
+}
+
+// Test URL parsing and canonicalization in Blink. The implementation of this
+// part is mostly shared between Blink and Chromium proper.
+void tst_Origins::jsUrlCanon()
+{
+ QVERIFY(verifyLoad(QSL("about:blank")));
+
+ // Standard schemes are biased towards the authority part.
+ QCOMPARE(eval(QSL("new URL(\"http:foo/bar\").href")), QVariant(QSL("http://foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"http:/foo/bar\").href")), QVariant(QSL("http://foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"http://foo/bar\").href")), QVariant(QSL("http://foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"http:///foo/bar\").href")), QVariant(QSL("http://foo/bar")));
+
+ // The file scheme is however a (particularly) special case.
+#ifdef Q_OS_WIN
+ QCOMPARE(eval(QSL("new URL(\"file:foo/bar\").href")), QVariant(QSL("file://foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"file:/foo/bar\").href")), QVariant(QSL("file://foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"file://foo/bar\").href")), QVariant(QSL("file://foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"file:///foo/bar\").href")), QVariant(QSL("file:///foo/bar")));
+#else
+ QCOMPARE(eval(QSL("new URL(\"file:foo/bar\").href")), QVariant(QSL("file:///foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"file:/foo/bar\").href")), QVariant(QSL("file:///foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"file://foo/bar\").href")), QVariant(QSL("file://foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"file:///foo/bar\").href")), QVariant(QSL("file:///foo/bar")));
+#endif
+
+ // The qrc scheme is a PathSyntax scheme, having only a path and nothing else.
+ QCOMPARE(eval(QSL("new URL(\"qrc:foo/bar\").href")), QVariant(QSL("qrc:foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"qrc:/foo/bar\").href")), QVariant(QSL("qrc:/foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"qrc://foo/bar\").href")), QVariant(QSL("qrc://foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"qrc:///foo/bar\").href")), QVariant(QSL("qrc:///foo/bar")));
+
+ // Same for unregistered schemes.
+ QCOMPARE(eval(QSL("new URL(\"tst:foo/bar\").href")), QVariant(QSL("tst:foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"tst:/foo/bar\").href")), QVariant(QSL("tst:/foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"tst://foo/bar\").href")), QVariant(QSL("tst://foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"tst:///foo/bar\").href")), QVariant(QSL("tst:///foo/bar")));
+
+ // A HostSyntax scheme is like http without the port & user information.
+ QCOMPARE(eval(QSL("new URL(\"HostSyntax:foo/bar\").href")), QVariant(QSL("hostsyntax://foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"HostSyntax:foo:42/bar\").href")), QVariant(QSL("hostsyntax://foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"HostSyntax:a:b@foo/bar\").href")), QVariant(QSL("hostsyntax://foo/bar")));
+
+ // A HostAndPortSyntax scheme is like http without the user information.
+ QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo/bar\").href")),
+ QVariant(QSL("hostandportsyntax://foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:41/bar\").href")),
+ QVariant(QSL("hostandportsyntax://foo:41/bar")));
+ QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:42/bar\").href")),
+ QVariant(QSL("hostandportsyntax://foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:a:b@foo/bar\").href")),
+ QVariant(QSL("hostandportsyntax://foo/bar")));
+
+ // A HostPortAndUserInformationSyntax scheme is exactly like http.
+ QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo/bar\").href")),
+ QVariant(QSL("hostportanduserinformationsyntax://foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:41/bar\").href")),
+ QVariant(QSL("hostportanduserinformationsyntax://foo:41/bar")));
+ QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:42/bar\").href")),
+ QVariant(QSL("hostportanduserinformationsyntax://foo/bar")));
+ QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:a:b@foo/bar\").href")),
+ QVariant(QSL("hostportanduserinformationsyntax://a:b@foo/bar")));
+}
+
+// Test relative URL resolution.
+void tst_Origins::jsUrlRelative()
+{
+ QVERIFY(verifyLoad(QSL("about:blank")));
+
+ // Schemes with hosts, like http, work as expected.
+ QCOMPARE(eval(QSL("new URL('bar', 'http://foo').href")), QVariant(QSL("http://foo/bar")));
+ QCOMPARE(eval(QSL("new URL('baz', 'http://foo/bar').href")), QVariant(QSL("http://foo/baz")));
+ QCOMPARE(eval(QSL("new URL('baz', 'http://foo/bar/').href")), QVariant(QSL("http://foo/bar/baz")));
+ QCOMPARE(eval(QSL("new URL('/baz', 'http://foo/bar/').href")), QVariant(QSL("http://foo/baz")));
+ QCOMPARE(eval(QSL("new URL('./baz', 'http://foo/bar/').href")), QVariant(QSL("http://foo/bar/baz")));
+ QCOMPARE(eval(QSL("new URL('../baz', 'http://foo/bar/').href")), QVariant(QSL("http://foo/baz")));
+ QCOMPARE(eval(QSL("new URL('../../baz', 'http://foo/bar/').href")), QVariant(QSL("http://foo/baz")));
+ QCOMPARE(eval(QSL("new URL('//baz', 'http://foo/bar/').href")), QVariant(QSL("http://baz/")));
+
+ // In the case of schemes without hosts, relative URLs only work if the URL
+ // starts with a single slash -- and canonicalization does not guarantee
+ // this. The following cases all fail with TypeErrors.
+ QCOMPARE(eval(QSL("new URL('bar', 'tst:foo').href")), QVariant());
+ QCOMPARE(eval(QSL("new URL('baz', 'tst:foo/bar').href")), QVariant());
+ QCOMPARE(eval(QSL("new URL('bar', 'tst://foo').href")), QVariant());
+ QCOMPARE(eval(QSL("new URL('bar', 'tst:///foo').href")), QVariant());
+
+ // However, registered custom schemes have been patched to allow relative
+ // URLs even without an initial slash.
+ QCOMPARE(eval(QSL("new URL('bar', 'qrc:foo').href")), QVariant(QSL("qrc:bar")));
+ QCOMPARE(eval(QSL("new URL('baz', 'qrc:foo/bar').href")), QVariant(QSL("qrc:foo/baz")));
+ QCOMPARE(eval(QSL("new URL('bar', 'qrc://foo').href")), QVariant(QSL("qrc://bar")));
+ QCOMPARE(eval(QSL("new URL('bar', 'qrc:///foo').href")), QVariant(QSL("qrc:///bar")));
+
+ // With a slash it works the same as http except 'foo' is part of the path and not the host.
+ QCOMPARE(eval(QSL("new URL('bar', 'qrc:/foo').href")), QVariant(QSL("qrc:/bar")));
+ QCOMPARE(eval(QSL("new URL('bar', 'qrc:/foo/').href")), QVariant(QSL("qrc:/foo/bar")));
+ QCOMPARE(eval(QSL("new URL('baz', 'qrc:/foo/bar').href")), QVariant(QSL("qrc:/foo/baz")));
+ QCOMPARE(eval(QSL("new URL('baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/foo/bar/baz")));
+ QCOMPARE(eval(QSL("new URL('/baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/baz")));
+ QCOMPARE(eval(QSL("new URL('./baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/foo/bar/baz")));
+ QCOMPARE(eval(QSL("new URL('../baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/foo/baz")));
+ QCOMPARE(eval(QSL("new URL('../../baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/baz")));
+ QCOMPARE(eval(QSL("new URL('../../../baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/baz")));
+
+ // If the relative URL begins with >= 2 slashes, then the scheme is treated
+ // not as a Syntax::Path scheme but as a Syntax::HostPortAndUserInformation
+ // scheme.
+ QCOMPARE(eval(QSL("new URL('//baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc://baz/")));
+ QCOMPARE(eval(QSL("new URL('///baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc://baz/")));
+}
+
+// Test origin serialization in Blink, implemented by blink::KURL and
+// blink::SecurityOrigin as opposed to GURL and url::Origin.
+void tst_Origins::jsUrlOrigin()
+{
+ QVERIFY(verifyLoad(QSL("about:blank")));
+
+ // For network protocols the origin string must include the domain and port.
+ QCOMPARE(eval(QSL("new URL(\"http://foo.com/page.html\").origin")), QVariant(QSL("http://foo.com")));
+ QCOMPARE(eval(QSL("new URL(\"https://foo.com/page.html\").origin")), QVariant(QSL("https://foo.com")));
+
+ // Even though file URL can also have domains, these are not included in the
+ // origin string by Chromium. The standard does not specify a value here,
+ // but suggests 'null' (https://url.spec.whatwg.org/#origin).
+ QCOMPARE(eval(QSL("new URL(\"file:/etc/passwd\").origin")), QVariant(QSL("file://")));
+ QCOMPARE(eval(QSL("new URL(\"file://foo.com/etc/passwd\").origin")), QVariant(QSL("file://")));
+
+ // Unregistered schemes behave like file.
+ QCOMPARE(eval(QSL("new URL(\"tst:/banana\").origin")), QVariant(QSL("tst://")));
+ QCOMPARE(eval(QSL("new URL(\"tst://foo.com/banana\").origin")), QVariant(QSL("tst://")));
+
+ // The non-PathSyntax schemes should have hosts and potentially ports.
+ QCOMPARE(eval(QSL("new URL(\"HostSyntax:foo:41/bar\").origin")),
+ QVariant(QSL("hostsyntax://foo")));
+ QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:41/bar\").origin")),
+ QVariant(QSL("hostandportsyntax://foo:41")));
+ QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:42/bar\").origin")),
+ QVariant(QSL("hostandportsyntax://foo")));
+ QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:41/bar\").origin")),
+ QVariant(QSL("hostportanduserinformationsyntax://foo:41")));
+ QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:42/bar\").origin")),
+ QVariant(QSL("hostportanduserinformationsyntax://foo")));
+
+ // A PathSyntax scheme should have a 'universal' origin.
+ QCOMPARE(eval(QSL("new URL(\"PathSyntax:foo\").origin")), QVariant(QSL("pathsyntax:")));
+ QCOMPARE(eval(QSL("new URL(\"qrc:/crysis.css\").origin")), QVariant(QSL("qrc:")));
+ QCOMPARE(eval(QSL("new URL(\"qrc://foo.com/crysis.css\").origin")), QVariant(QSL("qrc:")));
+
+ // The NoAccessAllowed flag forces opaque origins.
+ QCOMPARE(eval(QSL("new URL(\"PathSyntax-NoAccessAllowed:foo\").origin")),
+ QVariant(QSL("null")));
+}
+
+class ScopedAttribute {
+public:
+ ScopedAttribute(QWebEngineSettings *settings, QWebEngineSettings::WebAttribute attribute, bool newValue)
+ : m_settings(settings)
+ , m_attribute(attribute)
+ , m_oldValue(m_settings->testAttribute(m_attribute))
+ {
+ m_settings->setAttribute(m_attribute, newValue);
+ }
+ ~ScopedAttribute()
+ {
+ m_settings->setAttribute(m_attribute, m_oldValue);
+ }
+private:
+ QWebEngineSettings *m_settings;
+ QWebEngineSettings::WebAttribute m_attribute;
+ bool m_oldValue;
+};
+
+// Test same-origin policy of file, qrc and custom schemes.
+//
+// Note the test case involves the main page trying to load an iframe from a
+// file that resides in a parent directory. This is just a small detail to
+// demonstrate the difference with Firefox where such access is not allowed.
+void tst_Origins::subdirWithAccess()
+{
+ ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, true);
+
+ QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/subdir/index.html"));
+ QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello")));
+ QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world")));
+
+ QVERIFY(verifyLoad(QSL("qrc:/resources/subdir/index.html")));
+ QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello")));
+ QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world")));
+
+ QVERIFY(verifyLoad(QSL("tst:/resources/subdir/index.html")));
+ QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello")));
+ QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world")));
+}
+
+// In this variation the LocalContentCanAccessFileUrls attribute is disabled. As
+// a result all file URLs will be considered to have unique/opaque origins, that
+// is, they are not the 'same origin as' any other origin.
+//
+// Note that this applies only to file URLs and not qrc or custom schemes.
+//
+// See also (in Blink):
+// - the allow_file_access_from_file_urls option and
+// - the blink::SecurityOrigin::BlockLocalAccessFromLocalOrigin() method.
+void tst_Origins::subdirWithoutAccess()
+{
+ ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, false);
+
+ QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/subdir/index.html"));
+ QCOMPARE(eval(QSL("msg[0]")), QVariant());
+ QCOMPARE(eval(QSL("msg[1]")), QVariant());
+
+ QVERIFY(verifyLoad(QSL("qrc:/resources/subdir/index.html")));
+ QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello")));
+ QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world")));
+
+ QVERIFY(verifyLoad(QSL("tst:/resources/subdir/index.html")));
+ QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello")));
+ QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world")));
+}
+
+void tst_Origins::fileAccessRemoteUrl_data()
+{
+ QTest::addColumn<bool>("EnableAccess");
+ QTest::addColumn<bool>("UserGesture");
+ QTest::addRow("enabled, XHR") << true << false;
+ QTest::addRow("enabled, link click") << true << true;
+ QTest::addRow("disabled, XHR") << false << false;
+ QTest::addRow("disabled, link click") << false << true;
+}
+
+void tst_Origins::fileAccessRemoteUrl()
+{
+ QFETCH(bool, EnableAccess);
+ QFETCH(bool, UserGesture);
+
+ QWebEngineView view;
+ view.setPage(m_page);
+ view.resize(800, 600);
+ view.show();
+
+ HttpServer server;
+ server.setResourceDirs({ QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + "/resources" });
+ QVERIFY(server.start());
+
+ ScopedAttribute sa1(m_page->settings(), QWebEngineSettings::LocalContentCanAccessRemoteUrls, EnableAccess);
+ ScopedAttribute sa2(m_page->settings(), QWebEngineSettings::ErrorPageEnabled, false);
+
+ if (UserGesture) {
+ QString remoteUrl(server.url("/link.html").toString());
+#ifdef Q_OS_WIN
+ QString localUrl("file:///" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/link.html?linkLocation=" + remoteUrl);
+#else
+ QString localUrl("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/link.html?linkLocation=" + remoteUrl);
+#endif
+
+ QVERIFY(verifyLoad(localUrl));
+
+ QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, elementCenter(m_page, "link"));
+ // Succeed independently of EnableAccess == false
+ QTRY_COMPARE(m_page->url(), remoteUrl);
+
+ // Back/forward navigation is also allowed, however they are not user gesture
+ m_page->triggerAction(QWebEnginePage::Back);
+ QTRY_COMPARE(m_page->url(), localUrl);
+ m_page->triggerAction(QWebEnginePage::Forward);
+ QTRY_COMPARE(m_page->url(), remoteUrl);
+ } else {
+ QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/mixedXHR.html"));
+ eval("sendXHR('" + server.url("/mixedXHR.txt").toString() + "')");
+ QTRY_COMPARE(eval("result"), (EnableAccess ? QString("ok") : QString("error")));
+ }
+}
+
+void tst_Origins::fileAccessLocalUrl_data()
+{
+ QTest::addColumn<bool>("EnableAccess");
+ QTest::addColumn<bool>("UserGesture");
+ QTest::addRow("enabled, XHR") << true << false;
+ QTest::addRow("enabled, link click") << true << true;
+ QTest::addRow("disabled, XHR") << false << false;
+ QTest::addRow("disabled, link click") << false << true;
+}
+
+void tst_Origins::fileAccessLocalUrl()
+{
+ QFETCH(bool, EnableAccess);
+ QFETCH(bool, UserGesture);
+
+ QWebEngineView view;
+ view.setPage(m_page);
+ view.resize(800, 600);
+ view.show();
+
+ ScopedAttribute sa1(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, EnableAccess);
+ ScopedAttribute sa2(m_page->settings(), QWebEngineSettings::ErrorPageEnabled, false);
+
+ if (UserGesture) {
+#ifdef Q_OS_WIN
+ QString localUrl1("file:///" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/link.html?linkLocation=link.html");
+ QString localUrl2("file:///" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/link.html");
+#else
+ QString localUrl1("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/link.html?linkLocation=link.html");
+ QString localUrl2("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/link.html");
+#endif
+
+ QVERIFY(verifyLoad(localUrl1));
+ QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, elementCenter(m_page, "link"));
+ // Succeed independently of EnableAccess == false
+ QTRY_COMPARE(m_page->url(), localUrl2);
+
+ // Back/forward navigation is also allowed, however they are not user gesture
+ m_page->triggerAction(QWebEnginePage::Back);
+ QTRY_COMPARE(m_page->url(), localUrl1);
+ m_page->triggerAction(QWebEnginePage::Forward);
+ QTRY_COMPARE(m_page->url(), localUrl2);
+ } else {
+ QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/mixedXHR.html"));
+ eval("sendXHR('file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/mixedXHR.txt" + "')");
+ QTRY_COMPARE(eval("result"), (EnableAccess ? QString("ok") : QString("error")));
+ }
+}
+
+// Load the main page over one scheme with an iframe over another scheme.
+//
+// For file and qrc schemes, the iframe should load but it should not be
+// possible for scripts in different frames to interact.
+//
+// Additionally for unregistered custom schemes and custom schemes without
+// LocalAccessAllowed it should not be possible to load an iframe over the
+// file: scheme.
+void tst_Origins::mixedSchemes_data()
+{
+ QTest::addColumn<QString>("schemeFrom");
+ QTest::addColumn<QVariantMap>("testPairs");
+
+ QVariant SLF = QVariant(QSL("canLoadAndAccess")), OK = QVariant(QSL("canLoadButNotAccess")),
+ ERR = QVariant(QSL("cannotLoad"));
+ std::vector<std::pair<const char *, std::vector<std::pair<const char *, QVariant>>>> data = {
+ { "file",
+ {
+ { "file", SLF },
+ { "qrc", OK },
+ { "tst", ERR },
+ } },
+ { "qrc",
+ {
+ { "file", ERR },
+ { "qrc", SLF },
+ { "tst", OK },
+ } },
+ { "tst",
+ {
+ { "file", ERR },
+ { "qrc", OK },
+ { "tst", SLF },
+ } },
+ { "PathSyntax",
+ {
+ { "PathSyntax", SLF },
+ { "PathSyntax-Local", ERR },
+ { "PathSyntax-LocalAccessAllowed", OK },
+ { "PathSyntax-NoAccessAllowed", OK },
+ } },
+ { "PathSyntax-LocalAccessAllowed",
+ {
+ { "PathSyntax", OK },
+ { "PathSyntax-Local", OK },
+ { "PathSyntax-LocalAccessAllowed", SLF },
+ { "PathSyntax-NoAccessAllowed", OK },
+ } },
+ { "PathSyntax-NoAccessAllowed",
+ {
+ { "PathSyntax", OK },
+ { "PathSyntax-Local", ERR },
+ { "PathSyntax-LocalAccessAllowed", OK },
+ { "PathSyntax-NoAccessAllowed", OK },
+ } },
+ { "HostSyntax://a",
+ {
+ { "HostSyntax://a", SLF },
+ { "HostSyntax://b", OK },
+ } },
+ { "local-localaccess",
+ {
+ { "local-cors", OK },
+ { "local-localaccess", SLF },
+ { "local", OK },
+ } },
+ { "local-cors",
+ {
+ { "local", OK },
+ { "local-cors", SLF },
+ } },
+ };
+
+ for (auto &&d : data) {
+ auto schemeFrom = d.first;
+ QVariantMap testPairs;
+ for (auto &&destSchemes : d.second) {
+ auto &&destScheme = destSchemes.first;
+ testPairs[destScheme] = destSchemes.second;
+ }
+ QTest::addRow("%s", schemeFrom) << schemeFrom << testPairs;
+ }
+}
+
+static QStringList protocolAndHost(const QString scheme)
+{
+ static QString srcDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath());
+ QStringList result;
+ if (scheme == QSL("file")) {
+ return QStringList{ scheme, srcDir };
+ }
+ if (scheme.contains(QSL("HostSyntax:"))) {
+ const QStringList &res = scheme.split(':');
+ Q_ASSERT(res.size() == 2);
+ return res;
+ }
+ return QStringList{ scheme, "" };
+}
+
+void tst_Origins::mixedSchemes()
+{
+ QFETCH(QString, schemeFrom);
+ QFETCH(QVariantMap, testPairs);
+
+ ScopedAttribute sa(m_page->settings(), QWebEngineSettings::ErrorPageEnabled, false);
+ QString srcDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath());
+ QString host;
+ auto pah = protocolAndHost(schemeFrom);
+ auto loadUrl = QString("%1:%2/resources/mixedSchemes.html").arg(pah[0]).arg(pah[1]);
+ QVERIFY(verifyLoad(loadUrl));
+
+ QStringList schemesTo, expected, results;
+ for (auto it = testPairs.begin(), end = testPairs.end(); it != end; ++it) {
+
+ auto schemeTo = it.key();
+ auto pah = protocolAndHost(schemeTo);
+ auto expectedResult = it.value().toString();
+ auto frameUrl = QString("%1:%2/resources/mixedSchemes_frame.html").arg(pah[0]).arg(pah[1]);
+ auto imgUrl = QString("%1:%2/resources/red.png").arg(pah[0]).arg(pah[1]);
+
+ eval(QString("setIFrameUrl('%1','%2')").arg(frameUrl).arg(imgUrl));
+
+ // wait for token in the log
+ QTRY_VERIFY(m_page->logContainsDoneMarker());
+ const QString result = m_page->findResultInLog();
+ m_page->clearLog();
+ schemesTo.append(schemeTo.rightJustified(20));
+ results.append(result.rightJustified(20));
+ expected.append(expectedResult.rightJustified(20));
+ }
+
+ QVERIFY2(results == expected,
+ qPrintable(QString("\nFrom '%1' to:\n\tScheme: %2\n\tActual: %3\n\tExpect: %4")
+ .arg(schemeFrom)
+ .arg(schemesTo.join(' '))
+ .arg(results.join(' '))
+ .arg(expected.join(' '))));
+}
+
+// Like mixedSchemes but adds a Content-Security-Policy: frame-src 'none' header.
+void tst_Origins::mixedSchemesWithCsp()
+{
+ QVERIFY(verifyLoad(QSL("HostSyntax://a/resources/mixedSchemesWithCsp.html")));
+ eval(QSL("setIFrameUrl('HostSyntax://a/resources/mixedSchemes_frame.html')"));
+ QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
+ eval(QSL("setIFrameUrl('HostSyntax://b/resources/mixedSchemes_frame.html')"));
+ QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
+
+ QVERIFY(verifyLoad(QSL("HostSyntax-ContentSecurityPolicyIgnored://a/resources/mixedSchemesWithCsp.html")));
+ eval(QSL("setIFrameUrl('HostSyntax-ContentSecurityPolicyIgnored://a/resources/mixedSchemes_frame.html')"));
+ QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess")));
+ eval(QSL("setIFrameUrl('HostSyntax-ContentSecurityPolicyIgnored://b/resources/mixedSchemes_frame.html')"));
+ QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
+}
+
+// Load the main page over one scheme, then make an XMLHttpRequest to a
+// different scheme.
+//
+// Cross-origin XMLHttpRequests can only be made to CORS-enabled schemes. These
+// include the builtin schemes http, https, data, and chrome, as well as custom
+// schemes with the CorsEnabled flag.
+void tst_Origins::mixedXHR_data()
+{
+ QTest::addColumn<QString>("schemeFrom");
+ QTest::addColumn<bool>("canAccessFileUrls");
+ QTest::addColumn<bool>("canAccessRemoteUrl");
+ QTest::addColumn<QVariantMap>("testPairs");
+
+ bool defaultFileAccess = QWebEnginePage().settings()->testAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls);
+ bool defaultRemoteAccess = QWebEnginePage().settings()->testAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls);
+ Q_ASSERT(defaultFileAccess);
+ Q_ASSERT(!defaultRemoteAccess);
+ std::vector<std::pair<bool, bool>> settingCombinations = {
+ { defaultFileAccess, defaultRemoteAccess }, // tag: *schemeFrom*_local_noremote
+ { defaultFileAccess, !defaultRemoteAccess }, // tag: *schemeFrom*_local_remote
+ { !defaultFileAccess, defaultRemoteAccess }, // tag: *schemeFrom*_nolocal_noremote
+ { !defaultFileAccess, !defaultRemoteAccess } // tag: *schemeFrom*_nolocal_remote
+ };
+
+ QVariant OK = QString("ok"), ERR = QString("error");
+ std::vector<
+ std::pair<const char *, std::vector<
+ std::pair<const char *, std::vector<QVariant>>>>> data = {
+ { "file", {
+ { "file", { OK, OK, ERR, ERR } },
+ { "qrc", { ERR, ERR, ERR, ERR } },
+ { "tst", { ERR, ERR, ERR, ERR } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { ERR, OK, ERR, OK } },
+ { "local-localaccess", { OK, OK, ERR, ERR } },
+ { "local-cors", { OK, OK, ERR, ERR } }, } },
+
+ { "qrc", {
+ { "file", { ERR, ERR, ERR, ERR } },
+ { "qrc", { OK, OK, OK, OK } },
+ { "tst", { ERR, ERR, ERR, ERR } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { OK, OK, OK, OK } },
+ { "local-localaccess", { ERR, ERR, ERR, ERR } },
+ { "local-cors", { ERR, ERR, ERR, ERR } }, } },
+
+ { "tst", {
+ { "file", { ERR, ERR, ERR, ERR } },
+ { "qrc", { ERR, ERR, ERR, ERR } },
+ { "tst", { OK, OK, OK, OK } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { OK, OK, OK, OK } },
+ { "local-localaccess", { ERR, ERR, ERR, ERR } },
+ { "local-cors", { ERR, ERR, ERR, ERR } }, } },
+
+ { "cors", { // -local +cors -local-access
+ { "file", { ERR, ERR, ERR, ERR } },
+ { "qrc", { ERR, ERR, ERR, ERR } },
+ { "tst", { ERR, ERR, ERR, ERR } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { OK, OK, OK, OK } },
+ { "local-localaccess", { ERR, ERR, ERR, ERR } },
+ { "local-cors", { ERR, ERR, ERR, ERR } }, } },
+
+ { "local", { // +local -cors -local-access
+ { "file", { OK, OK, ERR, ERR } },
+ { "qrc", { ERR, ERR, ERR, ERR } },
+ { "tst", { ERR, ERR, ERR, ERR } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { ERR, OK, ERR, OK } },
+ { "local-localaccess", { OK, OK, ERR, ERR } },
+ { "local-cors", { OK, OK, ERR, ERR } }, } },
+
+ { "local-cors", { // +local +cors -local-access
+ { "file", { OK, OK, ERR, ERR } },
+ { "qrc", { ERR, ERR, ERR, ERR } },
+ { "tst", { ERR, ERR, ERR, ERR } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { ERR, OK, ERR, OK } },
+ { "local-localaccess", { OK, OK, ERR, ERR } },
+ { "local-cors", { OK, OK, ERR, ERR } }, } },
+
+ { "local-localaccess", { // +local -cors +local-access
+ { "file", { OK, OK, OK, OK } },
+ { "qrc", { ERR, ERR, ERR, ERR } },
+ { "tst", { ERR, ERR, ERR, ERR } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { ERR, OK, ERR, OK } },
+ { "local-localaccess", { OK, OK, OK, OK } },
+ { "local-cors", { OK, OK, OK, OK } }, } },
+
+ { "localaccess", { // -local -cors +local-access
+ { "file", { OK, OK, OK, OK } },
+ { "qrc", { ERR, ERR, ERR, ERR } },
+ { "tst", { ERR, ERR, ERR, ERR } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { OK, OK, OK, OK } },
+ { "local-localaccess", { OK, OK, OK, OK } },
+ { "local-cors", { OK, OK, OK, OK } }, } },
+ };
+
+ for (auto &&d : data) {
+ auto schemeFrom = d.first;
+
+ for (int i = 0; i < 4; ++i) {
+ const auto &it = settingCombinations[i];
+ bool canAccessFileUrls = it.first, canAccessRemoteUrl = it.second;
+
+ QVariantMap testPairs;
+ for (auto &&destSchemes : d.second) {
+ auto &&destScheme = destSchemes.first;
+ auto &&expectedResults = destSchemes.second;
+ testPairs[destScheme] = expectedResults[i];
+ }
+
+ QTest::addRow("%s_%s_%s", schemeFrom, (canAccessFileUrls ? "local" : "nolocal"), (canAccessRemoteUrl ? "remote" : "noremote"))
+ << schemeFrom << canAccessFileUrls << canAccessRemoteUrl << testPairs;
+ }
+ }
+}
+
+void tst_Origins::mixedXHR()
+{
+ QFETCH(QString, schemeFrom);
+ QFETCH(bool, canAccessFileUrls);
+ QFETCH(bool, canAccessRemoteUrl);
+ QFETCH(QVariantMap, testPairs);
+
+ QString srcDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath());
+ auto loadUrl = QString("%1:%2/resources/mixedXHR.html").arg(schemeFrom).arg(schemeFrom == "file" ? srcDir : "");
+ auto sendXHR = [&] (const QString &scheme) {
+ if (scheme == "data")
+ return QString("sendXHR('data:,ok')");
+ return QString("sendXHR('%1:%2/resources/mixedXHR.txt')").arg(scheme).arg(scheme == "file" ? srcDir : "");
+ };
+
+ QCOMPARE(testPairs.size(), 7);
+ ScopedAttribute sa0(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, canAccessFileUrls);
+ ScopedAttribute sa1(m_page->settings(), QWebEngineSettings::LocalContentCanAccessRemoteUrls, canAccessRemoteUrl);
+ QVERIFY(verifyLoad(loadUrl));
+
+ QStringList schemesTo, expected, results;
+ for (auto it = testPairs.begin(), end = testPairs.end(); it != end; ++it) {
+ auto schemeTo = it.key();
+ auto expectedResult = it.value().toString();
+ auto command = sendXHR(schemeTo);
+
+ eval(command);
+
+ QTRY_COMPARE(eval(QSL("result !== undefined")), QVariant(true));
+ auto result = eval(QSL("result")).toString();
+
+ schemesTo.append(schemeTo.rightJustified(10));
+ results.append(result.rightJustified(10));
+ expected.append(expectedResult.rightJustified(10));
+ }
+ QVERIFY2(results == expected,
+ qPrintable(QString("From '%1' to:\n\tScheme: %2\n\tActual: %3\n\tExpect: %4")
+ .arg(schemeFrom).arg(schemesTo.join(' ')).arg(results.join(' ')).arg(expected.join(' '))));
+}
+
+// Load the main page over one scheme, then load an iframe over a different scheme. This load is not considered CORS.
+void tst_Origins::mixedContent_data()
+{
+ QTest::addColumn<QString>("schemeFrom");
+ QTest::addColumn<bool>("canAccessFileUrls");
+ QTest::addColumn<bool>("canAccessRemoteUrl");
+ QTest::addColumn<QVariantMap>("testPairs");
+
+ bool defaultFileAccess = true;
+ bool defaultRemoteAccess = false;
+ std::vector<std::pair<bool, bool>> settingCombinations = {
+ { defaultFileAccess, defaultRemoteAccess }, // tag: *schemeFrom*_local_noremote
+ { defaultFileAccess, !defaultRemoteAccess }, // tag: *schemeFrom*_local_remote
+ { !defaultFileAccess, defaultRemoteAccess }, // tag: *schemeFrom*_nolocal_noremote
+ { !defaultFileAccess, !defaultRemoteAccess } // tag: *schemeFrom*_nolocal_remote
+ };
+
+ QVariant SLF = QVariant(QSL("canLoadAndAccess")), OK = QVariant(QSL("canLoadButNotAccess")), ERR = QVariant(QSL("cannotLoad"));
+ std::vector<
+ std::pair<const char *, std::vector<
+ std::pair<const char *, std::vector<QVariant>>>>> data = {
+ { "file", {
+ { "file", { SLF, SLF, ERR, ERR } },
+ { "qrc", { OK, OK, OK, OK } },
+ { "tst", { ERR, OK, ERR, OK } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { ERR, OK, ERR, OK } },
+ { "local-localaccess", { OK, OK, ERR, ERR } },
+ { "local-cors", { OK, OK, ERR, ERR } },
+ } },
+
+ { "qrc", {
+ { "file", { ERR, ERR, ERR, ERR } },
+ { "qrc", { SLF, SLF, SLF, SLF } },
+ { "tst", { OK, OK, OK, OK } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { OK, OK, OK, OK } },
+ { "local-localaccess", { ERR, ERR, ERR, ERR } },
+ { "local-cors", { ERR, ERR, ERR, ERR } }, } },
+
+ { "tst", {
+ { "file", { ERR, ERR, ERR, ERR } },
+ { "qrc", { OK, OK, OK, OK } },
+ { "tst", { SLF, SLF, SLF, SLF } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { OK, OK, OK, OK } },
+ { "local-localaccess", { ERR, ERR, ERR, ERR } },
+ { "local-cors", { ERR, ERR, ERR, ERR } }, } },
+
+ { "cors", { // -local +cors -local-access
+ { "file", { ERR, ERR, ERR, ERR } },
+ { "qrc", { OK, OK, OK, OK } },
+ { "tst", { OK, OK, OK, OK } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { SLF, SLF, SLF, SLF } },
+ { "local-localaccess", { ERR, ERR, ERR, ERR } },
+ { "local-cors", { ERR, ERR, ERR, ERR } }, } },
+
+ { "local", { // +local -cors -local-access
+ { "file", { OK, OK, ERR, ERR } },
+ { "qrc", { OK, OK, OK, OK } },
+ { "tst", { ERR, OK, ERR, OK } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { ERR, OK, ERR, OK } },
+ { "local-localaccess", { OK, OK, ERR, ERR } },
+ { "local-cors", { OK, OK, ERR, ERR } },
+ } },
+
+ { "local-cors", { // +local +cors -local-access
+ { "file", { OK, OK, ERR, ERR } },
+ { "qrc", { OK, OK, OK, OK } },
+ { "tst", { ERR, OK, ERR, OK } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { ERR, OK, ERR, OK } },
+ { "local-localaccess", { OK, OK, ERR, ERR } },
+ { "local-cors", { SLF, SLF, ERR, ERR } },
+ } },
+
+ { "local-localaccess", { // +local -cors + OK-access
+ { "file", { OK, OK, OK, OK } },
+ { "qrc", { OK, OK, OK, OK } },
+ { "tst", { ERR, OK, ERR, OK } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { ERR, OK, ERR, OK } },
+ { "local-localaccess", { SLF, SLF, OK, OK } }, // ### should probably be: SLF, SLF, SLF, SLF
+ { "local-cors", { OK, OK, OK, OK } },
+ } },
+
+ { "localaccess", { // -local -cors +local-access
+ { "file", { OK, OK, OK, OK } },
+ { "qrc", { OK, OK, OK, OK } },
+ { "tst", { OK, OK, OK, OK } },
+ { "data", { OK, OK, OK, OK } },
+ { "cors", { OK, OK, OK, OK } },
+ { "local-localaccess", { OK, OK, OK, OK } },
+ { "local-cors", { OK, OK, OK, OK } }, } },
+ };
+
+ for (auto &&d : data) {
+ auto schemeFrom = d.first;
+
+ for (int i = 0; i < 4; ++i) {
+ const auto &it = settingCombinations[i];
+ bool canAccessFileUrls = it.first, canAccessRemoteUrl = it.second;
+
+ QVariantMap testPairs;
+ for (auto &&destSchemes : d.second) {
+ auto &&destScheme = destSchemes.first;
+ auto &&expectedResults = destSchemes.second;
+ testPairs[destScheme] = expectedResults[i];
+ }
+
+ QTest::addRow("%s_%s_%s", schemeFrom, (canAccessFileUrls ? "local" : "nolocal"), (canAccessRemoteUrl ? "remote" : "noremote"))
+ << schemeFrom << canAccessFileUrls << canAccessRemoteUrl << testPairs;
+ }
+ }
+}
+
+void tst_Origins::mixedContent()
+{
+ QFETCH(QString, schemeFrom);
+ QFETCH(bool, canAccessFileUrls);
+ QFETCH(bool, canAccessRemoteUrl);
+ QFETCH(QVariantMap, testPairs);
+
+ QString srcDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath());
+ auto loadUrl = QString("%1:%2/resources/mixedSchemes.html").arg(schemeFrom).arg(schemeFrom == "file" ? srcDir : "");
+
+ QCOMPARE(testPairs.size(), 7);
+ ScopedAttribute sa2(m_page->settings(), QWebEngineSettings::ErrorPageEnabled, false);
+ ScopedAttribute sa0(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, canAccessFileUrls);
+ ScopedAttribute sa1(m_page->settings(), QWebEngineSettings::LocalContentCanAccessRemoteUrls, canAccessRemoteUrl);
+ QVERIFY(verifyLoad(loadUrl));
+
+ auto setIFrameUrl = [&] (const QString &scheme) {
+ if (scheme == "data")
+ return QString("setIFrameUrl('data:,<script>var canary = true; parent.canary = "
+ "true</script>','data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA"
+ "AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/"
+ "w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==')");
+ auto frameUrl = QString("%1:%2/resources/mixedSchemes_frame.html").arg(scheme).arg(scheme == "file" ? srcDir : "");
+ auto imgUrl =
+ QString("%1:%2/resources/red.png").arg(scheme).arg(scheme == "file" ? srcDir : "");
+ return QString("setIFrameUrl('%1','%2')").arg(frameUrl).arg(imgUrl);
+ };
+
+ m_page->clearLog();
+ QStringList schemesTo, expected, results;
+ for (auto it = testPairs.begin(), end = testPairs.end(); it != end; ++it) {
+
+ auto schemeTo = it.key();
+ auto expectedResult = it.value().toString();
+
+ eval(setIFrameUrl(schemeTo));
+
+ // wait for token in the log
+ QTRY_VERIFY(m_page->logContainsDoneMarker());
+ const QString result = m_page->findResultInLog();
+ m_page->clearLog();
+ schemesTo.append(schemeTo.rightJustified(20));
+ results.append(result.rightJustified(20));
+ expected.append(expectedResult.rightJustified(20));
+ }
+ QVERIFY2(results == expected,
+ qPrintable(QString("\nFrom '%1' to:\n\tScheme: %2\n\tActual: %3\n\tExpect: %4")
+ .arg(schemeFrom).arg(schemesTo.join(' ')).arg(results.join(' ')).arg(expected.join(' '))));
+}
+
+#if defined(WEBSOCKETS)
+class EchoServer : public QObject {
+ Q_OBJECT
+ Q_PROPERTY(QUrl url READ url NOTIFY urlChanged)
+public:
+ EchoServer() : webSocketServer(QSL("EchoServer"), QWebSocketServer::NonSecureMode)
+ {
+ connect(&webSocketServer, &QWebSocketServer::newConnection, this, &EchoServer::onNewConnection);
+ }
+
+ bool listen()
+ {
+ if (webSocketServer.listen(QHostAddress::Any)) {
+ Q_EMIT urlChanged();
+ return true;
+ }
+ return false;
+ }
+
+ QUrl url() const
+ {
+ return webSocketServer.serverUrl();
+ }
+
+Q_SIGNALS:
+ void urlChanged();
+
+private:
+ void onNewConnection()
+ {
+ QWebSocket *socket = webSocketServer.nextPendingConnection();
+ connect(socket, &QWebSocket::textMessageReceived, this, &EchoServer::onTextMessageReceived);
+ connect(socket, &QWebSocket::disconnected, socket, &QObject::deleteLater);
+ }
+
+ void onTextMessageReceived(const QString &message)
+ {
+ QWebSocket *socket = qobject_cast<QWebSocket *>(sender());
+ socket->sendTextMessage(message);
+ }
+
+ QWebSocketServer webSocketServer;
+};
+
+// Try opening a WebSocket from pages loaded over various URL schemes.
+void tst_Origins::webSocket()
+{
+ EchoServer echoServer;
+ QWebChannel channel;
+ channel.registerObject(QSL("echoServer"), &echoServer);
+ m_page->setWebChannel(&channel);
+ QVERIFY(echoServer.listen());
+
+ QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/websocket.html"));
+ QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok")));
+
+ QVERIFY(verifyLoad(QSL("qrc:/resources/websocket.html")));
+ QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok")));
+
+ // Unregistered schemes can also open WebSockets (since Chromium 71)
+ QVERIFY(verifyLoad(QSL("tst:/resources/websocket2.html")));
+ QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok")));
+
+ // Even an insecure registered scheme can open WebSockets.
+ QVERIFY(verifyLoad(QSL("PathSyntax:/resources/websocket2.html")));
+ QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok")));
+}
+#endif
+// Create a (Dedicated)Worker. Since dedicated workers can only be accessed from
+// one page, there is not much need for security restrictions.
+void tst_Origins::dedicatedWorker()
+{
+ QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/dedicatedWorker.html"));
+ QTRY_VERIFY(eval(QSL("done")).toBool());
+ QCOMPARE(eval(QSL("result")), QVariant(42));
+
+ QVERIFY(verifyLoad(QSL("qrc:/resources/dedicatedWorker.html")));
+ QTRY_VERIFY(eval(QSL("done")).toBool());
+ QCOMPARE(eval(QSL("result")), QVariant(42));
+
+ // Unregistered schemes can also create Workers (since Chromium 71)
+ QVERIFY(verifyLoad(QSL("tst:/resources/dedicatedWorker.html")));
+ QTRY_VERIFY(eval(QSL("done")).toBool());
+ QCOMPARE(eval(QSL("result")), QVariant(42));
+
+ // Even an insecure registered scheme can create Workers.
+ QVERIFY(verifyLoad(QSL("PathSyntax:/resources/dedicatedWorker.html")));
+ QTRY_VERIFY(eval(QSL("done")).toBool());
+ QCOMPARE(eval(QSL("result")), QVariant(42));
+
+ // But not if the NoAccessAllowed flag is set.
+ QVERIFY(verifyLoad(QSL("PathSyntax-NoAccessAllowed:/resources/dedicatedWorker.html")));
+ QTRY_VERIFY(eval(QSL("done")).toBool());
+ QVERIFY(eval(QSL("error")).toString()
+ .contains(QSL("cannot be accessed from origin 'null'")));
+}
+
+// Create a SharedWorker. Shared workers can be accessed from multiple pages,
+// and therefore the same-origin policy applies.
+void tst_Origins::sharedWorker()
+{
+ {
+ ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, false);
+ QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/sharedWorker.html"));
+ QTRY_VERIFY_WITH_TIMEOUT(eval(QSL("done")).toBool(), 10000);
+ QVERIFY(eval(QSL("error")).toString()
+ .contains(QSL("cannot be accessed from origin 'null'")));
+ }
+
+ {
+ ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, true);
+ QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/sharedWorker.html"));
+ QTRY_VERIFY_WITH_TIMEOUT(eval(QSL("done")).toBool(), 10000);
+ QCOMPARE(eval(QSL("result")), QVariant(42));
+ }
+
+ QVERIFY(verifyLoad(QSL("qrc:/resources/sharedWorker.html")));
+ QTRY_VERIFY_WITH_TIMEOUT(eval(QSL("done")).toBool(), 10000);
+ QCOMPARE(eval(QSL("result")), QVariant(42));
+
+ // Unregistered schemes should not create SharedWorkers.
+
+ QVERIFY(verifyLoad(QSL("PathSyntax:/resources/sharedWorker.html")));
+ QTRY_VERIFY_WITH_TIMEOUT(eval(QSL("done")).toBool(), 10000);
+ QCOMPARE(eval(QSL("result")), QVariant(42));
+
+ QVERIFY(verifyLoad(QSL("PathSyntax-NoAccessAllowed:/resources/sharedWorker.html")));
+ QTRY_VERIFY_WITH_TIMEOUT(eval(QSL("done")).toBool(), 10000);
+ QVERIFY(eval(QSL("error")).toString()
+ .contains(QSL("denied to origin 'null'")));
+}
+
+// Service workers have to be explicitly enabled for a scheme.
+void tst_Origins::serviceWorker()
+{
+ QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/serviceWorker.html"));
+ QTRY_VERIFY(eval(QSL("done")).toBool());
+ QVERIFY(eval(QSL("error")).toString()
+ .contains(QSL("The URL protocol of the current origin ('file://') is not supported.")));
+
+ QVERIFY(verifyLoad(QSL("qrc:/resources/serviceWorker.html")));
+ QTRY_VERIFY(eval(QSL("done")).toBool());
+ QVERIFY(eval(QSL("error")).toString()
+ .contains(QSL("The URL protocol of the current origin ('qrc:') is not supported.")));
+
+ QVERIFY(verifyLoad(QSL("tst:/resources/serviceWorker.html")));
+ QTRY_VERIFY(eval(QSL("done")).toBool());
+ QVERIFY(eval(QSL("error")).toString()
+ .contains(QSL("Cannot read properties of undefined")));
+
+ QVERIFY(verifyLoad(QSL("PathSyntax:/resources/serviceWorker.html")));
+ QTRY_VERIFY(eval(QSL("done")).toBool());
+ QVERIFY(eval(QSL("error")).toString()
+ .contains(QSL("Cannot read properties of undefined")));
+
+ QVERIFY(verifyLoad(QSL("PathSyntax-Secure:/resources/serviceWorker.html")));
+ QTRY_VERIFY(eval(QSL("done")).toBool());
+ QVERIFY(eval(QSL("error")).toString()
+ .contains(QSL("The URL protocol of the current origin ('pathsyntax-secure:') is not supported.")));
+
+ QVERIFY(verifyLoad(QSL("PathSyntax-ServiceWorkersAllowed:/resources/serviceWorker.html")));
+ QTRY_VERIFY(eval(QSL("done")).toBool());
+ QVERIFY(eval(QSL("error")).toString()
+ .contains(QSL("Cannot read properties of undefined")));
+
+ QVERIFY(verifyLoad(QSL("PathSyntax-Secure-ServiceWorkersAllowed:/resources/serviceWorker.html")));
+ QTRY_VERIFY(eval(QSL("done")).toBool());
+ QCOMPARE(eval(QSL("error")), QVariant());
+
+ QVERIFY(verifyLoad(QSL("PathSyntax-NoAccessAllowed:/resources/serviceWorker.html")));
+ QTRY_VERIFY(eval(QSL("done")).toBool());
+ QVERIFY(eval(QSL("error")).toString()
+ .contains(QSL("Cannot read properties of undefined")));
+}
+
+// Support for view-source must be enabled explicitly.
+void tst_Origins::viewSource()
+{
+ QVERIFY(verifyLoad("view-source:file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/viewSource.html"));
+#ifdef Q_OS_WIN
+ QCOMPARE(m_page->requestedUrl().toString(),
+ "file:///" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/viewSource.html");
+#else
+ QCOMPARE(m_page->requestedUrl().toString(),
+ "file://" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/viewSource.html");
+#endif
+
+ QVERIFY(verifyLoad(QSL("view-source:qrc:/resources/viewSource.html")));
+ QCOMPARE(m_page->requestedUrl().toString(), QSL("qrc:/resources/viewSource.html"));
+
+ QVERIFY(verifyLoad(QSL("view-source:tst:/resources/viewSource.html")));
+ QCOMPARE(m_page->requestedUrl().toString(), QSL("about:blank"));
+
+ QVERIFY(verifyLoad(QSL("view-source:PathSyntax:/resources/viewSource.html")));
+ QCOMPARE(m_page->requestedUrl().toString(), QSL("about:blank"));
+
+ QVERIFY(verifyLoad(QSL("view-source:PathSyntax-ViewSourceAllowed:/resources/viewSource.html")));
+ QCOMPARE(m_page->requestedUrl().toString(), QSL("pathsyntax-viewsourceallowed:/resources/viewSource.html"));
+}
+
+void tst_Origins::createObjectURL()
+{
+ // Legal for registered custom schemes.
+ QVERIFY(verifyLoad(QSL("qrc:/resources/createObjectURL.html")));
+ QVERIFY(eval(QSL("result")).toString().startsWith(QSL("blob:qrc:")));
+
+ // Also legal for unregistered schemes (since Chromium 71)
+ QVERIFY(verifyLoad(QSL("tst:/resources/createObjectURL.html")));
+ QVERIFY(eval(QSL("result")).toString().startsWith(QSL("blob:tst:")));
+}
+
+void tst_Origins::redirectScheme()
+{
+ QVERIFY(verifyLoad(QSL("redirect:cors/resources/redirect.html")));
+ eval("addStylesheetLink('redirect:cors/resources/redirect.css')");
+ QTRY_COMPARE(m_handler->requests().size(), 4);
+ QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("redirect:cors/resources/redirect.html")));
+ QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("cors:/resources/redirect.html")));
+ QCOMPARE(m_handler->requests()[2], QUrl(QStringLiteral("redirect:cors/resources/redirect.css")));
+ QCOMPARE(m_handler->requests()[3], QUrl(QStringLiteral("cors:/resources/redirect.css")));
+
+ QVERIFY(!verifyLoad(QSL("redirect:file/resources/redirect.html")));
+ QVERIFY(!verifyLoad(QSL("redirect:local/resources/redirect.html")));
+ QVERIFY(!verifyLoad(QSL("redirect:local-cors/resources/redirect.html")));
+}
+
+void tst_Origins::redirectSchemeLocal()
+{
+ QVERIFY(verifyLoad(QSL("redirect-local:local/resources/redirect.html")));
+ eval("addStylesheetLink('redirect-local:local/resources/redirect.css')");
+ QTRY_COMPARE(m_handler->requests().size(), 4);
+ QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("redirect-local:local/resources/redirect.html")));
+ QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("local:/resources/redirect.html")));
+ QCOMPARE(m_handler->requests()[2], QUrl(QStringLiteral("redirect-local:local/resources/redirect.css")));
+ QCOMPARE(m_handler->requests()[3], QUrl(QStringLiteral("local:/resources/redirect.css")));
+}
+
+void tst_Origins::redirectSchemeSecure()
+{
+ QVERIFY(verifyLoad(QSL("redirect-secure:secure-cors/resources/redirect.html")));
+ eval("addStylesheetLink('redirect-secure:secure-cors/resources/redirect.css')");
+ QTRY_COMPARE(m_handler->requests().size(), 4);
+ QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("redirect-secure:secure-cors/resources/redirect.html")));
+ QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("secure-cors:/resources/redirect.html")));
+ QCOMPARE(m_handler->requests()[2], QUrl(QStringLiteral("redirect-secure:secure-cors/resources/redirect.css")));
+ QCOMPARE(m_handler->requests()[3], QUrl(QStringLiteral("secure-cors:/resources/redirect.css")));
+}
+
+void tst_Origins::redirectInterceptor()
+{
+ TestRequestInterceptor interceptor;
+ m_profile.setUrlRequestInterceptor(&interceptor);
+
+ QVERIFY(verifyLoad(QSL("redirect:cors/resources/redirect.html")));
+ eval("addStylesheetLink('redirect:cors/resources/redirect.css')");
+
+ QTRY_COMPARE(interceptor.requests.size(), 4);
+ QTRY_COMPARE(m_handler->requests().size(), 2);
+ QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("cors:/resources/redirect.html")));
+ QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("cors:/resources/redirect.css")));
+
+ QCOMPARE(interceptor.requests[0], QUrl(QStringLiteral("redirect:cors/resources/redirect.html")));
+ QCOMPARE(interceptor.requests[1], QUrl(QStringLiteral("cors:/resources/redirect.html")));
+ QCOMPARE(interceptor.requests[2], QUrl(QStringLiteral("redirect:cors/resources/redirect.css")));
+ QCOMPARE(interceptor.requests[3], QUrl(QStringLiteral("cors:/resources/redirect.css")));
+
+ QVERIFY(!verifyLoad(QSL("redirect:file/resources/redirect.html")));
+ QVERIFY(!verifyLoad(QSL("redirect:local/resources/redirect.html")));
+ QVERIFY(!verifyLoad(QSL("redirect:local-cors/resources/redirect.html")));
+}
+
+void tst_Origins::redirectInterceptorLocal()
+{
+ TestRequestInterceptor interceptor;
+ m_profile.setUrlRequestInterceptor(&interceptor);
+
+ QVERIFY(verifyLoad(QSL("redirect-local:local/resources/redirect.html")));
+ eval("addStylesheetLink('redirect-local:local/resources/redirect.css')");
+
+ QTRY_COMPARE(interceptor.requests.size(), 4);
+ QTRY_COMPARE(m_handler->requests().size(), 2);
+ QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("local:/resources/redirect.html")));
+ QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("local:/resources/redirect.css")));
+
+ QCOMPARE(interceptor.requests[0], QUrl(QStringLiteral("redirect-local:local/resources/redirect.html")));
+ QCOMPARE(interceptor.requests[1], QUrl(QStringLiteral("local:/resources/redirect.html")));
+ QCOMPARE(interceptor.requests[2], QUrl(QStringLiteral("redirect-local:local/resources/redirect.css")));
+ QCOMPARE(interceptor.requests[3], QUrl(QStringLiteral("local:/resources/redirect.css")));
+}
+
+void tst_Origins::redirectInterceptorSecure()
+{
+ TestRequestInterceptor interceptor;
+ m_profile.setUrlRequestInterceptor(&interceptor);
+
+ QVERIFY(verifyLoad(QSL("redirect-secure:secure-cors/resources/redirect.html")));
+ eval("addStylesheetLink('redirect-secure:secure-cors/resources/redirect.css')");
+
+ QTRY_COMPARE(interceptor.requests.size(), 4);
+ QTRY_COMPARE(m_handler->requests().size(), 2);
+ QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("secure-cors:/resources/redirect.html")));
+ QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("secure-cors:/resources/redirect.css")));
+
+ QCOMPARE(interceptor.requests[0], QUrl(QStringLiteral("redirect-secure:secure-cors/resources/redirect.html")));
+ QCOMPARE(interceptor.requests[1], QUrl(QStringLiteral("secure-cors:/resources/redirect.html")));
+ QCOMPARE(interceptor.requests[2], QUrl(QStringLiteral("redirect-secure:secure-cors/resources/redirect.css")));
+ QCOMPARE(interceptor.requests[3], QUrl(QStringLiteral("secure-cors:/resources/redirect.css")));
+}
+
+class TestRedirectInterceptor : public QWebEngineUrlRequestInterceptor
+{
+public:
+ TestRedirectInterceptor() = default;
+ void interceptRequest(QWebEngineUrlRequestInfo &info) override
+ {
+ qCDebug(lc) << this << "Type:" << info.resourceType() << info.requestMethod() << "Navigation:" << info.navigationType()
+ << info.requestUrl() << "Initiator:" << info.initiator();
+
+ QUrl url = info.requestUrl();
+ requests << url;
+ if (url.path().startsWith("/redirect")) {
+ QString path = url.path();
+ int idx = path.indexOf(QChar('/'), 10);
+ if (idx > 0) {
+ url.setScheme(path.mid(10, idx - 10));
+ url.setPath(path.mid(idx, -1));
+ url.setHost({});
+ info.redirect(url);
+ }
+ }
+ }
+ QList<QUrl> requests;
+};
+
+void tst_Origins::redirectInterceptorFile()
+{
+ TestRedirectInterceptor interceptor;
+ m_profile.setUrlRequestInterceptor(&interceptor);
+
+ QVERIFY(verifyLoad(QSL("file:///redirect/local-cors/resources/redirect.html")));
+ eval("addStylesheetLink('file:///redirect/local-cors/resources/redirect.css')");
+
+ QTRY_COMPARE(interceptor.requests.size(), 4);
+ QTRY_COMPARE(m_handler->requests().size(), 2);
+ QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("local-cors:/resources/redirect.html")));
+ QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("local-cors:/resources/redirect.css")));
+
+ QCOMPARE(interceptor.requests[0], QUrl(QStringLiteral("file:///redirect/local-cors/resources/redirect.html")));
+ QCOMPARE(interceptor.requests[1], QUrl(QStringLiteral("local-cors:/resources/redirect.html")));
+ QCOMPARE(interceptor.requests[2], QUrl(QStringLiteral("file:///redirect/local-cors/resources/redirect.css")));
+ QCOMPARE(interceptor.requests[3], QUrl(QStringLiteral("local-cors:/resources/redirect.css")));
+}
+
+void tst_Origins::redirectInterceptorHttp()
+{
+ TestRedirectInterceptor interceptor;
+ m_profile.setUrlRequestInterceptor(&interceptor);
+
+ QVERIFY(verifyLoad(QSL("http://hallo/redirect/cors/resources/redirect.html")));
+ eval("addStylesheetLink('http://hallo/redirect/cors/resources/redirect.css')");
+
+ QTRY_COMPARE(interceptor.requests.size(), 4);
+ QTRY_COMPARE(m_handler->requests().size(), 2);
+ QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("cors:/resources/redirect.html")));
+ QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("cors:/resources/redirect.css")));
+
+ QCOMPARE(interceptor.requests[0], QUrl(QStringLiteral("http://hallo/redirect/cors/resources/redirect.html")));
+ QCOMPARE(interceptor.requests[1], QUrl(QStringLiteral("cors:/resources/redirect.html")));
+ QCOMPARE(interceptor.requests[2], QUrl(QStringLiteral("http://hallo/redirect/cors/resources/redirect.css")));
+ QCOMPARE(interceptor.requests[3], QUrl(QStringLiteral("cors:/resources/redirect.css")));
+}
+
+void tst_Origins::localMediaBlock_data()
+{
+ QTest::addColumn<bool>("enableAccess");
+ QTest::addRow("enabled") << true;
+ QTest::addRow("disabled") << false;
+}
+
+void tst_Origins::localMediaBlock()
+{
+ QFETCH(bool, enableAccess);
+
+ std::atomic<bool> accessed = false;
+ HttpServer server;
+ server.setResourceDirs({ QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + "/resources" });
+ connect(&server, &HttpServer::newRequest, [&](HttpReqRep *) { accessed.store(true); });
+ QVERIFY(server.start());
+
+ ScopedAttribute sa1(m_page->settings(), QWebEngineSettings::LocalContentCanAccessRemoteUrls, enableAccess);
+
+ QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/media.html"));
+ eval("addAudio('" + server.url("/mixedXHR.txt").toString() + "')");
+
+ // Give it a chance to avoid a false positive on the default value of accessed.
+ if (!enableAccess)
+ QTest::qSleep(500);
+ QTRY_COMPARE(accessed.load(), enableAccess);
+
+}
+
+class FetchApiHandler : public QWebEngineUrlSchemeHandler
+{
+ Q_OBJECT
+public:
+ FetchApiHandler(QByteArray schemeName, QObject *parent = nullptr)
+ : QWebEngineUrlSchemeHandler(parent), m_schemeName(schemeName)
+ {
+ }
+
+ void requestStarted(QWebEngineUrlRequestJob *job) override
+ {
+ QCOMPARE(job->requestUrl(), QUrl(m_schemeName + ":about"));
+ fetchWasAllowed = true;
+ }
+
+ bool fetchWasAllowed = false;
+
+private:
+ QByteArray m_schemeName;
+};
+
+class FetchApiPage : public QWebEnginePage
+{
+ Q_OBJECT
+
+signals:
+ void jsCalled();
+
+public:
+ FetchApiPage(QWebEngineProfile *profile, QObject *parent = nullptr)
+ : QWebEnginePage(profile, parent)
+ {
+ }
+
+protected:
+ void javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel,
+ const QString &message, int, const QString &) override
+ {
+ qCritical() << "js:" << message;
+ emit jsCalled();
+ }
+};
+
+void tst_Origins::fetchApiCustomUrl_data()
+{
+ QTest::addColumn<QUrl>("url");
+ QTest::addColumn<QByteArray>("fetchApiScheme");
+ QTest::addColumn<bool>("expectedFetchWasAllowed");
+
+ QTest::newRow("custom url with fetch allowed flag")
+ << QUrl("qrc:///resources/fetchApi.html?printRes=false&url=fetchapi-allowed:about")
+ << QBAL("fetchapi-allowed") << true;
+ QTest::newRow("custom url without fetch allowed flag")
+ << QUrl("qrc:///resources/fetchApi.html?printRes=false&url=fetchapi-not-allowed:about")
+ << QBAL("fetchapi-not-allowed") << false;
+}
+
+void tst_Origins::fetchApiCustomUrl()
+{
+ QFETCH(QUrl, url);
+ QFETCH(QByteArray, fetchApiScheme);
+ QFETCH(bool, expectedFetchWasAllowed);
+
+ QWebEngineProfile profile;
+ FetchApiHandler handler(fetchApiScheme);
+
+ profile.installUrlSchemeHandler(fetchApiScheme, &handler);
+
+ FetchApiPage page(&profile);
+ QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
+ QSignalSpy jsSpy(&page, SIGNAL(jsCalled()));
+
+ if (fetchApiScheme == "fetchapi-not-allowed") {
+ QTest::ignoreMessage(QtCriticalMsg, QRegularExpression("Failed to fetch"));
+ QTest::ignoreMessage(
+ QtCriticalMsg,
+ QRegularExpression("Fetch API cannot load fetchapi-not-allowed:about."));
+ }
+
+ page.load(url);
+ QTRY_VERIFY(loadSpy.count() > 0);
+ QTRY_COMPARE(handler.fetchWasAllowed, expectedFetchWasAllowed);
+
+ if (fetchApiScheme == "fetchapi-not-allowed") {
+ QTRY_VERIFY(jsSpy.count() > 0);
+ }
+}
+
+void tst_Origins::fetchApiHttpUrl()
+{
+ HttpServer httpServer;
+ QObject::connect(&httpServer, &HttpServer::newRequest, this, [](HttpReqRep *rr) {
+ rr->setResponseBody(QBAL("Fetch Was Allowed"));
+ rr->setResponseHeader(QBAL("Access-Control-Allow-Origin"), QBAL("*"));
+ rr->sendResponse();
+ });
+ QVERIFY(httpServer.start());
+
+ QWebEngineProfile profile;
+ FetchApiPage page(&profile);
+
+ QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
+ QSignalSpy jsSpy(&page, SIGNAL(jsCalled()));
+
+ QTest::ignoreMessage(QtCriticalMsg, QRegularExpression("Fetch Was Allowed"));
+
+ const QByteArray fullUrl = QByteArray("qrc:///resources/fetchApi.html?printRes=true&url=")
+ + httpServer.url("/somepage.html").toEncoded();
+ page.load(QUrl(fullUrl));
+
+ QTRY_VERIFY(loadSpy.count() > 0);
+ QTRY_VERIFY(jsSpy.count() > 0);
+ QVERIFY(httpServer.stop());
+}
+
+QTEST_MAIN(tst_Origins)
+#include "tst_origins.moc"
diff --git a/tests/auto/core/qtversion/CMakeLists.txt b/tests/auto/core/qtversion/CMakeLists.txt
new file mode 100644
index 000000000..9a5e89266
--- /dev/null
+++ b/tests/auto/core/qtversion/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_internal_add_test(tst_qtversion
+ SOURCES
+ tst_qtversion.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+)
+
diff --git a/tests/auto/core/qtversion/tst_qtversion.cpp b/tests/auto/core/qtversion/tst_qtversion.cpp
new file mode 100644
index 000000000..44c2d4e5c
--- /dev/null
+++ b/tests/auto/core/qtversion/tst_qtversion.cpp
@@ -0,0 +1,34 @@
+// 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/qwebenginepage.h>
+
+class tst_QtVersion : public QObject
+{
+ Q_OBJECT
+signals:
+ void done();
+private Q_SLOTS:
+ void checkVersion();
+};
+
+void tst_QtVersion::checkVersion()
+{
+ QWebEnginePage page;
+ QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished);
+ QSignalSpy doneSpy(this, &tst_QtVersion::done);
+ page.load(QUrl("chrome://qt"));
+ QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 12000);
+ page.toPlainText([this](const QString &result) {
+ QVERIFY(result.contains(qWebEngineVersion()));
+ QVERIFY(result.contains(qWebEngineChromiumVersion()));
+ QVERIFY(result.contains(qWebEngineChromiumSecurityPatchVersion()));
+ emit done();
+ });
+ QTRY_VERIFY(doneSpy.size());
+}
+
+QTEST_MAIN(tst_QtVersion)
+
+#include "tst_qtversion.moc"
diff --git a/tests/auto/core/qwebengineclientcertificatestore/CMakeLists.txt b/tests/auto/core/qwebengineclientcertificatestore/CMakeLists.txt
new file mode 100644
index 000000000..8cee7f630
--- /dev/null
+++ b/tests/auto/core/qwebengineclientcertificatestore/CMakeLists.txt
@@ -0,0 +1,80 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+include(../../httpserver/httpserver.cmake)
+include(../../util/util.cmake)
+
+qt_internal_add_test(tst_qwebengineclientcertificatestore
+ SOURCES
+ tst_qwebengineclientcertificatestore.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+ Test::HttpServer
+ Test::Util
+)
+
+set(tst_qwebengineclientcertificatestore_resource_files
+ "resources/certificate.crt"
+ "resources/certificate1.crt"
+ "resources/privatekey.key"
+ "resources/privatekey1.key"
+ "resources/server.pem"
+ "resources/server.key"
+ "resources/client.pem"
+ "resources/client.key"
+ "resources/client2.pem"
+ "resources/client2.key"
+ "resources/ca.pem"
+)
+
+qt_internal_add_resource(tst_qwebengineclientcertificatestore "tst_qwebengineclientcertificatestore"
+ PREFIX
+ "/"
+ FILES
+ ${tst_qwebengineclientcertificatestore_resource_files}
+)
+
+if(LINUX AND NOT CMAKE_CROSSCOMPILING)
+
+ get_filename_component(homePath $ENV{HOME} ABSOLUTE)
+
+ find_program(pk12util_EXECUTABLE NAMES pk12util)
+ find_program(certutil_EXECUTABLE NAMES certutil)
+
+ if(pk12util_EXECUTABLE AND certutil_EXECUTABLE)
+ add_custom_command(
+ DEPENDS resources/client2.p12
+ COMMAND test -e "${homePath}/.pki/nssdb" || ${CMAKE_COMMAND} -E make_directory
+ "${homePath}/.pki/nssdb"
+ COMMAND test -e "${homePath}/.pki/nssdb/cert9.db" || ${certutil_EXECUTABLE}
+ -N --empty-password -d sql:${homePath}/.pki/nssdb
+ COMMAND test -e "${homePath}/.pki/nssdb/cert9.db" && ${pk12util_EXECUTABLE}
+ -d sql:${homePath}/.pki/nssdb
+ -i "${CMAKE_CURRENT_LIST_DIR}/resources/client2.p12"
+ -W ""
+ COMMAND ${CMAKE_COMMAND} -E touch pk12util.stamp
+ OUTPUT pk12util.stamp
+ VERBATIM
+ USES_TERMINAL
+ )
+ add_custom_target(
+ add-user-personal-certificate
+ DEPENDS pk12util.stamp
+ )
+ qt_internal_extend_target(tst_qwebengineclientcertificatestore DEFINES TEST_NSS)
+ add_dependencies(tst_qwebengineclientcertificatestore add-user-personal-certificate)
+ endif()
+
+ find_program(certutil_EXECUTABLE NAMES certutil)
+
+ if(certutil_EXECUTABLE)
+ add_custom_target(remove-user-personal-certificate
+ COMMAND ${CMAKE_COMMAND} -E remove pk12util.stamp
+ COMMAND ${certutil_EXECUTABLE}
+ -d sql:"${homePath}/.pki/nssdb"
+ -D
+ -n qwebengineclientcertificatestore
+ )
+ endif()
+endif()
+
diff --git a/tests/auto/core/qwebengineclientcertificatestore/qwebengineclientcertificatestore.pro b/tests/auto/core/qwebengineclientcertificatestore/qwebengineclientcertificatestore.pro
deleted file mode 100644
index e99c7f493..000000000
--- a/tests/auto/core/qwebengineclientcertificatestore/qwebengineclientcertificatestore.pro
+++ /dev/null
@@ -1 +0,0 @@
-include(../tests.pri)
diff --git a/tests/auto/core/qwebengineclientcertificatestore/resources/ca.pem b/tests/auto/core/qwebengineclientcertificatestore/resources/ca.pem
new file mode 100644
index 000000000..cb62ad62c
--- /dev/null
+++ b/tests/auto/core/qwebengineclientcertificatestore/resources/ca.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIECzCCAvOgAwIBAgIUdhDW1WgGxF313LYA0JjEQpKbanQwDQYJKoZIhvcNAQEL
+BQAwgZQxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl
+cmxpbjEXMBUGA1UECgwOVGhlIFF0IENvbXBhbnkxFDASBgNVBAsMC1F0V2ViRW5n
+aW5lMRIwEAYDVQQDDAl3d3cucXQuaW8xIDAeBgkqhkiG9w0BCQEWEXF0d2ViZW5n
+aW5lQHF0LmlvMB4XDTIyMTExNjExMDQxNFoXDTMyMTExMzExMDQxNFowgZQxCzAJ
+BgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEXMBUG
+A1UECgwOVGhlIFF0IENvbXBhbnkxFDASBgNVBAsMC1F0V2ViRW5naW5lMRIwEAYD
+VQQDDAl3d3cucXQuaW8xIDAeBgkqhkiG9w0BCQEWEXF0d2ViZW5naW5lQHF0Lmlv
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxyNLLwAA+FgNQavVJ19n
+gdoy+NKLHQyhzcRFykKSp9aAbpAR6e4ukxwG7mWNBcuR7zv1Zw/JqLFE0gmVztVw
+FeQWdw1cvTN/OlVEuM+0ShTDHHsCqRpx7/XJT6ytMKVU8jdZN4Vl1m7MubWv4aPy
+0WYYd3zIAicciYgy/RHaRhPTKpPzWIPYhmHsM5w2cebL8I0aZXUkC0OeklJArnp9
+007Fr6SXXK0xQ3RO20n7X193gCfd5U70lug0ks/ZZqxtzPHmzIO1WGAOBura50HR
+hxUKAu7qQHzBiW5Qwdn0af4FPLJR/SX8ADKTLCSWlMOo1FLYO5w6D8hB4K6/b9VQ
+RwIDAQABo1MwUTAdBgNVHQ4EFgQUXuTuB85/iBgwJpLdOc+8TB0KESIwHwYDVR0j
+BBgwFoAUXuTuB85/iBgwJpLdOc+8TB0KESIwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAQEAvtucUJa0IECltWv8U6R+LQuZ1Q+ubbmstojO/h8tg6Wf
+v6FZ5bH3oboSyGEcytRr6INf4G6znUNAypbodehAEW6/PETdzGM9CJyv2JPJAWzV
+rxb1H5VTyiEs8924QOqcNATD+oe7G0vwnDkvprcqaWBA6yvQkWpCXoqMc+F95KnY
+8VFt2VQw17l4L4nhaX3Us6hJLMiKV+dLeF0pN+pkCPRP9G5WKgW3mT2U6Gig+rLz
+6L7rBbb5KWAttdAbuHCrMa65PgXoVD1P/GteFxUnghDd0PWgUaign8c/DyHGsrbA
+uvJqSym0kmQQXptryRaKFsGcCrizdbE6FfrH2iE7vQ==
+-----END CERTIFICATE-----
diff --git a/tests/auto/core/qwebengineclientcertificatestore/resources/client.key b/tests/auto/core/qwebengineclientcertificatestore/resources/client.key
new file mode 100644
index 000000000..21c8e3183
--- /dev/null
+++ b/tests/auto/core/qwebengineclientcertificatestore/resources/client.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAqnHbq38y1VprEaV2xXzv2nAPyjqCuIfuick8qETkzEsNWPQi
+dsBlLfcyf+15wEMhpRIwILXCrUM7Sb7WCGtg1XC00JZvCh2xPBMSD2fiQyHn4men
+Fwh9vVbTf1v7w21ZT/pXQrwlgLgNWYZHE3JrcEAwlThQRIdQfzSE6/QeHfYZoGB9
+WfvbREsOWiUlZze/yrblS9vnAVhYwVurelc7lXyHA0dHmkcZ0HwMxVJZ/vLuCyIw
+lNGT/ytnA9p1l8uFkAgTcbWZKoyJAsAZG9faZp46hk8+e3KAyKQ78aoUSbjAqnNQ
+tBM3bnHeHanf3ddCxyej+k9PfSIY27a9FZxHpQIDAQABAoIBAFsomA8p8ZsQR9Fh
+SJupDXMrmhZTotRkxxxkR4/LgP8OaO4ZbFFM5xBldFndPc+pV9Y8WwczjxIxsgTo
+Dvrjyx98rwgcXPjxFniFzpP0wJudB7McMs5r2SwpwuYL4SQNWMYgowjrLbehOGqY
+GW16NaIMgq9cNfng0RmnkivMHUtyE5GGdK+C6cyK+fIE+cNtQtHPRKfEnwbE9VHz
+3EY/nCXGZvMFyj5uHaU4EeZFCzo19TUqhh8H7b0EA44pBtb5U/CxsH4xphZ7rpjt
+iVjMfRSMR4qalQNIs6ZEj57We+M/zca/Qq1yhjW+0NYbZifcYo1Oj6e4lC9YlIgn
+kGkcuUECgYEA1j0iVFjgBXS8pJP3jBgmbrbBBTNEUv27yjnJCAQx5TbplJkvBM4/
+qzum1uH2o6uRrFtrYJFiAhDHARtg+70rMeYqZp8WFvzJT5c5s+FOmGQPfFjgrD6e
+wfnCwFzS7nohJ8TM2mPGJ88pBv0eBYW6D0f7fvcJmEk8hnGktdLRCrECgYEAy6tU
+YFZDzGhbgrG2wWzBvAKVngUNhrYZHMiF1WVN8zZdCm7Z8b1S/NMe0rPA5orhAkSX
+8fxlDfKOm+U2fKp43aiN0NDiP0TlGRbypAXe7FSnvDxNHbV+Ie0UbwuiJ4s3vJuc
+6cdzgKqAs5/rjPXPdUpM8C7344HV7azgSzHIYTUCgYAtVmCmcuxtmye0uG+BoTa4
+5UnxvMivu2x7PkFRxfl9JWLHBKfTn4YPyZ7kCIu2VT+NtwcBN6MDBuPmUxHyFDVI
+6Ql+EBqPoM1FX55hd8O3Mi2oxfI94T6dlCpnpP0qZIQRs28apFSx5gArr3Mj/gnC
+5BvP4Z2RMaZyWShfJg8A8QKBgQClZEhswyDjiYtmorJqeMsKxn6BiFDnqFDUUvJ7
+zHx0mR0NL9/Es54Eud059ccccIMwuEs7s17M6MBuUMDik/z647nmbPqNroDs0vnP
+wQS6njRoY/+rtIrtOf1x/9x6iE+G1keigNmHDu7c72z1V1hVQzUfhsS+99yl2dF6
+vr6eUQKBgF/OHW1bE3FruZ+53Arcb94N/IKnpH9VWoB3elIzr0w6pLtL4HHhmQ58
+TayEpq6YguUAjTvCBbaHuYuKPHiXCAy5DhtrXvP4YdMNH9X1nHc7jVEbGltVbnQU
+bG/p5YfZSrDmsjf8w0z7feFOcovC6vF1YCXc8OHK/LQ6JFJ/gtO1
+-----END RSA PRIVATE KEY-----
diff --git a/tests/auto/core/qwebengineclientcertificatestore/resources/client.pem b/tests/auto/core/qwebengineclientcertificatestore/resources/client.pem
new file mode 100644
index 000000000..dd1f898f7
--- /dev/null
+++ b/tests/auto/core/qwebengineclientcertificatestore/resources/client.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApcCFFNQAgGBu5nr81tUMdXXLGkm8Li+MA0GCSqGSIb3DQEBCwUAMIGU
+MQswCQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4x
+FzAVBgNVBAoMDlRoZSBRdCBDb21wYW55MRQwEgYDVQQLDAtRdFdlYkVuZ2luZTES
+MBAGA1UEAwwJd3d3LnF0LmlvMSAwHgYJKoZIhvcNAQkBFhFxdHdlYmVuZ2luZUBx
+dC5pbzAeFw0yMjExMTYxMjExMDFaFw0zMjExMTMxMjExMDFaMIGSMQswCQYDVQQG
+EwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xFzAVBgNVBAoM
+DlRoZSBRdCBDb21wYW55MRQwEgYDVQQLDAtRdFdlYkVuZ2luZTEVMBMGA1UEAwwM
+Y2xpZW50LnF0LmlvMRswGQYJKoZIhvcNAQkBFgxjbGllbnRAcXQuaW8wggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqcdurfzLVWmsRpXbFfO/acA/KOoK4
+h+6JyTyoROTMSw1Y9CJ2wGUt9zJ/7XnAQyGlEjAgtcKtQztJvtYIa2DVcLTQlm8K
+HbE8ExIPZ+JDIefiZ6cXCH29VtN/W/vDbVlP+ldCvCWAuA1ZhkcTcmtwQDCVOFBE
+h1B/NITr9B4d9hmgYH1Z+9tESw5aJSVnN7/KtuVL2+cBWFjBW6t6VzuVfIcDR0ea
+RxnQfAzFUln+8u4LIjCU0ZP/K2cD2nWXy4WQCBNxtZkqjIkCwBkb19pmnjqGTz57
+coDIpDvxqhRJuMCqc1C0Ezducd4dqd/d10LHJ6P6T099Ihjbtr0VnEelAgMBAAEw
+DQYJKoZIhvcNAQELBQADggEBALE75ZQxmEXJA16cNAxxmxCKHkaqAE6Ulim1vXNH
+jCFfNCDGYn/R28F3BVtMe+bIMoomaTh3h5eOd/9uc2nm8IiT5FUz9epJWPeRG/cl
+I+hQ3fvaE7oJ3m3EwfGq1mdqUf1zi+DFjtkimNbn9ZRDocZfpO5VN0u23ptEuk0P
+5cH4+Dst0giRMv5W0kXG6QD13H/eVH3jDZCtZa/8T4oxGGskHEa4yDr8s976lVOV
+XLI1r7oN4a/KXKow8WN3oHFeKn4QJx86z1uecuZLtT8xjABKSWpZqgsIlmGTGE1a
+9W06C+uPVamwn5ND3gnf93YQqn6PwrjlHdrQOTG/vngJLPw=
+-----END CERTIFICATE-----
diff --git a/tests/auto/core/qwebengineclientcertificatestore/resources/client2.key b/tests/auto/core/qwebengineclientcertificatestore/resources/client2.key
new file mode 100644
index 000000000..3c1346519
--- /dev/null
+++ b/tests/auto/core/qwebengineclientcertificatestore/resources/client2.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAv0vrzULGwDJBoZgnGXdkMFxCvkTqqQYCE/LlNtStLJfJH7Fo
+CgenVFcJ8RIFHdkL7HeFAIZjDLSjIp2Ud41fd+VsaGgB/+j1/UeEN8nkArvYB9ol
+OnKGq6CbSrCocrLo2o2X+6eyLtrtLG6RLr8/UiqB2OWNAdnw70S5RCvnbV6phr8z
+bgYqPdPSBaedfZk5Kj6yM6XvIKSK6IjgZuo+Z5SyabJqk2VhaBlB7mjCf3Mj4zPD
+XvQXsAq0ZNQXQVwKRfJ2I9uAeNAZiQP5i00pBqe2kIJEKnk8qbP4/Jho2Tp8XSBC
+jHMn0oWrAZyO9vw3W940qmqmdRftyt+J8DO9xwIDAQABAoIBAGBpXTCYRR88tQNC
+cgJNv/r3pNPMXBBP7OAs/QUDbzwYS89jVDIp5VWGgIY1NMr0RyQooKnBEU6oA8hA
+b0FJySHeSSLduJRHzyKV1rdfU0Fldt2OPlEUw3bgfSPJoTwdm2n7DuxQemdPA1Xv
+a9CJpto8fjDYkJasRtfwZQdMsVjXCfQ/cCzkOkblUDZcc7yTx3uiBKF8Jy8C+0qc
+98btotYU88KWoE9A0ucWt/ik68MjYmccO6PYXKerNW2Ijgd1kik35G3TbEWxOFWW
+y3zLFtfoD+21SdUgTMzM06owDVfSt/MER4tOxFyUPRuze7BJXrBofGQfuPiGiPuK
+f5QZP8ECgYEA+x1PkClsqtRnjrzmRfi3OFez1Kbbzneucg5ssWR+Hd4EUFhhO42q
+te1ZYoydy09tEqd002U7e5hob0/o+rVK9jldpZszMCBfVDYCDqdtw5rNI89bL1Uz
+8krn6nk3BBx42lgAFU4C1JEaur4r14OOUtoFfRTAwjogQHcDmpyPNjcCgYEAwwSv
+FJAKRjw1oOXKlGotoeYEAREVxH9HFnfM5IcVwcwMt+KUFEyrMtXeH1gk7jo+2ev4
+87njQ8hU3VPObCUcnTJHi2a6D9JIY+zA9bKTJjc8drcBathipmwtak14TsX2qe14
+JBIKlC3V0h1FqM3ep76p4dnt7sTmVc7ZOqBR7PECgYEA1HQE94wEkzdnch0hmbuG
+kBWrYNPXDgS1w2uuzBqglPZcoflUMkV2U7s+r6EWc4d8WZbxwVRZkgTs/pgWHd66
+UD1SnKUFFsecv6t97BX9SMu0mYJ6vD4S2ABF3Fu3jzPjj596WowI2vz1J19zyj9U
+b4ZjtGKVfv4cgU3v76RbidsCgYAx4CvKzX/jMJjimoJx7KnZAxO5Fh6ED60loOQE
++ktlMgN6r/cBLg6GxM23JHrldn4Gi+QyqTLnbf/OTxW28NLdnTNRAqfJThV3gOBk
+thQOLQhIsEsrgUXRnE8NJd0EAHsyQGp+hyKvfP13bEcZgfVU311hRrQkYbUq8uj5
+pnDtcQKBgEFIpP7EzdJWrVOUjnjMQloqBhW8KVVtNwI5bmlcsUvVYjfZph016SiF
+UTfZss1KkBmQClAVtyZsrKIfObIJ9KJ4hPAzzk+ca1D6XTLsYjxPwtB/U0ewB2Dm
+yMxkXpT1kAiJ2Tdr1hZ8OcQhvnGWmrhtz+AkjyLXiYgST7Hubrxt
+-----END RSA PRIVATE KEY-----
diff --git a/tests/auto/core/qwebengineclientcertificatestore/resources/client2.p12 b/tests/auto/core/qwebengineclientcertificatestore/resources/client2.p12
new file mode 100644
index 000000000..81e7eb624
--- /dev/null
+++ b/tests/auto/core/qwebengineclientcertificatestore/resources/client2.p12
Binary files differ
diff --git a/tests/auto/core/qwebengineclientcertificatestore/resources/client2.pem b/tests/auto/core/qwebengineclientcertificatestore/resources/client2.pem
new file mode 100644
index 000000000..39c0b3f09
--- /dev/null
+++ b/tests/auto/core/qwebengineclientcertificatestore/resources/client2.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDsTCCApkCFFNQAgGBu5nr81tUMdXXLGkm8LjBMA0GCSqGSIb3DQEBCwUAMIGU
+MQswCQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4x
+FzAVBgNVBAoMDlRoZSBRdCBDb21wYW55MRQwEgYDVQQLDAtRdFdlYkVuZ2luZTES
+MBAGA1UEAwwJd3d3LnF0LmlvMSAwHgYJKoZIhvcNAQkBFhFxdHdlYmVuZ2luZUBx
+dC5pbzAeFw0yMjExMTYxOTIwMzBaFw0zMjExMTMxOTIwMzBaMIGUMQswCQYDVQQG
+EwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xFzAVBgNVBAoM
+DlRoZSBRdCBDb21wYW55MRQwEgYDVQQLDAtRdFdlYkVuZ2luZTEWMBQGA1UEAwwN
+Y2xpZW50Mi5xdC5pbzEcMBoGCSqGSIb3DQEJARYNY2xpZW50MkBxdC5pbzCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9L681CxsAyQaGYJxl3ZDBcQr5E
+6qkGAhPy5TbUrSyXyR+xaAoHp1RXCfESBR3ZC+x3hQCGYwy0oyKdlHeNX3flbGho
+Af/o9f1HhDfJ5AK72AfaJTpyhqugm0qwqHKy6NqNl/unsi7a7SxukS6/P1Iqgdjl
+jQHZ8O9EuUQr521eqYa/M24GKj3T0gWnnX2ZOSo+sjOl7yCkiuiI4GbqPmeUsmmy
+apNlYWgZQe5own9zI+Mzw170F7AKtGTUF0FcCkXydiPbgHjQGYkD+YtNKQantpCC
+RCp5PKmz+PyYaNk6fF0gQoxzJ9KFqwGcjvb8N1veNKpqpnUX7crfifAzvccCAwEA
+ATANBgkqhkiG9w0BAQsFAAOCAQEAic8F8q1TpP2ufnBRbrBp54Jgddl/zdVb7O3M
+AAK67KiEpEr9xPPVcIowfns1ZTIsIB8D4VS4NQGJXBrwvGWL08SpSmi76I1E156x
+9Hql0PHXCjqsJTOSEvljIgQ4sp33zs0DTmlyejSSGnG9sw2FtcYAGZNV+ImAhTO2
+DNxw3BnF++ilHsQbiWIKD5z14bOXb77SJrimup0YBzfwBWJO013k8g8lkiRRs5Ng
+XYVr3NoTLcIJQ7BTFu4W1Wegxwrw3fQZ98BBlCVh0htrOcLpWKelJeI16MgZA/7T
+P4MwvN5tkyjqrcsrDORldR6JKdX8i+GLF49MgRW4QispcZzoYA==
+-----END CERTIFICATE-----
diff --git a/tests/auto/core/qwebengineclientcertificatestore/resources/server.key b/tests/auto/core/qwebengineclientcertificatestore/resources/server.key
new file mode 100644
index 000000000..632cc4d2e
--- /dev/null
+++ b/tests/auto/core/qwebengineclientcertificatestore/resources/server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA5gQoJryenjmvzy4RbqHdNXHK8Gk/8Lto1SwT8+Wbh5EyYRTt
+hFdioT1JYcIe3XMwOmx3TjADY1jAXAPfeRcjTkMcnZwF76AXUK2XqBANhaG1wjsi
+b7ISGU/U5/Jarm2iwQJ5zjKsNm8pZYqpmKsYAVFMErtfcpdLdSp6BG54SrbItcXh
+WHfsUs5cuVEi9nCeugLkDzoPLlj/TeouKWOdzhyvLXkPvPmD4/hD0dULTXpCDZhf
+73AuQBWTGsWeUnJQiQhDRwuXWhGRX8qFJQ4rzY8rIbaKhge+BQ6BL+pij2uzHKNQ
+j12ZLFZgLihLDJogGp08y9Ud6Ru/3WGoFkY38wIDAQABAoIBABM/TczQA8XhteB0
+Tmkfik8qknzDkeInDIKqCZFjKTyS3dBZ2/YzCcHMSxOvFr4ZIXQCF4mnYuExUAdj
+G5QaZ43o98AIikae8tSBcitSDI+eFIOIRz1pfTI5B+vQz93AttnHx0GF4/s6GhCx
+JbfsuTmDAAahPz9rgZjwUP2F8PLvaAZqJrXBPY+QLWz0SN2zh6vWAHPbJA0sO/4E
+oWUhRPXJDf33YCFxnwtbUBie5313suAfNspODcyH+AxBH2FFh63pe0ZGOhX7XFMJ
+yxJqujeZrQdfwFZNPXAPVLJGbd7AIOrVE+O8/bYUB/uuj6pPJBqr+Ob/JhY48pRb
+VG2qL4ECgYEA9n3PuL13F9XFcLeergGH7fUcSQeD1T6Z1qaI2Wth0Umfmer/fFZh
+IKSCSwEGMTLsalFdlTj8jsSAasjuSorQTeSgHjzvzik1Ll2P6syputjsD1RX/nkl
+8L50Pwdeey57Y9dgow7Cw/heGYs6dkXLe9H6qM7eoB8Vrk7/TAFuqNECgYEA7uOl
+oKyOxeLn005cenc5enY2IxDhXTaAjTGHE64C0lmicD2OZB7/b+ZIb8M5R7GnCNox
+4TxLSRhZYOMO/QcTrnSND5PXbX/HLd3nyQRIN1XtBbg7pJooxP/MQ/Ne5XTTMjCg
+qPudkOe0ZgUHEcuH8m/YAFY3DDJC50uiXqYtxYMCgYBHfL+ExbZHfGExyp9Duf/x
+PHhCmeJbMzessEnaPLF24FJgcm48YlTzAaMkG5zvIeS9BPIOOCPPSCAyWCn8BnxZ
+SuhBPM0TzpG067+0ijzjiswTuhN3Iy2kv6e5K+rz8MwqbamCQOKtsVehMub2rFFS
+jNiUosKgT8Oa9SBHq9arMQKBgQCE3EVEnFP3iOAILH/QeLiV/GLVk9DTR7mtTUtj
+zZayKLnoFMQ5uOe182x8BCa6UfqlOL0fGKqCZ7Fl6kJuxV3T2+yMKlxZAQTk5JLB
+wMjtRbPCR5mcTUS5c87GR/eSRCwlsNfZw775VXSGfOtWoUzlsACBB3IsLVP6UZ1n
+aKLyQwKBgC61BvKiyGBEYIchqMI4dSF+zCJbSjNUtjwVobcgC6yERZtX2OeLFCoh
+NEf9CcL2Eqb+RzwAD3OV65AiZcrThQNXZ8poBxvwWK8I6E6zB+LX7POAvNu/AV/5
+ANnxwHGGLqi+wTcdMZal2iXkdsrno1Ek/nGMCdA7IVs7l5k7fEpG
+-----END RSA PRIVATE KEY-----
diff --git a/tests/auto/core/qwebengineclientcertificatestore/resources/server.pem b/tests/auto/core/qwebengineclientcertificatestore/resources/server.pem
new file mode 100644
index 000000000..4706fa73e
--- /dev/null
+++ b/tests/auto/core/qwebengineclientcertificatestore/resources/server.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApcCFFNQAgGBu5nr81tUMdXXLGkm8Li/MA0GCSqGSIb3DQEBCwUAMIGU
+MQswCQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4x
+FzAVBgNVBAoMDlRoZSBRdCBDb21wYW55MRQwEgYDVQQLDAtRdFdlYkVuZ2luZTES
+MBAGA1UEAwwJd3d3LnF0LmlvMSAwHgYJKoZIhvcNAQkBFhFxdHdlYmVuZ2luZUBx
+dC5pbzAeFw0yMjExMTYxMjExMTRaFw0zMjExMTMxMjExMTRaMIGSMQswCQYDVQQG
+EwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xFzAVBgNVBAoM
+DlRoZSBRdCBDb21wYW55MRQwEgYDVQQLDAtRdFdlYkVuZ2luZTEVMBMGA1UEAwwM
+c2VydmVyLnF0LmlvMRswGQYJKoZIhvcNAQkBFgxzZXJ2ZXJAcXQuaW8wggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDmBCgmvJ6eOa/PLhFuod01ccrwaT/w
+u2jVLBPz5ZuHkTJhFO2EV2KhPUlhwh7dczA6bHdOMANjWMBcA995FyNOQxydnAXv
+oBdQrZeoEA2FobXCOyJvshIZT9Tn8lqubaLBAnnOMqw2bylliqmYqxgBUUwSu19y
+l0t1KnoEbnhKtsi1xeFYd+xSzly5USL2cJ66AuQPOg8uWP9N6i4pY53OHK8teQ+8
++YPj+EPR1QtNekINmF/vcC5AFZMaxZ5SclCJCENHC5daEZFfyoUlDivNjyshtoqG
+B74FDoEv6mKPa7Mco1CPXZksVmAuKEsMmiAanTzL1R3pG7/dYagWRjfzAgMBAAEw
+DQYJKoZIhvcNAQELBQADggEBAHotgaBbqIlG4EqjzSpX8kQnZnGJUsA51dbY3K5C
+4tNCd+JquQfPmCIKDHkRsmmEU6pcU+LT8m+toJ8Gx0XG4nrdUIDt0Nlf/QrykbPj
+hN8z+aSfP9J5tg4NsT7qMWmqUHOa3BcsgWcC4IwWVkbOMz/XbczEQqdBJMbE0+PC
+32ihTKPZBPC2QlIvXyuwupvQtcXgEjw1r2FQeYcmItk3CKbJPE/Rk4/aXSCo4b0F
+iXPphh8BJPZVvQ2cLpPaGvcse5qjIhF9ODb2HEK3myMwuJVi7teURy8mPlS23Li/
+8gRCNu/stjMlkic7d3dqV0LwaG8+Df1W2wzxsT7IkxN/Z+o=
+-----END CERTIFICATE-----
diff --git a/tests/auto/core/qwebengineclientcertificatestore/tst_qwebengineclientcertificatestore.cpp b/tests/auto/core/qwebengineclientcertificatestore/tst_qwebengineclientcertificatestore.cpp
index 6d51bf7af..7d82a5640 100644
--- a/tests/auto/core/qwebengineclientcertificatestore/tst_qwebengineclientcertificatestore.cpp
+++ b/tests/auto/core/qwebengineclientcertificatestore/tst_qwebengineclientcertificatestore.cpp
@@ -1,34 +1,14 @@
-/****************************************************************************
-**
-** Copyright (C) 2019 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtWebEngine module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include <httpsserver.h>
+#include <util.h>
#include <QtTest/QtTest>
#include <QtWebEngineCore/qwebengineclientcertificatestore.h>
-#include <QtWebEngineWidgets/qwebengineprofile.h>
+#include <QtWebEngineCore/qwebenginepage.h>
+#include <QtWebEngineCore/qwebengineprofile.h>
+#include <QtWebEngineCore/qwebenginecertificateerror.h>
+#include <QtWebEngineCore/qwebenginesettings.h>
class tst_QWebEngineClientCertificateStore : public QObject
{
@@ -39,8 +19,12 @@ public:
~tst_QWebEngineClientCertificateStore();
private Q_SLOTS:
+ void init();
+ void cleanup();
void addAndListCertificates();
void removeAndClearCertificates();
+ void clientAuthentication_data();
+ void clientAuthentication();
};
tst_QWebEngineClientCertificateStore::tst_QWebEngineClientCertificateStore()
@@ -51,6 +35,19 @@ tst_QWebEngineClientCertificateStore::~tst_QWebEngineClientCertificateStore()
{
}
+void tst_QWebEngineClientCertificateStore::init()
+{
+ QCOMPARE(0,
+ QWebEngineProfile::defaultProfile()->clientCertificateStore()->certificates().size());
+}
+
+void tst_QWebEngineClientCertificateStore::cleanup()
+{
+ QWebEngineProfile::defaultProfile()->clientCertificateStore()->clear();
+ QCOMPARE(0,
+ QWebEngineProfile::defaultProfile()->clientCertificateStore()->certificates().size());
+}
+
void tst_QWebEngineClientCertificateStore::addAndListCertificates()
{
// Load QSslCertificate
@@ -77,21 +74,93 @@ void tst_QWebEngineClientCertificateStore::addAndListCertificates()
QWebEngineProfile::defaultProfile()->clientCertificateStore()->add(cert, sslKey);
QWebEngineProfile::defaultProfile()->clientCertificateStore()->add(certSecond, sslKeySecond);
- QCOMPARE(2, QWebEngineProfile::defaultProfile()->clientCertificateStore()->certificates().length());
+ QCOMPARE(2, QWebEngineProfile::defaultProfile()->clientCertificateStore()->certificates().size());
}
void tst_QWebEngineClientCertificateStore::removeAndClearCertificates()
{
- QCOMPARE(2, QWebEngineProfile::defaultProfile()->clientCertificateStore()->certificates().length());
+ addAndListCertificates();
+ QCOMPARE(2, QWebEngineProfile::defaultProfile()->clientCertificateStore()->certificates().size());
// Remove one certificate from in-memory store
auto list = QWebEngineProfile::defaultProfile()->clientCertificateStore()->certificates();
QWebEngineProfile::defaultProfile()->clientCertificateStore()->remove(list[0]);
- QCOMPARE(1, QWebEngineProfile::defaultProfile()->clientCertificateStore()->certificates().length());
+ QCOMPARE(1, QWebEngineProfile::defaultProfile()->clientCertificateStore()->certificates().size());
// Remove all certificates in-memory store
QWebEngineProfile::defaultProfile()->clientCertificateStore()->clear();
- QCOMPARE(0, QWebEngineProfile::defaultProfile()->clientCertificateStore()->certificates().length());
+ QCOMPARE(0, QWebEngineProfile::defaultProfile()->clientCertificateStore()->certificates().size());
+}
+
+void tst_QWebEngineClientCertificateStore::clientAuthentication_data()
+{
+ QTest::addColumn<QString>("client_certificate");
+ QTest::addColumn<QString>("client_key");
+ QTest::addColumn<bool>("in_memory");
+ QTest::addColumn<bool>("add_more_in_memory_certificates");
+ QTest::newRow("in_memory") << ":/resources/client.pem"
+ << ":/resources/client.key" << true << false;
+#if defined(TEST_NSS)
+ QTest::newRow("nss") << ":/resources/client2.pem"
+ << ":/resources/client2.key" << false << false;
+ QTest::newRow("in_memory + nss") << ":/resources/client2.pem"
+ << ":/resources/client2.key" << false << true;
+#endif
+}
+
+void tst_QWebEngineClientCertificateStore::clientAuthentication()
+{
+ QFETCH(QString, client_certificate);
+ QFETCH(QString, client_key);
+ QFETCH(bool, in_memory);
+ QFETCH(bool, add_more_in_memory_certificates);
+
+ HttpsServer server(":/resources/server.pem", ":/resources/server.key", ":resources/ca.pem");
+ server.setExpectError(false);
+ QVERIFY(server.start());
+
+ connect(&server, &HttpsServer::newRequest, [&](HttpReqRep *rr) {
+ rr->setResponseBody(QByteArrayLiteral("<html><body>TEST</body></html>"));
+ rr->sendResponse();
+ });
+
+ QFile certFile(client_certificate);
+ certFile.open(QIODevice::ReadOnly);
+ const QSslCertificate cert(certFile.readAll(), QSsl::Pem);
+
+ QFile keyFile(client_key);
+ keyFile.open(QIODevice::ReadOnly);
+ const QSslKey sslKey(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, "");
+
+ if (in_memory)
+ QWebEngineProfile::defaultProfile()->clientCertificateStore()->add(cert, sslKey);
+
+ if (add_more_in_memory_certificates)
+ addAndListCertificates();
+
+ QWebEnginePage page;
+ connect(&page, &QWebEnginePage::certificateError, [](QWebEngineCertificateError e) {
+ // ca is self signed in this test simply accept the certificate error
+ e.acceptCertificate();
+ });
+ connect(&page, &QWebEnginePage::selectClientCertificate, &page,
+ [&cert](QWebEngineClientCertificateSelection selection) {
+ QVERIFY(!selection.certificates().isEmpty());
+ for (const QSslCertificate &sCert : selection.certificates()) {
+ if (cert == sCert) {
+ selection.select(sCert);
+ return;
+ }
+ }
+ QFAIL("No certificate found.");
+ });
+ QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool)));
+ page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+ page.setUrl(server.url());
+ QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size() > 0, true, 20000);
+ QCOMPARE(loadFinishedSpy.takeFirst().at(0).toBool(), true);
+ QCOMPARE(toPlainTextSync(&page), QStringLiteral("TEST"));
+ QVERIFY(server.stop());
}
QTEST_MAIN(tst_QWebEngineClientCertificateStore)
diff --git a/tests/auto/core/qwebengineclientcertificatestore/tst_qwebengineclientcertificatestore.qrc b/tests/auto/core/qwebengineclientcertificatestore/tst_qwebengineclientcertificatestore.qrc
deleted file mode 100644
index db481fef6..000000000
--- a/tests/auto/core/qwebengineclientcertificatestore/tst_qwebengineclientcertificatestore.qrc
+++ /dev/null
@@ -1,8 +0,0 @@
-<RCC>
- <qresource prefix="/">
- <file>resources/certificate.crt</file>
- <file>resources/privatekey.key</file>
- <file>resources/certificate1.crt</file>
- <file>resources/privatekey1.key</file>
- </qresource>
-</RCC>
diff --git a/tests/auto/core/qwebenginecookiestore/CMakeLists.txt b/tests/auto/core/qwebenginecookiestore/CMakeLists.txt
new file mode 100644
index 000000000..cc14940f1
--- /dev/null
+++ b/tests/auto/core/qwebenginecookiestore/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+include(../../httpserver/httpserver.cmake)
+include(../../util/util.cmake)
+
+qt_internal_add_test(tst_qwebenginecookiestore
+ SOURCES
+ tst_qwebenginecookiestore.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+ Test::HttpServer
+ Test::Util
+)
+
+set(tst_qwebenginecookiestore_resource_files
+ "resources/content.html"
+ "resources/index.html"
+)
+
+qt_internal_add_resource(tst_qwebenginecookiestore "tst_qwebenginecookiestore"
+ PREFIX
+ "/"
+ FILES
+ ${tst_qwebenginecookiestore_resource_files}
+)
diff --git a/tests/auto/core/qwebenginecookiestore/qwebenginecookiestore.pro b/tests/auto/core/qwebenginecookiestore/qwebenginecookiestore.pro
deleted file mode 100644
index 9c239f1a7..000000000
--- a/tests/auto/core/qwebenginecookiestore/qwebenginecookiestore.pro
+++ /dev/null
@@ -1,2 +0,0 @@
-include(../tests.pri)
-include(../../shared/http.pri)
diff --git a/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp b/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp
index 5290d5373..3fff2cd45 100644
--- a/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp
+++ b/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp
@@ -1,41 +1,19 @@
-/****************************************************************************
-**
-** Copyright (C) 2017 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtWebEngine module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "../../widgets/util.h"
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <util.h>
#include <QtTest/QtTest>
-#include <QtWebEngineCore/qwebenginecallback.h>
#include <QtWebEngineCore/qwebenginecookiestore.h>
-#include <QtWebEngineWidgets/qwebenginepage.h>
-#include <QtWebEngineWidgets/qwebengineprofile.h>
+#include <QtWebEngineCore/qwebengineprofile.h>
+#include <QtWebEngineCore/qwebenginepage.h>
#include "httpserver.h"
#include "httpreqrep.h"
+// locally overwrite the default timeout of QTY_(COMPARE|VERIFY)
+#define QWE_TRY_COMPARE(x, y) QTRY_COMPARE_WITH_TIMEOUT(x, y, 30000)
+#define QWE_TRY_VERIFY(x) QTRY_VERIFY_WITH_TIMEOUT(x, 30000)
+
class tst_QWebEngineCookieStore : public QObject
{
Q_OBJECT
@@ -56,6 +34,7 @@ private Q_SLOTS:
// as it checks storage manipulation without navigation
void setAndDeleteCookie();
+ void setInvalidCookie();
void cookieSignals();
void batchCookieTasks();
void basicFilter();
@@ -105,22 +84,22 @@ void tst_QWebEngineCookieStore::cookieSignals()
page.load(QUrl("qrc:///resources/index.html"));
- QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 30000);
+ QWE_TRY_COMPARE(loadSpy.size(), 1);
QVariant success = loadSpy.takeFirst().takeFirst();
QVERIFY(success.toBool());
- QTRY_COMPARE(cookieAddedSpy.count(), 2);
+ QWE_TRY_COMPARE(cookieAddedSpy.size(), 2);
// try whether updating a cookie to be expired results in that cookie being removed.
QNetworkCookie expiredCookie(QNetworkCookie::parseCookies(QByteArrayLiteral("SessionCookie=delete; expires=Thu, 01-Jan-1970 00:00:00 GMT; path=///resources")).first());
client->setCookie(expiredCookie, QUrl("qrc:///resources/index.html"));
- QTRY_COMPARE(cookieRemovedSpy.count(), 1);
+ QWE_TRY_COMPARE(cookieRemovedSpy.size(), 1);
cookieRemovedSpy.clear();
// try removing the other cookie.
QNetworkCookie nonSessionCookie(QNetworkCookie::parseCookies(QByteArrayLiteral("CookieWithExpiresField=QtWebEngineCookieTest; path=///resources")).first());
client->deleteCookie(nonSessionCookie, QUrl("qrc:///resources/index.html"));
- QTRY_COMPARE(cookieRemovedSpy.count(), 1);
+ QWE_TRY_COMPARE(cookieRemovedSpy.size(), 1);
}
void tst_QWebEngineCookieStore::setAndDeleteCookie()
@@ -141,33 +120,64 @@ void tst_QWebEngineCookieStore::setAndDeleteCookie()
client->loadAllCookies();
// /* FIXME remove 'blank' navigation once loadAllCookies api is fixed
page.load(QUrl("about:blank"));
- QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 30000);
+ QWE_TRY_COMPARE(loadSpy.size(), 1);
// */
// check if pending cookies are set and removed
client->setCookie(cookie1);
client->setCookie(cookie2);
- QTRY_COMPARE(cookieAddedSpy.count(), 2);
+ QWE_TRY_COMPARE(cookieAddedSpy.size(), 2);
client->deleteCookie(cookie1);
- QTRY_COMPARE(cookieRemovedSpy.count(), 1);
+ QWE_TRY_COMPARE(cookieRemovedSpy.size(), 1);
page.load(QUrl("qrc:///resources/content.html"));
- QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 2, 30000);
+ QWE_TRY_COMPARE(loadSpy.size(), 2);
QVariant success = loadSpy.takeFirst().takeFirst();
QVERIFY(success.toBool());
- QTRY_COMPARE(cookieAddedSpy.count(), 2);
- QTRY_COMPARE(cookieRemovedSpy.count(), 1);
+ QWE_TRY_COMPARE(cookieAddedSpy.size(), 2);
+ QWE_TRY_COMPARE(cookieRemovedSpy.size(), 1);
cookieAddedSpy.clear();
cookieRemovedSpy.clear();
client->setCookie(cookie3);
- QTRY_COMPARE(cookieAddedSpy.count(), 1);
+ QWE_TRY_COMPARE(cookieAddedSpy.size(), 1);
// updating a cookie with an expired 'expires' field should remove the cookie with the same name
client->setCookie(expiredCookie3);
client->deleteCookie(cookie2);
- QTRY_COMPARE(cookieAddedSpy.count(), 1);
- QTRY_COMPARE(cookieRemovedSpy.count(), 2);
+ QWE_TRY_COMPARE(cookieAddedSpy.size(), 1);
+ QWE_TRY_COMPARE(cookieRemovedSpy.size(), 2);
+}
+
+void tst_QWebEngineCookieStore::setInvalidCookie()
+{
+ QWebEnginePage page(m_profile);
+ QWebEngineCookieStore *client = m_profile->cookieStore();
+
+ QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
+ QSignalSpy cookieAddedSpy(client, SIGNAL(cookieAdded(const QNetworkCookie &)));
+ QSignalSpy cookieRemovedSpy(client, SIGNAL(cookieRemoved(const QNetworkCookie &)));
+
+ QNetworkCookie goodCookie(
+ QNetworkCookie::parseCookies(
+ QByteArrayLiteral("khaos=I9GX8CWI; Domain=.example.com; Path=/docs"))
+ .first());
+ QNetworkCookie badCookie(
+ QNetworkCookie::parseCookies(QByteArrayLiteral("TestCookie=foo\tbar;")).first());
+
+ // force to init storage as it's done lazily upon first navigation
+ client->loadAllCookies();
+ // /* FIXME remove 'blank' navigation once loadAllCookies api is fixed
+ page.load(QUrl("about:blank"));
+ QWE_TRY_COMPARE(loadSpy.size(), 1);
+ // */
+
+ client->setCookie(badCookie);
+ client->setCookie(goodCookie);
+ client->deleteCookie(goodCookie);
+ // by the time the second cookie is removed, only one cookie should have been added
+ QWE_TRY_COMPARE(cookieRemovedSpy.size(), 1);
+ QWE_TRY_COMPARE(cookieAddedSpy.size(), 1);
}
void tst_QWebEngineCookieStore::batchCookieTasks()
@@ -186,29 +196,29 @@ void tst_QWebEngineCookieStore::batchCookieTasks()
client->loadAllCookies();
// /* FIXME remove 'blank' navigation once loadAllCookies api is fixed
page.load(QUrl("about:blank"));
- QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 30000);
+ QWE_TRY_COMPARE(loadSpy.size(), 1);
// */
client->setCookie(cookie1);
client->setCookie(cookie2);
- QTRY_COMPARE(cookieAddedSpy.count(), 2);
+ QWE_TRY_COMPARE(cookieAddedSpy.size(), 2);
page.load(QUrl("qrc:///resources/index.html"));
- QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 2, 30000);
+ QWE_TRY_COMPARE(loadSpy.size(), 2);
QVariant success = loadSpy.takeFirst().takeFirst();
QVERIFY(success.toBool());
- QTRY_COMPARE(cookieAddedSpy.count(), 4);
- QTRY_COMPARE(cookieRemovedSpy.count(), 0);
+ QWE_TRY_COMPARE(cookieAddedSpy.size(), 4);
+ QWE_TRY_COMPARE(cookieRemovedSpy.size(), 0);
cookieAddedSpy.clear();
cookieRemovedSpy.clear();
client->deleteSessionCookies();
- QTRY_COMPARE(cookieRemovedSpy.count(), 3);
+ QWE_TRY_COMPARE(cookieRemovedSpy.size(), 3);
client->deleteAllCookies();
- QTRY_COMPARE(cookieRemovedSpy.count(), 4);
+ QWE_TRY_COMPARE(cookieRemovedSpy.size(), 4);
}
void tst_QWebEngineCookieStore::basicFilter()
@@ -225,22 +235,22 @@ void tst_QWebEngineCookieStore::basicFilter()
page.load(QUrl("qrc:///resources/index.html"));
- QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 30000);
+ QWE_TRY_COMPARE(loadSpy.size(), 1);
QVERIFY(loadSpy.takeFirst().takeFirst().toBool());
- QTRY_COMPARE(cookieAddedSpy.count(), 2);
- QTRY_COMPARE(accessTested.loadAcquire(), 2); // FIXME?
+ QWE_TRY_COMPARE(cookieAddedSpy.size(), 2);
+ QWE_TRY_COMPARE(accessTested.loadAcquire(), 2); // FIXME?
client->deleteAllCookies();
- QTRY_COMPARE(cookieRemovedSpy.count(), 2);
+ QWE_TRY_COMPARE(cookieRemovedSpy.size(), 2);
client->setCookieFilter([&](const QWebEngineCookieStore::FilterRequest &){ ++accessTested; return false; });
page.triggerAction(QWebEnginePage::ReloadAndBypassCache);
- QTRY_COMPARE(loadSpy.count(), 1);
+ QWE_TRY_COMPARE(loadSpy.size(), 1);
QVERIFY(loadSpy.takeFirst().takeFirst().toBool());
- QTRY_COMPARE(accessTested.loadAcquire(), 4); // FIXME?
+ QWE_TRY_COMPARE(accessTested.loadAcquire(), 4); // FIXME?
// Test cookies are NOT added:
QTest::qWait(100);
- QCOMPARE(cookieAddedSpy.count(), 2);
+ QCOMPARE(cookieAddedSpy.size(), 2);
}
void tst_QWebEngineCookieStore::basicFilterOverHTTP()
@@ -249,22 +259,27 @@ void tst_QWebEngineCookieStore::basicFilterOverHTTP()
QWebEngineCookieStore *client = m_profile->cookieStore();
QAtomicInt accessTested = 0;
- client->setCookieFilter([&](const QWebEngineCookieStore::FilterRequest &) { ++accessTested; return true; });
+ QList<QPair<QUrl, QUrl>> resourceFirstParty;
+ client->setCookieFilter([&](const QWebEngineCookieStore::FilterRequest &request) {
+ resourceFirstParty.append(qMakePair(request.origin, request.firstPartyUrl));
+ ++accessTested;
+ return true;
+ });
HttpServer httpServer;
-
- if (!httpServer.start())
- QSKIP("Failed to start http server");
+ httpServer.setHostDomain(QString("sub.test.localhost"));
+ QVERIFY(httpServer.start());
QByteArray cookieRequestHeader;
connect(&httpServer, &HttpServer::newRequest, [&cookieRequestHeader](HttpReqRep *rr) {
- if (rr->requestPath().size() <= 1) {
+ if (rr->requestMethod() == "GET" && rr->requestPath() == "/test.html") {
cookieRequestHeader = rr->requestHeader(QByteArrayLiteral("Cookie"));
if (cookieRequestHeader.isEmpty())
rr->setResponseHeader(QByteArrayLiteral("Set-Cookie"), QByteArrayLiteral("Test=test"));
+ rr->setResponseBody("<head><link rel='icon' type='image/png' href='resources/Fav.png'/>"
+ "<title>Page with a favicon and an icon</title></head>"
+ "<body><img src='resources/Img.ico'></body>");
rr->sendResponse();
- } else {
- rr->sendResponse(404);
}
});
@@ -273,43 +288,59 @@ void tst_QWebEngineCookieStore::basicFilterOverHTTP()
QSignalSpy cookieRemovedSpy(client, SIGNAL(cookieRemoved(const QNetworkCookie &)));
QSignalSpy serverSpy(&httpServer, SIGNAL(newRequest(HttpReqRep *)));
- page.load(httpServer.url());
+ QUrl firstPartyUrl = httpServer.url("/test.html");
+ page.load(firstPartyUrl);
- QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 30000);
+ QWE_TRY_COMPARE(loadSpy.size(), 1);
QVERIFY(loadSpy.takeFirst().takeFirst().toBool());
- QTRY_COMPARE(cookieAddedSpy.count(), 1);
- QTRY_COMPARE(accessTested.loadAcquire(), 3);
+ QWE_TRY_COMPARE(cookieAddedSpy.size(), 1);
+ QWE_TRY_COMPARE(accessTested.loadAcquire(), 4);
QVERIFY(cookieRequestHeader.isEmpty());
+ QWE_TRY_COMPARE(serverSpy.size(), 3);
+
page.triggerAction(QWebEnginePage::Reload);
- QTRY_COMPARE(loadSpy.count(), 1);
+ QWE_TRY_COMPARE(loadSpy.size(), 1);
QVERIFY(loadSpy.takeFirst().takeFirst().toBool());
QVERIFY(!cookieRequestHeader.isEmpty());
- QTRY_COMPARE(cookieAddedSpy.count(), 1);
- QTRY_COMPARE(accessTested.loadAcquire(), 5);
+ QWE_TRY_COMPARE(cookieAddedSpy.size(), 1);
+ QWE_TRY_COMPARE(accessTested.loadAcquire(), 6);
+
+ QWE_TRY_COMPARE(serverSpy.size(), 5);
client->deleteAllCookies();
- QTRY_COMPARE(cookieRemovedSpy.count(), 1);
+ QWE_TRY_COMPARE(cookieRemovedSpy.size(), 1);
- client->setCookieFilter([&](const QWebEngineCookieStore::FilterRequest &) { ++accessTested; return false; });
+ client->setCookieFilter([&](const QWebEngineCookieStore::FilterRequest &request) {
+ resourceFirstParty.append(qMakePair(request.origin, request.firstPartyUrl));
+ ++accessTested;
+ return false;
+ });
page.triggerAction(QWebEnginePage::ReloadAndBypassCache);
- QTRY_COMPARE(loadSpy.count(), 1);
+ QWE_TRY_COMPARE(loadSpy.size(), 1);
QVERIFY(loadSpy.takeFirst().takeFirst().toBool());
QVERIFY(cookieRequestHeader.isEmpty());
// Test cookies are NOT added:
QTest::qWait(100);
- QCOMPARE(cookieAddedSpy.count(), 1);
- QTRY_COMPARE(accessTested.loadAcquire(), 8);
+ QCOMPARE(cookieAddedSpy.size(), 1);
+ QWE_TRY_COMPARE(accessTested.loadAcquire(), 9);
+
+ QWE_TRY_COMPARE(serverSpy.size(), 7);
page.triggerAction(QWebEnginePage::Reload);
- QTRY_COMPARE(loadSpy.count(), 1);
+ QWE_TRY_COMPARE(loadSpy.size(), 1);
QVERIFY(loadSpy.takeFirst().takeFirst().toBool());
QVERIFY(cookieRequestHeader.isEmpty());
- QCOMPARE(cookieAddedSpy.count(), 1);
+ QCOMPARE(cookieAddedSpy.size(), 1);
// Wait for last GET /favicon.ico
- QTRY_COMPARE(serverSpy.count(), 8);
+ QWE_TRY_COMPARE(serverSpy.size(), 9);
(void) httpServer.stop();
+
+ QCOMPARE(resourceFirstParty.size(), accessTested.loadAcquire());
+ for (auto &&p : std::as_const(resourceFirstParty))
+ QVERIFY2(p.second == firstPartyUrl,
+ qPrintable(QString("Resource [%1] has wrong firstPartyUrl: %2").arg(p.first.toString(), p.second.toString())));
}
void tst_QWebEngineCookieStore::html5featureFilter()
@@ -324,17 +355,17 @@ void tst_QWebEngineCookieStore::html5featureFilter()
page.load(QUrl("qrc:///resources/content.html"));
- QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 30000);
+ QWE_TRY_COMPARE(loadSpy.size(), 1);
QVERIFY(loadSpy.takeFirst().takeFirst().toBool());
QCOMPARE(accessTested.loadAcquire(), 0); // FIXME?
QTest::ignoreMessage(QtCriticalMsg, QRegularExpression(".*Uncaught SecurityError.*sessionStorage.*"));
page.runJavaScript("sessionStorage.test = 5;");
- QTRY_COMPARE(accessTested.loadAcquire(), 1);
+ QWE_TRY_COMPARE(accessTested.loadAcquire(), 1);
QTest::ignoreMessage(QtCriticalMsg, QRegularExpression(".*Uncaught SecurityError.*sessionStorage.*"));
QAtomicInt callbackTriggered = 0;
page.runJavaScript("sessionStorage.test", [&](const QVariant &v) { QVERIFY(!v.isValid()); callbackTriggered = 1; });
- QTRY_VERIFY(callbackTriggered);
+ QWE_TRY_VERIFY(callbackTriggered);
}
QTEST_MAIN(tst_QWebEngineCookieStore)
diff --git a/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.qrc b/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.qrc
deleted file mode 100644
index afeae268b..000000000
--- a/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.qrc
+++ /dev/null
@@ -1,6 +0,0 @@
-<!DOCTYPE RCC><RCC version="1.0">
-<qresource>
- <file>resources/index.html</file>
- <file>resources/content.html</file>
-</qresource>
-</RCC>
diff --git a/tests/auto/core/qwebengineglobalsettings/CMakeLists.txt b/tests/auto/core/qwebengineglobalsettings/CMakeLists.txt
new file mode 100644
index 000000000..dd2f28857
--- /dev/null
+++ b/tests/auto/core/qwebengineglobalsettings/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+include(../../httpserver/httpserver.cmake)
+include(../../util/util.cmake)
+
+qt_internal_add_test(tst_qwebengineglobalsettings
+ SOURCES
+ tst_qwebengineglobalsettings.cpp
+ LIBRARIES
+ Qt::Network
+ Qt::WebEngineCore
+ Test::HttpServer
+ Qt::WebEngineWidgets
+ Test::Util
+)
+
+# Resources:
+set(tst_qwebengineglobalsettings_resource_files
+ "cert/localhost.crt"
+ "cert/localhost.key"
+ "cert/RootCA.pem"
+)
+
+qt_add_resources(tst_qwebengineglobalsettings "tst_qwebengineglobalsettings"
+ PREFIX
+ "/"
+ FILES
+ ${tst_qwebengineglobalsettings_resource_files}
+)
diff --git a/tests/auto/core/qwebengineglobalsettings/cert/RootCA.pem b/tests/auto/core/qwebengineglobalsettings/cert/RootCA.pem
new file mode 100644
index 000000000..16be384bb
--- /dev/null
+++ b/tests/auto/core/qwebengineglobalsettings/cert/RootCA.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDTzCCAjegAwIBAgIUNYpmREIW67Fh7WNzCwPL6nnSgRMwDQYJKoZIhvcNAQEL
+BQAwNzELMAkGA1UEBhMCREUxKDAmBgNVBAMMH1dlYkVuZ2luZUdsb2JhbFNldHRp
+bmdzLVRlc3QtQ0EwHhcNMjMwMzI5MTYwMzMzWhcNMjYwMTE2MTYwMzMzWjA3MQsw
+CQYDVQQGEwJERTEoMCYGA1UEAwwfV2ViRW5naW5lR2xvYmFsU2V0dGluZ3MtVGVz
+dC1DQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJhZ9DwcdbVBzMyY
+/nEqt5KUi74LFwEnS0G2ne8IYco9+Pbkpb8wV5u6n43IsQ2c3u8D/KVtu1Vy3tf2
+G3aKOwhFzaj7GWLE9FweZyMoL6ASOtWEa55myT5zAysVQtHAkePu0smAPP0gVq3E
+vjSTwV1W1mVXv4wMwffR8AvNGhKrJIa3L2/uYKGbzEmaCk2kt0vIqfrx8095RlXC
+lUcwTMJ6/d/e/DMDtqQ1ypUuz5QYQybIVKwuqkhojT2DXbitv0rE4HLQub8CxOZ+
+9GcQjeAt8Tzrlp1UP6c9OtlsMoo37gJYzb/XDE6OPnk42chQXDxGQjtVRs+60kcT
+Dx/YHG0CAwEAAaNTMFEwHQYDVR0OBBYEFP1FK1U9CUHQEp7coaab7IdR18zDMB8G
+A1UdIwQYMBaAFP1FK1U9CUHQEp7coaab7IdR18zDMA8GA1UdEwEB/wQFMAMBAf8w
+DQYJKoZIhvcNAQELBQADggEBAGTlcxmRsuwBeRW0CsjX/qbdcB0OWkIC857Jn8RU
+6yGa7P9i6EQb1O/DEF+Z7dkASx5zfN6LPIrph6J56/mmcNBeqArovWJwxQUTNO9i
+1kOU3xoH5n/ya+gdBr3reA90bAMKWXwa6uI3smPJKy+2hOkdDaSBa5KECYWhniH0
+yRxL7YdhQhuCc7Ijf+S6WzeHRwdLkdiV8c2vAGWdunDFuGT2iYVOZ1qbp6O/tmjv
+TxWAnvP4+0ykFIlMor0vYWD8xbnq28263VmNh7mrFwkBYnHJiY/nTDwxaL+g6Uji
+9n6+VuxUDgfQWX1YRHC4a89zI/Zvnn2z/92To7zRmNd73RQ=
+-----END CERTIFICATE-----
diff --git a/tests/auto/core/qwebengineglobalsettings/cert/localhost.crt b/tests/auto/core/qwebengineglobalsettings/cert/localhost.crt
new file mode 100644
index 000000000..c063bf268
--- /dev/null
+++ b/tests/auto/core/qwebengineglobalsettings/cert/localhost.crt
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDtDCCApygAwIBAgIURotPFTfDJxwaqhZsr0IpAahl2EMwDQYJKoZIhvcNAQEL
+BQAwNzELMAkGA1UEBhMCREUxKDAmBgNVBAMMH1dlYkVuZ2luZUdsb2JhbFNldHRp
+bmdzLVRlc3QtQ0EwHhcNMjMwMzI5MTYwNDI1WhcNMjYwMTE2MTYwNDI1WjB0MQsw
+CQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xKTAn
+BgNVBAoMIFFXZWJFbmdpbmVHbG9iYWxTZXR0aW5ncy1FeGFtcGxlMRgwFgYDVQQD
+DA9sb2NhbGhvc3QubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCY9FX/8YetuJKSFSoYPxMKPvjt2zJ5g13DywqZtbDzLAIASCxn4iad3qgxaoWB
+uI0g4ykzrhUa98YHU8fDH4T4Vwhwu72SRYW4+MgT9ohc1oCKBX05b+BWSuTSeuHy
+leqdL78bj3bu5TtFs2dJt2t8eA6SNR9lDa5g4v7oA4xVp93gMo2YqZwaLONmxIKY
+cI4lcETnHHsvc6+dB2UqWHJEN75UkdC/XnDLM/VbL3/4zxU+9x34nvvfSJwCHVnE
+u+zYwrZXkbiDVDovT855phVC/K5skVgBL2miz3eygljuw1tIwnmVix/e/xHZyqMg
+Lje/LZN53v5G61Wut6bbdkeVAgMBAAGjezB5MB8GA1UdIwQYMBaAFP1FK1U9CUHQ
+Ep7coaab7IdR18zDMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMB8GA1UdEQQYMBaC
+CWxvY2FsaG9zdIIJMTI3LjAuMC4xMB0GA1UdDgQWBBQv20ImViFtvMm5gHqTeML6
+pi5BtTANBgkqhkiG9w0BAQsFAAOCAQEAGsL7eOms30+IPdKQZ2pjtX22GfM6EiUs
+xsQfsX/Q3bus30B2m9GJ6AVIwVUJimOGiMauDCLjDeXWCMZpihzodExhC0D/X1B+
+FsCLagcjlgfWwekKEo8sUWUZp0DNCyacPtTPxqoS2RA7foEzQhRLViLSvf+UXU8g
+jZAwWGB/5V849zcbbNBcWKzRsPvNOqeENWEn1ByGcsWhas2V0KzRcUODuA9UHv1x
++eDlLZYsWV9c1MuL8a1VDEluIR19eR/Gl9axjPZY2oiPvlp2b7I4z1bY+wV2i6vh
+NeDCAxAxJ42tXeb86vtnVXfSDbzedDbLv0l2kEhcywVN3MwhsEpmpg==
+-----END CERTIFICATE-----
diff --git a/tests/auto/core/qwebengineglobalsettings/cert/localhost.key b/tests/auto/core/qwebengineglobalsettings/cert/localhost.key
new file mode 100644
index 000000000..49502ab9a
--- /dev/null
+++ b/tests/auto/core/qwebengineglobalsettings/cert/localhost.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCY9FX/8YetuJKS
+FSoYPxMKPvjt2zJ5g13DywqZtbDzLAIASCxn4iad3qgxaoWBuI0g4ykzrhUa98YH
+U8fDH4T4Vwhwu72SRYW4+MgT9ohc1oCKBX05b+BWSuTSeuHyleqdL78bj3bu5TtF
+s2dJt2t8eA6SNR9lDa5g4v7oA4xVp93gMo2YqZwaLONmxIKYcI4lcETnHHsvc6+d
+B2UqWHJEN75UkdC/XnDLM/VbL3/4zxU+9x34nvvfSJwCHVnEu+zYwrZXkbiDVDov
+T855phVC/K5skVgBL2miz3eygljuw1tIwnmVix/e/xHZyqMgLje/LZN53v5G61Wu
+t6bbdkeVAgMBAAECggEAF7ADX5NivUsv29LORZoDE1ukRoXjX8Ex9MANoLdsM4S1
+vKBwzBfQfjN83cZO7cOMi7LSby//EcGcmAboEXZgq+siof7ZQX1l07snlTvha2tG
+1dk6xvnmBscrf9NLCbwg7P33fUevFhlHICjEDr0KtuiK7Sav+YDwaA3Ph1QBWERd
+GO3sVlnuGsDpf/0GijlwqEGuDKUePEEANOhXcByh693VmvlKVf9SbrimYeunKW1O
+FvvcAiBMzqurhZotb9/juiq+fIMID29OULCCxlZZSySRYw0REpnAAxgWvaSZqyWd
+tGosSKEgc4SptPTmC8DzRDDfN/zqvXmkmYnN9o4qMwKBgQC3tVYdEPn3fHX973Df
+Ukp55cRpZFuNxjOiV3Qo1aTAKqpbmJ4/x8tUL7rhsmJXSxlW7xdNQ/WIHM1PJlZx
+UAIr5eBq1MUVd5OENP8PuVIdAIumHXICB5FioJR/WnXRXLJEbGxSRr5gwaTw3sXd
+ObPRQEUOrJtK+W0aeBKePRtIiwKBgQDVJN7A26vy8PMcE4TcDp75vAY/qasZl6ES
+oksaHf3c4/jsnr70wRoOXi3fPo/DpWmVFylttMSEnzh3nfnOkDXZD3mQx3sdVw8l
+12++t59733hllJZlwqk5OAc990kE1X44UW/gPA+5Vb9kpo6ahpFtqwhDmqa4RjtZ
+0R/1H/KUXwKBgGjR7Qq0rwwJVgHIZ2zlNV2MPp+sBZlFaBzPLZZHILQNJBsTX+gg
+heHJQiaZdAc+8Hxr+624gxZg6LyqsVQCRNrrVTtfn/x5uBANdSNxqGqn7wafcne5
+/bh6y4BHC0akT4s/Gidv+hyXIRfW5Ksvy2wv8bdHwWvsGdaqgGUNlM21AoGAe9Vc
+BbibAh6zYBCHFEL6YiW3i61L1yadUnIwKBBcucVJjk/8qb63ILne9OEoLYcg/Jnk
+W/S2aEcJS5Xg2P44CtBO1KrRAI7gIiA0sB2G7zU6gen+J0kdgDzpGDtflQtktdu6
+oBDFIeyLsjKCj4y3WXwQ5RYo3s8PFHPHmWbiTQkCgYBViqAHaAaPJQZG7Q6/Xqhf
+rFosC0DeZk5PHrEDbpAnuJbySafGZ4LxY5Oq05+aM8BeR+IuGopciBBDWXoh7msO
+N8WItu7WI86lIu7JZRbeju2w59tgj0EA+GyU1udPjTjseP5qpB27X1JEGV6TYBNs
+LMmQSgdGWqwteKsc50UrkA==
+-----END PRIVATE KEY-----
diff --git a/tests/auto/core/qwebengineglobalsettings/tst_qwebengineglobalsettings.cpp b/tests/auto/core/qwebengineglobalsettings/tst_qwebengineglobalsettings.cpp
new file mode 100644
index 000000000..5b6b64778
--- /dev/null
+++ b/tests/auto/core/qwebengineglobalsettings/tst_qwebengineglobalsettings.cpp
@@ -0,0 +1,130 @@
+// 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>
+#include <widgetutil.h>
+#include <QNetworkAccessManager>
+#include <QNetworkRequest>
+#include <QWebEngineProfile>
+#include <QWebEnginePage>
+#include <QWebEngineGlobalSettings>
+#include <QWebEngineLoadingInfo>
+
+#include "httpsserver.h"
+#include "httpreqrep.h"
+
+class tst_QWebEngineGlobalSettings : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QWebEngineGlobalSettings() { }
+ ~tst_QWebEngineGlobalSettings() { }
+
+public Q_SLOTS:
+ void init() { }
+ void cleanup() { }
+
+private Q_SLOTS:
+ void initTestCase() { }
+ void cleanupTestCase() { }
+ void dnsOverHttps_data();
+ void dnsOverHttps();
+};
+
+Q_LOGGING_CATEGORY(lc, "qt.webengine.tests")
+
+void tst_QWebEngineGlobalSettings::dnsOverHttps_data()
+{
+ QTest::addColumn<QWebEngineGlobalSettings::SecureDnsMode>("dnsMode");
+ QTest::addColumn<QString>("uriTemplate");
+ QTest::addColumn<bool>("isMockDnsServerCalledExpected");
+ QTest::addColumn<bool>("isDnsResolutionSuccessExpected");
+ QTest::addColumn<bool>("isConfigurationSuccessExpected");
+ QTest::newRow("DnsMode::SystemOnly (no DoH server)")
+ << QWebEngineGlobalSettings::SecureDnsMode::SystemOnly << QStringLiteral("") << false
+ << true << true;
+ QTest::newRow("DnsMode::SecureOnly (mock DoH server)")
+ << QWebEngineGlobalSettings::SecureDnsMode::SecureOnly
+ << QStringLiteral("https://127.0.0.1:3000/dns-query{?dns}") << true << false << true;
+ QTest::newRow("DnsMode::SecureOnly (real DoH server)")
+ << QWebEngineGlobalSettings::SecureDnsMode::SecureOnly
+ << QStringLiteral("https://dns.google/dns-query{?dns}") << false << true << true;
+ QTest::newRow("DnsMode::SecureOnly (Empty URI Templates)")
+ << QWebEngineGlobalSettings::SecureDnsMode::SecureOnly << QStringLiteral("") << false
+ << false << false;
+ // Note: In the following test, we can't verify that the DoH server is called first and
+ // afterwards insecure DNS is tried, because for the DoH server to ever be used when the DNS
+ // mode is set to DnsMode::WithFallback, Chromium starts an asynchronous DoH server DnsProbe and
+ // requires that the connection is successful. That is, we'd have to implement a correct
+ // DNS response, which in turn requires that certificate errors aren't ignored and
+ // non-self-signed certificates are used for correct encryption. Instead of implementing
+ // all of that, this test verifies that Chromium tries probing the configured DoH server only.
+ QTest::newRow("DnsMode::SecureWithFallback (mock DoH server)")
+ << QWebEngineGlobalSettings::SecureDnsMode::SecureWithFallback
+ << QStringLiteral("https://127.0.0.1:3000/dns-query{?dns}") << true << true << true;
+ QTest::newRow("DnsMode::SecureWithFallback (Empty URI Templates)")
+ << QWebEngineGlobalSettings::SecureDnsMode::SecureWithFallback << QStringLiteral("")
+ << false << false << false;
+}
+
+void tst_QWebEngineGlobalSettings::dnsOverHttps()
+{
+ const QUrl url = QStringLiteral("https://google.com/");
+ // Verify network access with NAM because the result of loadFinished signal
+ // is used to verify that the DNS resolution was successful.
+ QNetworkAccessManager nam;
+ QSignalSpy namSpy(&nam, &QNetworkAccessManager::finished);
+ QScopedPointer<QNetworkReply> reply(nam.get(QNetworkRequest(url)));
+ if (!namSpy.wait(20000) || reply->error() != QNetworkReply::NoError)
+ QSKIP("Couldn't load page from network, skipping test.");
+
+ QFETCH(QWebEngineGlobalSettings::SecureDnsMode, dnsMode);
+ QFETCH(QString, uriTemplate);
+ QFETCH(bool, isMockDnsServerCalledExpected);
+ QFETCH(bool, isDnsResolutionSuccessExpected);
+ QFETCH(bool, isConfigurationSuccessExpected);
+ bool isMockDnsServerCalled = false;
+ bool isLoadSuccessful = false;
+
+ bool configurationSuccess =
+ QWebEngineGlobalSettings::setDnsMode({ dnsMode, QStringList{ uriTemplate } });
+ QCOMPARE(configurationSuccess, isConfigurationSuccessExpected);
+
+ if (!configurationSuccess) {
+ // In this case, DNS has invalid configuration, so the DNS change transaction is not
+ // triggered and the result of the DNS resolution depends on the current DNS mode, which is
+ // set by the previous run of this function.
+ return;
+ }
+ HttpsServer httpsServer(":/cert/localhost.crt", ":/cert/localhost.key", ":/cert/RootCA.pem",
+ 3000, this);
+ QObject::connect(&httpsServer, &HttpsServer::newRequest, this,
+ [&isMockDnsServerCalled](HttpReqRep *rr) {
+ QVERIFY(rr->requestPath().contains(QByteArrayLiteral("/dns-query?dns=")));
+ isMockDnsServerCalled = true;
+ rr->close();
+ });
+ QVERIFY(httpsServer.start());
+ httpsServer.setExpectError(isMockDnsServerCalledExpected);
+ httpsServer.setVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone);
+
+ QWebEngineProfile profile;
+ QWebEnginePage page(&profile);
+ QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
+
+ connect(&page, &QWebEnginePage::loadFinished, this,
+ [&isLoadSuccessful](bool ok) { isLoadSuccessful = ok; });
+
+ page.load(url);
+ QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000);
+
+ QTRY_COMPARE(isMockDnsServerCalled, isMockDnsServerCalledExpected);
+ QCOMPARE(isLoadSuccessful, isDnsResolutionSuccessExpected);
+ QVERIFY(httpsServer.stop());
+}
+
+static QByteArrayList params = QByteArrayList() << "--ignore-certificate-errors";
+
+W_QTEST_MAIN(tst_QWebEngineGlobalSettings, params)
+#include "tst_qwebengineglobalsettings.moc"
diff --git a/tests/auto/core/qwebengineloadinginfo/CMakeLists.txt b/tests/auto/core/qwebengineloadinginfo/CMakeLists.txt
new file mode 100644
index 000000000..09d9c30f5
--- /dev/null
+++ b/tests/auto/core/qwebengineloadinginfo/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_internal_add_test(tst_qwebengineloadinginfo
+ SOURCES
+ tst_qwebengineloadinginfo.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+ Test::HttpServer
+)
diff --git a/tests/auto/core/qwebengineloadinginfo/tst_qwebengineloadinginfo.cpp b/tests/auto/core/qwebengineloadinginfo/tst_qwebengineloadinginfo.cpp
new file mode 100644
index 000000000..ccae7436b
--- /dev/null
+++ b/tests/auto/core/qwebengineloadinginfo/tst_qwebengineloadinginfo.cpp
@@ -0,0 +1,93 @@
+// 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/qwebengineprofile.h>
+#include <QtWebEngineCore/qwebenginepage.h>
+#include <QtWebEngineCore/qwebengineloadinginfo.h>
+#include <QtWebEngineCore/qwebenginehttprequest.h>
+#include <QtWebEngineCore/qwebenginesettings.h>
+
+#include <httpserver.h>
+#include <httpreqrep.h>
+
+typedef QMultiMap<QByteArray, QByteArray> Map;
+
+class tst_QWebEngineLoadingInfo : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QWebEngineLoadingInfo() { }
+
+public slots:
+ void loadingInfoChanged(QWebEngineLoadingInfo loadingInfo)
+ {
+ const auto responseHeaders = loadingInfo.responseHeaders();
+ QFETCH(Map, expected);
+
+ if (loadingInfo.status() == QWebEngineLoadingInfo::LoadSucceededStatus
+ || loadingInfo.status() == QWebEngineLoadingInfo::LoadFailedStatus) {
+ if (!expected.empty())
+ QCOMPARE(responseHeaders, expected);
+ } else {
+ QVERIFY(responseHeaders.size() == 0);
+ }
+ }
+
+private Q_SLOTS:
+ void responseHeaders_data()
+ {
+ QTest::addColumn<int>("responseCode");
+ QTest::addColumn<Map>("input");
+ QTest::addColumn<Map>("expected");
+
+ const Map empty;
+ const Map input {
+ std::make_pair("header1", "value1"),
+ std::make_pair("header2", "value2")
+ };
+ const Map expected {
+ std::make_pair("header1", "value1"),
+ std::make_pair("header2", "value2"),
+ std::make_pair("Connection", "close")
+ };
+
+
+ QTest::newRow("with headers HTTP 200") << 200 << input << expected;
+ QTest::newRow("with headers HTTP 500") << 500 << input << expected;
+ QTest::newRow("without headers HTTP 200") << 200 << empty << empty;
+ QTest::newRow("without headers HTTP 500") << 500 << empty << empty;
+ }
+
+ void responseHeaders()
+ {
+ HttpServer httpServer;
+
+ QFETCH(Map, input);
+ QFETCH(int, responseCode);
+ QObject::connect(&httpServer, &HttpServer::newRequest, this, [&](HttpReqRep *rr) {
+ for (auto it = input.cbegin(); it != input.cend(); ++it)
+ rr->setResponseHeader(it.key(), it.value());
+
+ rr->sendResponse(responseCode);
+ });
+ QVERIFY(httpServer.start());
+
+ QWebEngineProfile profile;
+ QWebEnginePage page(&profile);
+ page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+ QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
+ QObject::connect(&page, &QWebEnginePage::loadingChanged, this, &tst_QWebEngineLoadingInfo::loadingInfoChanged);
+
+
+ QWebEngineHttpRequest request(httpServer.url("/somepage.html"));
+ page.load(request);
+
+ QTRY_VERIFY(spy.count() > 0);
+ QVERIFY(httpServer.stop());
+ }
+};
+
+QTEST_MAIN(tst_QWebEngineLoadingInfo)
+#include "tst_qwebengineloadinginfo.moc"
diff --git a/tests/auto/core/qwebenginesettings/BLACKLIST b/tests/auto/core/qwebenginesettings/BLACKLIST
new file mode 100644
index 000000000..4b99b8e84
--- /dev/null
+++ b/tests/auto/core/qwebenginesettings/BLACKLIST
@@ -0,0 +1,5 @@
+[javascriptClipboard]
+ubuntu-20.04
+
+[setInAcceptNavigationRequest]
+macos
diff --git a/tests/auto/core/qwebenginesettings/CMakeLists.txt b/tests/auto/core/qwebenginesettings/CMakeLists.txt
new file mode 100644
index 000000000..756b99bbb
--- /dev/null
+++ b/tests/auto/core/qwebenginesettings/CMakeLists.txt
@@ -0,0 +1,13 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+include(../../util/util.cmake)
+
+qt_internal_add_test(tst_qwebenginesettings
+ SOURCES
+ tst_qwebenginesettings.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+ Qt::WebEngineWidgets
+ Test::Util
+)
diff --git a/tests/auto/core/qwebenginesettings/tst_qwebenginesettings.cpp b/tests/auto/core/qwebenginesettings/tst_qwebenginesettings.cpp
new file mode 100644
index 000000000..e856dd094
--- /dev/null
+++ b/tests/auto/core/qwebenginesettings/tst_qwebenginesettings.cpp
@@ -0,0 +1,307 @@
+/*
+ Copyright (C) 2015 The Qt Company Ltd.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <util.h>
+
+#include <QtTest/QtTest>
+
+#include <qwebenginepage.h>
+#include <qwebengineprofile.h>
+#include <qwebenginesettings.h>
+
+#include <QtGui/qclipboard.h>
+#include <QtGui/qguiapplication.h>
+#include <QtWebEngineWidgets/qwebengineview.h>
+
+class tst_QWebEngineSettings: public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+ void resetAttributes();
+ void defaultFontFamily_data();
+ void defaultFontFamily();
+ void javascriptClipboard_data();
+ void javascriptClipboard();
+ void setInAcceptNavigationRequest();
+ void disableReadingFromCanvas_data();
+ void disableReadingFromCanvas();
+ void forceDarkMode();
+ void forceDarkModeMultiView();
+};
+
+void tst_QWebEngineSettings::resetAttributes()
+{
+ QWebEngineProfile profile;
+ QWebEngineSettings *settings = profile.settings();
+
+ // Attribute
+ bool defaultValue = settings->testAttribute(QWebEngineSettings::FullScreenSupportEnabled);
+ settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, !defaultValue);
+ QCOMPARE(!defaultValue, settings->testAttribute(QWebEngineSettings::FullScreenSupportEnabled));
+ settings->resetAttribute(QWebEngineSettings::FullScreenSupportEnabled);
+ QCOMPARE(defaultValue, settings->testAttribute(QWebEngineSettings::FullScreenSupportEnabled));
+
+ // Font family
+ QString defaultFamily = settings->fontFamily(QWebEngineSettings::StandardFont);
+ QString newFontFamily("PugDog");
+ settings->setFontFamily(QWebEngineSettings::StandardFont, newFontFamily);
+ QCOMPARE(newFontFamily, settings->fontFamily(QWebEngineSettings::StandardFont));
+ settings->resetFontFamily(QWebEngineSettings::StandardFont);
+ QCOMPARE(defaultFamily, settings->fontFamily(QWebEngineSettings::StandardFont));
+
+ // Font size
+ int defaultSize = settings->fontSize(QWebEngineSettings::MinimumFontSize);
+ int newSize = defaultSize + 10;
+ settings->setFontSize(QWebEngineSettings::MinimumFontSize, newSize);
+ QCOMPARE(newSize, settings->fontSize(QWebEngineSettings::MinimumFontSize));
+ settings->resetFontSize(QWebEngineSettings::MinimumFontSize);
+ QCOMPARE(defaultSize, settings->fontSize(QWebEngineSettings::MinimumFontSize));
+}
+
+void tst_QWebEngineSettings::defaultFontFamily_data()
+{
+ QTest::addColumn<int>("fontFamily");
+
+ QTest::newRow("StandardFont") << static_cast<int>(QWebEngineSettings::StandardFont);
+ QTest::newRow("FixedFont") << static_cast<int>(QWebEngineSettings::FixedFont);
+ QTest::newRow("SerifFont") << static_cast<int>(QWebEngineSettings::SerifFont);
+ QTest::newRow("SansSerifFont") << static_cast<int>(QWebEngineSettings::SansSerifFont);
+ QTest::newRow("CursiveFont") << static_cast<int>(QWebEngineSettings::CursiveFont);
+ QTest::newRow("FantasyFont") << static_cast<int>(QWebEngineSettings::FantasyFont);
+}
+
+void tst_QWebEngineSettings::defaultFontFamily()
+{
+ QWebEngineProfile profile;
+ QWebEngineSettings *settings = profile.settings();
+
+ QFETCH(int, fontFamily);
+ QVERIFY(!settings->fontFamily(static_cast<QWebEngineSettings::FontFamily>(fontFamily)).isEmpty());
+}
+
+void tst_QWebEngineSettings::javascriptClipboard_data()
+{
+ QTest::addColumn<bool>("javascriptCanAccessClipboard");
+ QTest::addColumn<bool>("javascriptCanPaste");
+ QTest::addColumn<bool>("copyResult");
+ QTest::addColumn<bool>("pasteResult");
+
+ QTest::newRow("default") << false << false << false << false;
+ QTest::newRow("canCopy") << true << false << true << false;
+ // paste command requires both permissions
+ QTest::newRow("canPaste") << false << true << false << false;
+ QTest::newRow("canCopyAndPaste") << true << true << true << true;
+}
+
+void tst_QWebEngineSettings::javascriptClipboard()
+{
+ QFETCH(bool, javascriptCanAccessClipboard);
+ QFETCH(bool, javascriptCanPaste);
+ QFETCH(bool, copyResult);
+ QFETCH(bool, pasteResult);
+
+ QWebEnginePage page;
+
+ // check defaults
+ QCOMPARE(page.settings()->testAttribute(QWebEngineSettings::JavascriptCanAccessClipboard),
+ false);
+ QCOMPARE(page.settings()->testAttribute(QWebEngineSettings::JavascriptCanPaste), false);
+
+ // check accessors
+ page.settings()->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard,
+ javascriptCanAccessClipboard);
+ page.settings()->setAttribute(QWebEngineSettings::JavascriptCanPaste,
+ javascriptCanPaste);
+ QCOMPARE(page.settings()->testAttribute(QWebEngineSettings::JavascriptCanAccessClipboard),
+ javascriptCanAccessClipboard);
+ QCOMPARE(page.settings()->testAttribute(QWebEngineSettings::JavascriptCanPaste),
+ javascriptCanPaste);
+
+ QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool)));
+ page.setHtml("<html><body>"
+ "<input type='text' value='OriginalText' id='myInput'/>"
+ "</body></html>");
+ QVERIFY(loadFinishedSpy.wait());
+
+ // make sure that 'OriginalText' is selected
+ evaluateJavaScriptSync(&page, "document.getElementById('myInput').select()");
+ QCOMPARE(evaluateJavaScriptSync(&page, "window.getSelection().toString()").toString(),
+ QStringLiteral("OriginalText"));
+
+ // Check that the actual settings work by the
+ // - return value of queryCommandEnabled and
+ // - return value of execCommand
+ // - comparing the clipboard / input field
+ QGuiApplication::clipboard()->setText(QString());
+ QCOMPARE(evaluateJavaScriptSync(&page, "document.queryCommandEnabled('copy')").toBool(),
+ copyResult);
+ QCOMPARE(evaluateJavaScriptSync(&page, "document.execCommand('copy')").toBool(), copyResult);
+ QTRY_COMPARE(QGuiApplication::clipboard()->text(),
+ (copyResult ? QString("OriginalText") : QString()));
+
+
+ QGuiApplication::clipboard()->setText("AnotherText");
+ QCOMPARE(evaluateJavaScriptSync(&page, "document.queryCommandEnabled('paste')").toBool(),
+ pasteResult);
+ QCOMPARE(evaluateJavaScriptSync(&page, "document.execCommand('paste')").toBool(), pasteResult);
+ QCOMPARE(evaluateJavaScriptSync(&page, "document.getElementById('myInput').value").toString(),
+ (pasteResult ? QString("AnotherText") : QString("OriginalText")));
+}
+
+class NavigationRequestOverride : public QWebEnginePage
+{
+protected:
+ bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) override
+ {
+ Q_UNUSED(type);
+
+ if (isMainFrame && url.scheme().startsWith("data"))
+ settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
+ // TODO: note this setting is flaky, consider settings().commit()
+ return true;
+ }
+};
+
+void tst_QWebEngineSettings::setInAcceptNavigationRequest()
+{
+ NavigationRequestOverride page;
+ QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool)));
+ QWebEngineProfile::defaultProfile()->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
+ QVERIFY(!page.settings()->testAttribute(QWebEngineSettings::JavascriptEnabled));
+
+ page.load(QUrl("about:blank"));
+ QVERIFY(loadFinishedSpy.wait());
+ QVERIFY(!page.settings()->testAttribute(QWebEngineSettings::JavascriptEnabled));
+
+ page.setHtml("<html><body>"
+ "<script>document.write('PASS')</script>"
+ "<noscript>FAIL</noscript>"
+ "</body></html>");
+ QVERIFY(loadFinishedSpy.wait());
+ QVERIFY(page.settings()->testAttribute(QWebEngineSettings::JavascriptEnabled));
+ QCOMPARE(toPlainTextSync(&page), QStringLiteral("PASS"));
+}
+
+void tst_QWebEngineSettings::disableReadingFromCanvas_data()
+{
+ QTest::addColumn<bool>("disableReadingFromCanvas");
+ QTest::addColumn<bool>("result");
+ QTest::newRow("disabled") << false << true;
+ QTest::newRow("enabled") << true << false;
+}
+
+void tst_QWebEngineSettings::disableReadingFromCanvas()
+{
+ QFETCH(bool, disableReadingFromCanvas);
+ QFETCH(bool, result);
+
+ QWebEnginePage page;
+ QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool)));
+ page.settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
+ page.settings()->setAttribute(QWebEngineSettings::ReadingFromCanvasEnabled,
+ !disableReadingFromCanvas);
+ page.setHtml("<html><body>"
+ "<canvas id='myCanvas' width='200' height='40' style='border:1px solid "
+ "#000000;'></canvas>"
+ "</body></html>");
+ QVERIFY(loadFinishedSpy.wait());
+ QCOMPARE(page.settings()->testAttribute(QWebEngineSettings::ReadingFromCanvasEnabled),
+ !disableReadingFromCanvas);
+
+ const QString jsCode("(function(){"
+ " var canvas = document.getElementById(\"myCanvas\");"
+ " var ctx = canvas.getContext(\"2d\");"
+ " ctx.fillStyle = \"rgb(255,0,255)\";"
+ " ctx.fillRect(0, 0, 200, 40);"
+ " try {"
+ " src = canvas.toDataURL();"
+ " }"
+ " catch(err) {"
+ " src = \"\";"
+ " }"
+ " return src.length ? true : false;"
+ "})();");
+ QCOMPARE(evaluateJavaScriptSync(&page, jsCode).toBool(), result);
+}
+
+void tst_QWebEngineSettings::forceDarkMode()
+{
+ QWebEnginePage page;
+ QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool)));
+ page.settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
+
+ // based on: https://developer.chrome.com/blog/auto-dark-theme/#detecting-auto-dark-theme
+ page.setHtml("<html><body>"
+ "<div id=\"detection\", style=\"display: none; background-color: canvas; color-scheme: light\"</div>"
+ "</body></html>");
+
+ const QString isAutoDark("(() => {"
+ " const detectionDiv = document.querySelector('#detection');"
+ " return getComputedStyle(detectionDiv).backgroundColor != 'rgb(255, 255, 255)';"
+ "})()");
+
+ QVERIFY(loadFinishedSpy.wait());
+ QTRY_COMPARE(evaluateJavaScriptSync(&page, isAutoDark).toBool(), false);
+ page.settings()->setAttribute(QWebEngineSettings::ForceDarkMode, true);
+ QTRY_COMPARE(evaluateJavaScriptSync(&page, isAutoDark).toBool(), true);
+}
+
+void tst_QWebEngineSettings::forceDarkModeMultiView()
+{
+ QWebEngineView view1;
+ QWebEngineView view2;
+ QWebEnginePage *page1 = view1.page();
+ QWebEnginePage *page2 = view2.page();
+ page1->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
+ page2->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
+ view1.resize(300,300);
+ view2.resize(300,300);
+ view1.show();
+ view2.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&view1));
+ QVERIFY(QTest::qWaitForWindowExposed(&view2));
+
+ QSignalSpy loadFinishedSpy(page1, SIGNAL(loadFinished(bool)));
+ QSignalSpy loadFinishedSpy2(page2, SIGNAL(loadFinished(bool)));
+ QString html("<html><body>"
+ "<div id=\"detection\", style=\"display: none; background-color: canvas; color-scheme: light\"</div>"
+ "</body></html>");
+
+ const QString isAutoDark("(() => {"
+ " const detectionDiv = document.querySelector('#detection');"
+ " return getComputedStyle(detectionDiv).backgroundColor != 'rgb(255, 255, 255)';"
+ "})()");
+
+ view1.setHtml(html);
+ QVERIFY(loadFinishedSpy.wait());
+ view2.setHtml(html);
+ QVERIFY(loadFinishedSpy2.wait());
+
+ // both views has light color-scheme
+ QTRY_COMPARE(evaluateJavaScriptSync(page1, isAutoDark).toBool(), false);
+ QTRY_COMPARE(evaluateJavaScriptSync(page2, isAutoDark).toBool(), false);
+ view1.settings()->setAttribute(QWebEngineSettings::ForceDarkMode, true);
+ // dark mode should apply only for view1
+ QTRY_COMPARE(evaluateJavaScriptSync(page1, isAutoDark).toBool(), true);
+ QTRY_COMPARE(evaluateJavaScriptSync(page2, isAutoDark).toBool(), false);
+}
+
+QTEST_MAIN(tst_QWebEngineSettings)
+
+#include "tst_qwebenginesettings.moc"
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/CMakeLists.txt b/tests/auto/core/qwebengineurlrequestinterceptor/CMakeLists.txt
new file mode 100644
index 000000000..c12c0c45c
--- /dev/null
+++ b/tests/auto/core/qwebengineurlrequestinterceptor/CMakeLists.txt
@@ -0,0 +1,49 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+include(../../util/util.cmake)
+include(../../httpserver/httpserver.cmake)
+
+qt_internal_add_test(tst_qwebengineurlrequestinterceptor
+ SOURCES
+ tst_qwebengineurlrequestinterceptor.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+ Qt::WebEngineCorePrivate
+ Qt::CorePrivate
+ Test::HttpServer
+ Test::Util
+)
+
+set(tst_qwebengineurlrequestinterceptor_resource_files
+ "resources/content.html"
+ "resources/content2.html"
+ "resources/content3.html"
+ "resources/favicon.html"
+ "resources/firstparty.html"
+ "resources/fontawesome.woff"
+ "resources/icons/favicon.png"
+ "resources/iframe.html"
+ "resources/iframe2.html"
+ "resources/iframe3.html"
+ "resources/image.html"
+ "resources/image_in_iframe.html"
+ "resources/index.html"
+ "resources/media.html"
+ "resources/media.mp4"
+ "resources/media_in_iframe.html"
+ "resources/resource.html"
+ "resources/resource_in_iframe.html"
+ "resources/script.js"
+ "resources/style.css"
+ "resources/sw.html"
+ "resources/sw.js"
+ "resources/postBodyFile.txt"
+)
+
+qt_internal_add_resource(tst_qwebengineurlrequestinterceptor "tst_qwebengineurlrequestinterceptor"
+ PREFIX
+ "/"
+ FILES
+ ${tst_qwebengineurlrequestinterceptor_resource_files}
+)
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/qwebengineurlrequestinterceptor.pro b/tests/auto/core/qwebengineurlrequestinterceptor/qwebengineurlrequestinterceptor.pro
deleted file mode 100644
index 9c239f1a7..000000000
--- a/tests/auto/core/qwebengineurlrequestinterceptor/qwebengineurlrequestinterceptor.pro
+++ /dev/null
@@ -1,2 +0,0 @@
-include(../tests.pri)
-include(../../shared/http.pri)
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/resources/content.html b/tests/auto/core/qwebengineurlrequestinterceptor/resources/content.html
index 360ad65ef..84bf55036 100644
--- a/tests/auto/core/qwebengineurlrequestinterceptor/resources/content.html
+++ b/tests/auto/core/qwebengineurlrequestinterceptor/resources/content.html
@@ -1,5 +1,6 @@
<html>
+<head><link rel="icon" href="data:,"></head>
<body>
-<a>This is test content</a>
+<a>Simple test page without favicon (meaning no separate request from http server)</a>
</body>
</html>
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/resources/content2.html b/tests/auto/core/qwebengineurlrequestinterceptor/resources/content2.html
new file mode 100644
index 000000000..84bf55036
--- /dev/null
+++ b/tests/auto/core/qwebengineurlrequestinterceptor/resources/content2.html
@@ -0,0 +1,6 @@
+<html>
+<head><link rel="icon" href="data:,"></head>
+<body>
+<a>Simple test page without favicon (meaning no separate request from http server)</a>
+</body>
+</html>
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/resources/content3.html b/tests/auto/core/qwebengineurlrequestinterceptor/resources/content3.html
new file mode 100644
index 000000000..84bf55036
--- /dev/null
+++ b/tests/auto/core/qwebengineurlrequestinterceptor/resources/content3.html
@@ -0,0 +1,6 @@
+<html>
+<head><link rel="icon" href="data:,"></head>
+<body>
+<a>Simple test page without favicon (meaning no separate request from http server)</a>
+</body>
+</html>
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/resources/postBodyFile.txt b/tests/auto/core/qwebengineurlrequestinterceptor/resources/postBodyFile.txt
new file mode 100644
index 000000000..7729c4e0a
--- /dev/null
+++ b/tests/auto/core/qwebengineurlrequestinterceptor/resources/postBodyFile.txt
@@ -0,0 +1,3 @@
+{
+"test": "1234"
+}
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/resources/sw.html b/tests/auto/core/qwebengineurlrequestinterceptor/resources/sw.html
index af44b45a2..fc3d9ded4 100644
--- a/tests/auto/core/qwebengineurlrequestinterceptor/resources/sw.html
+++ b/tests/auto/core/qwebengineurlrequestinterceptor/resources/sw.html
@@ -2,13 +2,40 @@
<html>
<body>
<script>
+ function logState(state) {
+ console.log("Service worker " + state)
+ }
+
+ const registerServiceWorker = async () => {
+ try {
+ var serviceWorker;
+ const registration = await navigator.serviceWorker.register('/sw.js');
+ if (registration.installing) {
+ serviceWorker = registration.installing;
+ } else if (registration.waiting) {
+ serviceWorker = registration.waiting;
+ } else if (registration.active) {
+ serviceWorker = registration.active;
+ }
+ } catch (error) {
+ console.error("Service worker registration error: ${error}");
+ }
+ if (serviceWorker) {
+ logState(serviceWorker.state);
+ serviceWorker.addEventListener('statechange', function(e) {
+ logState(e.target.state);
+ });
+ }
+ };
if ('serviceWorker' in navigator) {
- window.addEventListener('load', function() {
- navigator.serviceWorker.register('/sw.js').then(function(registration) {
- console.log('ServiceWorker registration successful with scope: ', registration.scope);
- }, function(err) {
- console.error('ServiceWorker registration failed: ', err);
- });
+ registerServiceWorker();
+ navigator.serviceWorker.ready.then((registration) => {
+ navigator.serviceWorker.onmessage = (event) => {
+ if (event.data && event.data.type === 'PONG') {
+ console.log("Service worker done");
+ }
+ };
+ registration.active.postMessage({type: 'PING'});
});
}
</script>
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/resources/sw.js b/tests/auto/core/qwebengineurlrequestinterceptor/resources/sw.js
index 2216e2a07..196a9ad67 100644
--- a/tests/auto/core/qwebengineurlrequestinterceptor/resources/sw.js
+++ b/tests/auto/core/qwebengineurlrequestinterceptor/resources/sw.js
@@ -1,3 +1,16 @@
self.addEventListener('install', function(event) {
- console.log('ServiceWorker installed');
+ event.waitUntil(self.skipWaiting());
+});
+
+self.addEventListener('activate', function(event) {
+ event.waitUntil(self.clients.claim());
+});
+self.addEventListener('message', (event) => {
+ if (event.data && event.data.type === 'PING') {
+ self.clients.matchAll({includeUncontrolled: true, type: 'window'}).then((clients) => {
+ if (clients && clients.length) {
+ clients[0].postMessage({type: 'PONG'});
+ }
+ });
+ }
});
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp b/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp
index 20e191a4f..7cea14c0c 100644
--- a/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp
+++ b/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp
@@ -1,44 +1,21 @@
-/****************************************************************************
-**
-** Copyright (C) 2017 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtWebEngine module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "../../widgets/util.h"
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
+#include <util.h>
#include <QtTest/QtTest>
#include <QtWebEngineCore/qwebengineurlrequestinfo.h>
+#include <QtWebEngineCore/private/qwebengineurlrequestinfo_p.h>
#include <QtWebEngineCore/qwebengineurlrequestinterceptor.h>
-#include <QtWebEngineWidgets/qwebenginepage.h>
-#include <QtWebEngineWidgets/qwebengineprofile.h>
-#include <QtWebEngineWidgets/qwebenginesettings.h>
+#include <QtWebEngineCore/qwebenginesettings.h>
+#include <QtWebEngineCore/qwebengineprofile.h>
+#include <QtWebEngineCore/qwebenginepage.h>
+#include <QtWebEngineCore/qwebenginehttprequest.h>
#include <httpserver.h>
#include <httpreqrep.h>
-typedef void (QWebEngineProfile::*InterceptorSetter)(QWebEngineUrlRequestInterceptor *interceptor);
-Q_DECLARE_METATYPE(InterceptorSetter)
class tst_QWebEngineUrlRequestInterceptor : public QObject
{
Q_OBJECT
@@ -54,28 +31,30 @@ public Q_SLOTS:
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
- void interceptRequest_data();
void interceptRequest();
- void ipv6HostEncoding_data();
void ipv6HostEncoding();
void requestedUrl_data();
void requestedUrl();
void setUrlSameUrl_data();
void setUrlSameUrl();
- void firstPartyUrl_data();
void firstPartyUrl();
void firstPartyUrlNestedIframes_data();
void firstPartyUrlNestedIframes();
void requestInterceptorByResourceType_data();
void requestInterceptorByResourceType();
- void firstPartyUrlHttp_data();
void firstPartyUrlHttp();
- void passRefererHeader_data();
- void passRefererHeader();
- void initiator_data();
+ void headers();
+ void customHeaders();
void initiator();
- void jsServiceWorker_data();
void jsServiceWorker();
+ void replaceInterceptor_data();
+ void replaceInterceptor();
+ void replaceOnIntercept();
+ void multipleRedirects();
+ void postWithBody_data();
+ void postWithBody();
+ void profilePreventsPageInterception_data();
+ void profilePreventsPageInterception();
};
tst_QWebEngineUrlRequestInterceptor::tst_QWebEngineUrlRequestInterceptor()
@@ -108,44 +87,53 @@ struct RequestInfo {
, firstPartyUrl(info.firstPartyUrl())
, initiator(info.initiator())
, resourceType(info.resourceType())
+ , headers(info.httpHeaders())
{}
QUrl requestUrl;
QUrl firstPartyUrl;
QUrl initiator;
int resourceType;
+ QHash<QByteArray, QByteArray> headers;
};
-static const QByteArray kHttpHeaderReferrerValue = QByteArrayLiteral("http://somereferrer.com/");
-static const QByteArray kHttpHeaderRefererName = QByteArrayLiteral("referer");
static const QUrl kRedirectUrl = QUrl("qrc:///resources/content.html");
+Q_LOGGING_CATEGORY(lc, "qt.webengine.tests")
+
class TestRequestInterceptor : public QWebEngineUrlRequestInterceptor
{
public:
QList<RequestInfo> requestInfos;
bool shouldRedirect = false;
+ QUrl redirectUrl;
QMap<QUrl, QSet<QUrl>> requestInitiatorUrls;
QMap<QByteArray, QByteArray> headers;
+ std::function<bool (QWebEngineUrlRequestInfo &)> onIntercept;
void interceptRequest(QWebEngineUrlRequestInfo &info) override
{
- QCOMPARE(QThread::currentThread() == QCoreApplication::instance()->thread(), !property("deprecated").toBool());
+ QVERIFY(QThread::currentThread() == QCoreApplication::instance()->thread());
+ qCDebug(lc) << this << "Type:" << info.resourceType() << info.requestMethod() << "Navigation:" << info.navigationType()
+ << info.requestUrl() << "Initiator:" << info.initiator();
// Since 63 we also intercept some unrelated blob requests..
if (info.requestUrl().scheme() == QLatin1String("blob"))
return;
+ if (onIntercept && !onIntercept(info))
+ return;
+
bool block = info.requestMethod() != QByteArrayLiteral("GET");
- bool redirect = shouldRedirect && info.requestUrl() != kRedirectUrl;
+ bool redirect = shouldRedirect && info.requestUrl() != redirectUrl;
+
+ // set additional headers if any required by test
+ for (auto it = headers.begin(); it != headers.end(); ++it) info.setHttpHeader(it.key(), it.value());
if (block) {
info.block(true);
} else if (redirect) {
- info.redirect(kRedirectUrl);
- } else {
- // set additional headers if any required by test
- for (auto it = headers.begin(); it != headers.end(); ++it) info.setHttpHeader(it.key(), it.value());
+ info.redirect(redirectUrl);
}
requestInitiatorUrls[info.requestUrl()].insert(info.initiator());
@@ -153,7 +141,7 @@ public:
// MEMO avoid unintentionally changing request when it is not needed for test logic
// since api behavior depends on 'changed' state of the info object
- Q_ASSERT(info.changed() == (block || redirect || !headers.empty()));
+ Q_ASSERT(info.changed() == (block || redirect));
}
bool shouldSkipRequest(const RequestInfo &requestInfo)
@@ -195,8 +183,31 @@ public:
return false;
}
- TestRequestInterceptor(bool redirect)
- : shouldRedirect(redirect)
+ TestRequestInterceptor(bool redirect = false, const QUrl &url = kRedirectUrl)
+ : shouldRedirect(redirect), redirectUrl(url)
+ {
+ }
+};
+
+class TestMultipleRedirectsInterceptor : public QWebEngineUrlRequestInterceptor {
+public:
+ QList<RequestInfo> requestInfos;
+ QMap<QUrl, QUrl> redirectPairs;
+ int redirectCount = 0;
+ void interceptRequest(QWebEngineUrlRequestInfo &info) override
+ {
+ QVERIFY(QThread::currentThread() == QCoreApplication::instance()->thread());
+ qCDebug(lc) << this << "Type:" << info.resourceType() << info.requestMethod() << "Navigation:" << info.navigationType()
+ << info.requestUrl() << "Initiator:" << info.initiator();
+ auto redirectUrl = redirectPairs.constFind(info.requestUrl());
+ if (redirectUrl != redirectPairs.constEnd()) {
+ info.redirect(redirectUrl.value());
+ requestInfos.append(info);
+ redirectCount++;
+ }
+ }
+
+ TestMultipleRedirectsInterceptor()
{
}
};
@@ -206,7 +217,7 @@ class ConsolePage : public QWebEnginePage {
public:
ConsolePage(QWebEngineProfile* profile) : QWebEnginePage(profile) {}
- virtual void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString& message, int lineNumber, const QString& sourceID)
+ void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString& message, int lineNumber, const QString& sourceID) override
{
levels.append(level);
messages.append(message);
@@ -220,25 +231,17 @@ public:
QStringList sourceIDs;
};
-void tst_QWebEngineUrlRequestInterceptor::interceptRequest_data()
-{
- QTest::addColumn<InterceptorSetter>("setter");
- QTest::newRow("ui") << &QWebEngineProfile::setUrlRequestInterceptor;
- QTest::newRow("io") << &QWebEngineProfile::setRequestInterceptor;
-}
-
void tst_QWebEngineUrlRequestInterceptor::interceptRequest()
{
- QFETCH(InterceptorSetter, setter);
QWebEngineProfile profile;
profile.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
TestRequestInterceptor interceptor(/* intercept */ false);
- (profile.*setter)(&interceptor);
+ profile.setUrlRequestInterceptor(&interceptor);
QWebEnginePage page(&profile);
QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
page.load(QUrl("qrc:///resources/index.html"));
- QTRY_COMPARE(loadSpy.count(), 1);
+ QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000);
QVariant success = loadSpy.takeFirst().takeFirst();
QVERIFY(success.toBool());
loadSpy.clear();
@@ -246,7 +249,7 @@ void tst_QWebEngineUrlRequestInterceptor::interceptRequest()
page.runJavaScript("post();", [&ok](const QVariant result){ ok = result; });
QTRY_VERIFY(ok.toBool());
- QTRY_COMPARE(loadSpy.count(), 1);
+ QTRY_COMPARE(loadSpy.size(), 1);
success = loadSpy.takeFirst().takeFirst();
// We block non-GET requests, so this should not succeed.
QVERIFY(!success.toBool());
@@ -254,22 +257,22 @@ void tst_QWebEngineUrlRequestInterceptor::interceptRequest()
interceptor.shouldRedirect = true;
page.load(QUrl("qrc:///resources/__placeholder__"));
- QTRY_COMPARE(loadSpy.count(), 1);
+ QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000);
success = loadSpy.takeFirst().takeFirst();
// The redirection for __placeholder__ should succeed.
QVERIFY(success.toBool());
loadSpy.clear();
- QCOMPARE(interceptor.requestInfos.count(), 4);
+ QCOMPARE(interceptor.requestInfos.size(), 4);
// Make sure that registering an observer does not modify the request.
TestRequestInterceptor observer(/* intercept */ false);
- (profile.*setter)(&observer);
+ profile.setUrlRequestInterceptor(&observer);
page.load(QUrl("qrc:///resources/__placeholder__"));
- QTRY_COMPARE(loadSpy.count(), 1);
+ QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000);
success = loadSpy.takeFirst().takeFirst();
// Since we do not intercept, loading an invalid path should not succeed.
QVERIFY(!success.toBool());
- QCOMPARE(observer.requestInfos.count(), 1);
+ QCOMPARE(observer.requestInfos.size(), 1);
}
class LocalhostContentProvider : public QWebEngineUrlRequestInterceptor
@@ -292,53 +295,44 @@ public:
QList<QUrl> requestedUrls;
};
-void tst_QWebEngineUrlRequestInterceptor::ipv6HostEncoding_data()
-{
- interceptRequest_data();
-}
-
void tst_QWebEngineUrlRequestInterceptor::ipv6HostEncoding()
{
- QFETCH(InterceptorSetter, setter);
QWebEngineProfile profile;
LocalhostContentProvider contentProvider;
- (profile.*setter)(&contentProvider);
+ profile.setUrlRequestInterceptor(&contentProvider);
QWebEnginePage page(&profile);
QSignalSpy spyLoadFinished(&page, SIGNAL(loadFinished(bool)));
page.setHtml("<p>Hi", QUrl::fromEncoded("http://[::1]/index.html"));
- QTRY_COMPARE(spyLoadFinished.count(), 1);
- QCOMPARE(contentProvider.requestedUrls.count(), 0);
+ QTRY_COMPARE(spyLoadFinished.size(), 1);
+ QCOMPARE(contentProvider.requestedUrls.size(), 0);
evaluateJavaScriptSync(&page, "var r = new XMLHttpRequest();"
"r.open('GET', 'http://[::1]/test.xml', false);"
"r.send(null);"
);
- QCOMPARE(contentProvider.requestedUrls.count(), 1);
+ QCOMPARE(contentProvider.requestedUrls.size(), 1);
QCOMPARE(contentProvider.requestedUrls.at(0), QUrl::fromEncoded("http://[::1]/test.xml"));
}
void tst_QWebEngineUrlRequestInterceptor::requestedUrl_data()
{
- QTest::addColumn<InterceptorSetter>("setter");
QTest::addColumn<bool>("interceptInPage");
- QTest::newRow("ui profile intercept") << &QWebEngineProfile::setUrlRequestInterceptor << false;
- QTest::newRow("ui page intercept") << &QWebEngineProfile::setUrlRequestInterceptor << true;
- QTest::newRow("io profile intercept") << &QWebEngineProfile::setRequestInterceptor << false;
+ QTest::newRow("profile intercept") << false;
+ QTest::newRow("page intercept") << true;
}
void tst_QWebEngineUrlRequestInterceptor::requestedUrl()
{
- QFETCH(InterceptorSetter, setter);
QFETCH(bool, interceptInPage);
QWebEngineProfile profile;
profile.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
TestRequestInterceptor interceptor(/* intercept */ true);
if (!interceptInPage)
- (profile.*setter)(&interceptor);
+ profile.setUrlRequestInterceptor(&interceptor);
QWebEnginePage page(&profile);
if (interceptInPage)
@@ -347,8 +341,8 @@ void tst_QWebEngineUrlRequestInterceptor::requestedUrl()
page.setUrl(QUrl("qrc:///resources/__placeholder__"));
QVERIFY(spy.wait());
- QTRY_COMPARE(spy.count(), 1);
- QVERIFY(interceptor.requestInfos.count() >= 1);
+ QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 20000);
+ QVERIFY(interceptor.requestInfos.size() >= 1);
QCOMPARE(interceptor.requestInfos.at(0).requestUrl, QUrl("qrc:///resources/content.html"));
QCOMPARE(page.requestedUrl(), QUrl("qrc:///resources/__placeholder__"));
QCOMPARE(page.url(), QUrl("qrc:///resources/content.html"));
@@ -356,15 +350,15 @@ void tst_QWebEngineUrlRequestInterceptor::requestedUrl()
interceptor.shouldRedirect = false;
page.setUrl(QUrl("qrc:/non-existent.html"));
- QTRY_COMPARE(spy.count(), 2);
- QVERIFY(interceptor.requestInfos.count() >= 3);
+ QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 2, 20000);
+ QVERIFY(interceptor.requestInfos.size() >= 3);
QCOMPARE(interceptor.requestInfos.at(2).requestUrl, QUrl("qrc:/non-existent.html"));
QCOMPARE(page.requestedUrl(), QUrl("qrc:///resources/__placeholder__"));
QCOMPARE(page.url(), QUrl("qrc:///resources/content.html"));
page.setUrl(QUrl("http://abcdef.abcdef"));
- QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 3, 15000);
- QVERIFY(interceptor.requestInfos.count() >= 4);
+ QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 3, 20000);
+ QVERIFY(interceptor.requestInfos.size() >= 4);
QCOMPARE(interceptor.requestInfos.at(3).requestUrl, QUrl("http://abcdef.abcdef/"));
QCOMPARE(page.requestedUrl(), QUrl("qrc:///resources/__placeholder__"));
QCOMPARE(page.url(), QUrl("qrc:///resources/content.html"));
@@ -377,13 +371,12 @@ void tst_QWebEngineUrlRequestInterceptor::setUrlSameUrl_data()
void tst_QWebEngineUrlRequestInterceptor::setUrlSameUrl()
{
- QFETCH(InterceptorSetter, setter);
QFETCH(bool, interceptInPage);
QWebEngineProfile profile;
TestRequestInterceptor interceptor(/* intercept */ true);
if (!interceptInPage)
- (profile.*setter)(&interceptor);
+ profile.setUrlRequestInterceptor(&interceptor);
QWebEnginePage page(&profile);
if (interceptInPage)
@@ -393,95 +386,89 @@ void tst_QWebEngineUrlRequestInterceptor::setUrlSameUrl()
page.setUrl(QUrl("qrc:///resources/__placeholder__"));
QVERIFY(spy.wait());
QCOMPARE(page.url(), QUrl("qrc:///resources/content.html"));
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
page.setUrl(QUrl("qrc:///resources/__placeholder__"));
QVERIFY(spy.wait());
QCOMPARE(page.url(), QUrl("qrc:///resources/content.html"));
- QCOMPARE(spy.count(), 2);
+ QCOMPARE(spy.size(), 2);
// Now a case without redirect.
page.setUrl(QUrl("qrc:///resources/content.html"));
QVERIFY(spy.wait());
QCOMPARE(page.url(), QUrl("qrc:///resources/content.html"));
- QCOMPARE(spy.count(), 3);
+ QCOMPARE(spy.size(), 3);
page.setUrl(QUrl("qrc:///resources/__placeholder__"));
QVERIFY(spy.wait());
QCOMPARE(page.url(), QUrl("qrc:///resources/content.html"));
- QCOMPARE(spy.count(), 4);
-}
-
-void tst_QWebEngineUrlRequestInterceptor::firstPartyUrl_data()
-{
- interceptRequest_data();
+ QCOMPARE(spy.size(), 4);
}
void tst_QWebEngineUrlRequestInterceptor::firstPartyUrl()
{
- QFETCH(InterceptorSetter, setter);
QWebEngineProfile profile;
TestRequestInterceptor interceptor(/* intercept */ false);
- (profile.*setter)(&interceptor);
+ profile.setUrlRequestInterceptor(&interceptor);
QWebEnginePage page(&profile);
QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
page.setUrl(QUrl("qrc:///resources/firstparty.html"));
QVERIFY(spy.wait());
- QVERIFY(interceptor.requestInfos.count() >= 2);
+ QVERIFY(interceptor.requestInfos.size() >= 2);
QCOMPARE(interceptor.requestInfos.at(0).requestUrl, QUrl("qrc:///resources/firstparty.html"));
QCOMPARE(interceptor.requestInfos.at(1).requestUrl, QUrl("qrc:///resources/content.html"));
QCOMPARE(interceptor.requestInfos.at(0).firstPartyUrl, QUrl("qrc:///resources/firstparty.html"));
QCOMPARE(interceptor.requestInfos.at(1).firstPartyUrl, QUrl("qrc:///resources/firstparty.html"));
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
}
void tst_QWebEngineUrlRequestInterceptor::firstPartyUrlNestedIframes_data()
{
- QUrl url = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebengineurlrequestinterceptor/resources/iframe.html"));
- QTest::addColumn<InterceptorSetter>("setter");
+ QUrl url = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + QLatin1String("/resources/iframe.html"));
QTest::addColumn<QUrl>("requestUrl");
- QTest::newRow("ui file") << &QWebEngineProfile::setUrlRequestInterceptor << url;
- QTest::newRow("io file") << &QWebEngineProfile::setRequestInterceptor << url;
- QTest::newRow("ui qrc") << &QWebEngineProfile::setUrlRequestInterceptor
- << QUrl("qrc:///resources/iframe.html");
- QTest::newRow("io qrc") << &QWebEngineProfile::setRequestInterceptor
- << QUrl("qrc:///resources/iframe.html");
+ QTest::newRow("ui file") << url;
+ QTest::newRow("ui qrc") << QUrl("qrc:///resources/iframe.html");
}
void tst_QWebEngineUrlRequestInterceptor::firstPartyUrlNestedIframes()
{
- QFETCH(InterceptorSetter, setter);
QFETCH(QUrl, requestUrl);
- if (requestUrl.scheme() == "file" && !QDir(TESTS_SOURCE_DIR).exists())
- W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll);
+ if (requestUrl.scheme() == "file"
+ && !QDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()).exists())
+ W_QSKIP(QString("This test requires access to resources found in '%1'")
+ .arg(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath())
+ .toLatin1()
+ .constData(),
+ SkipAll);
QString adjustedUrl = requestUrl.adjusted(QUrl::RemoveFilename).toString();
QWebEngineProfile profile;
TestRequestInterceptor interceptor(/* intercept */ false);
- (profile.*setter)(&interceptor);
+ profile.setUrlRequestInterceptor(&interceptor);
QWebEnginePage page(&profile);
QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
page.setUrl(requestUrl);
- QTRY_COMPARE(loadSpy.count(), 1);
+ QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000);
- QVERIFY(interceptor.requestInfos.count() >= 1);
+ QVERIFY(interceptor.requestInfos.size() >= 1);
RequestInfo info = interceptor.requestInfos.at(0);
QCOMPARE(info.requestUrl, requestUrl);
QCOMPARE(info.firstPartyUrl, requestUrl);
QCOMPARE(info.resourceType, QWebEngineUrlRequestInfo::ResourceTypeMainFrame);
- QVERIFY(interceptor.requestInfos.count() >= 2);
+ QVERIFY(interceptor.requestInfos.size() >= 2);
info = interceptor.requestInfos.at(1);
QCOMPARE(info.requestUrl, QUrl(adjustedUrl + "iframe2.html"));
QCOMPARE(info.firstPartyUrl, requestUrl);
QCOMPARE(info.resourceType, QWebEngineUrlRequestInfo::ResourceTypeSubFrame);
- QVERIFY(interceptor.requestInfos.count() >= 3);
+ QVERIFY(interceptor.requestInfos.size() >= 3);
info = interceptor.requestInfos.at(2);
QCOMPARE(info.requestUrl, QUrl(adjustedUrl + "iframe3.html"));
QCOMPARE(info.firstPartyUrl, requestUrl);
@@ -490,84 +477,88 @@ void tst_QWebEngineUrlRequestInterceptor::firstPartyUrlNestedIframes()
void tst_QWebEngineUrlRequestInterceptor::requestInterceptorByResourceType_data()
{
- QUrl firstPartyUrl = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebengineurlrequestinterceptor/resources/resource_in_iframe.html"));
- QUrl styleRequestUrl = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebengineurlrequestinterceptor/resources/style.css"));
- QUrl scriptRequestUrl = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebengineurlrequestinterceptor/resources/script.js"));
- QUrl fontRequestUrl = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebengineurlrequestinterceptor/resources/fontawesome.woff"));
- QUrl xhrRequestUrl = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebengineurlrequestinterceptor/resources/test"));
- QUrl imageFirstPartyUrl = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebengineurlrequestinterceptor/resources/image_in_iframe.html"));
- QUrl imageRequestUrl = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebengineurlrequestinterceptor/resources/icons/favicon.png"));
- QUrl mediaFirstPartyUrl = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebengineurlrequestinterceptor/resources/media_in_iframe.html"));
- QUrl mediaRequestUrl = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebengineurlrequestinterceptor/resources/media.mp4"));
- QUrl faviconFirstPartyUrl = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebengineurlrequestinterceptor/resources/favicon.html"));
- QUrl faviconRequestUrl = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebengineurlrequestinterceptor/resources/icons/favicon.png"));
-
- QTest::addColumn<InterceptorSetter>("setter");
+ QUrl firstPartyUrl = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + QLatin1String("/resources/resource_in_iframe.html"));
+ QUrl styleRequestUrl = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + QLatin1String("/resources/style.css"));
+ QUrl scriptRequestUrl = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + QLatin1String("/resources/script.js"));
+ QUrl fontRequestUrl = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + QLatin1String("/resources/fontawesome.woff"));
+ QUrl xhrRequestUrl = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + QLatin1String("/resources/test"));
+ QUrl imageFirstPartyUrl =
+ QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + QLatin1String("/resources/image_in_iframe.html"));
+ QUrl imageRequestUrl = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + QLatin1String("/resources/icons/favicon.png"));
+ QUrl mediaFirstPartyUrl =
+ QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + QLatin1String("/resources/media_in_iframe.html"));
+ QUrl mediaRequestUrl = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + QLatin1String("/resources/media.mp4"));
+ QUrl faviconFirstPartyUrl = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + QLatin1String("/resources/favicon.html"));
+ QUrl faviconRequestUrl = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + QLatin1String("/resources/icons/favicon.png"));
+
QTest::addColumn<QUrl>("requestUrl");
QTest::addColumn<QUrl>("firstPartyUrl");
QTest::addColumn<int>("resourceType");
- QStringList name = { "ui", "io" };
- QVector<InterceptorSetter> setters = { &QWebEngineProfile::setUrlRequestInterceptor,
- &QWebEngineProfile::setRequestInterceptor };
- for (int i = 0; i < 2; i++) {
- QTest::newRow(qPrintable(name[i] + "StyleSheet"))
- << setters[i] << styleRequestUrl << firstPartyUrl
- << static_cast<int>(QWebEngineUrlRequestInfo::ResourceTypeStylesheet);
- QTest::newRow(qPrintable(name[i] + "Script")) << setters[i] << scriptRequestUrl << firstPartyUrl
- << static_cast<int>(QWebEngineUrlRequestInfo::ResourceTypeScript);
- QTest::newRow(qPrintable(name[i] + "Image")) << setters[i] << imageRequestUrl << imageFirstPartyUrl
- << static_cast<int>(QWebEngineUrlRequestInfo::ResourceTypeImage);
- QTest::newRow(qPrintable(name[i] + "FontResource"))
- << setters[i] << fontRequestUrl << firstPartyUrl
- << static_cast<int>(QWebEngineUrlRequestInfo::ResourceTypeFontResource);
- QTest::newRow(qPrintable(name[i] + "Media")) << setters[i] << mediaRequestUrl << mediaFirstPartyUrl
- << static_cast<int>(QWebEngineUrlRequestInfo::ResourceTypeMedia);
- QTest::newRow(qPrintable(name[i] + "Favicon"))
- << setters[i] << faviconRequestUrl << faviconFirstPartyUrl
- << static_cast<int>(QWebEngineUrlRequestInfo::ResourceTypeFavicon);
- QTest::newRow(qPrintable(name[i] + "Xhr")) << setters[i] << xhrRequestUrl << firstPartyUrl
- << static_cast<int>(QWebEngineUrlRequestInfo::ResourceTypeXhr);
- }
+ QTest::newRow("StyleSheet")
+ << styleRequestUrl << firstPartyUrl
+ << static_cast<int>(QWebEngineUrlRequestInfo::ResourceTypeStylesheet);
+ QTest::newRow("Script") << scriptRequestUrl << firstPartyUrl
+ << static_cast<int>(QWebEngineUrlRequestInfo::ResourceTypeScript);
+ QTest::newRow("Image") << imageRequestUrl << imageFirstPartyUrl
+ << static_cast<int>(QWebEngineUrlRequestInfo::ResourceTypeImage);
+ QTest::newRow("FontResource")
+ << fontRequestUrl << firstPartyUrl
+ << static_cast<int>(QWebEngineUrlRequestInfo::ResourceTypeFontResource);
+ QTest::newRow(qPrintable("Media")) << mediaRequestUrl << mediaFirstPartyUrl
+ << static_cast<int>(QWebEngineUrlRequestInfo::ResourceTypeMedia);
+ QTest::newRow("Favicon")
+ << faviconRequestUrl << faviconFirstPartyUrl
+ << static_cast<int>(QWebEngineUrlRequestInfo::ResourceTypeFavicon);
+ QTest::newRow(qPrintable("Xhr")) << xhrRequestUrl << firstPartyUrl
+ << static_cast<int>(QWebEngineUrlRequestInfo::ResourceTypeXhr);
}
void tst_QWebEngineUrlRequestInterceptor::requestInterceptorByResourceType()
{
- if (!QDir(TESTS_SOURCE_DIR).exists())
- W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll);
- QFETCH(InterceptorSetter, setter);
+ if (!QDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()).exists())
+ W_QSKIP(QString("This test requires access to resources found in '%1'")
+ .arg(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath())
+ .toLatin1()
+ .constData(),
+ SkipAll);
QFETCH(QUrl, requestUrl);
QFETCH(QUrl, firstPartyUrl);
QFETCH(int, resourceType);
QWebEngineProfile profile;
TestRequestInterceptor interceptor(/* intercept */ false);
- (profile.*setter)(&interceptor);
+ profile.setUrlRequestInterceptor(&interceptor);
QWebEnginePage page(&profile);
QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
page.setUrl(firstPartyUrl);
- QTRY_COMPARE(loadSpy.count(), 1);
+ QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000);
- QTRY_COMPARE(interceptor.getUrlRequestForType(static_cast<QWebEngineUrlRequestInfo::ResourceType>(resourceType)).count(), 1);
+ QTRY_COMPARE(interceptor.getUrlRequestForType(static_cast<QWebEngineUrlRequestInfo::ResourceType>(resourceType)).size(), 1);
QList<RequestInfo> infos = interceptor.getUrlRequestForType(static_cast<QWebEngineUrlRequestInfo::ResourceType>(resourceType));
- QVERIFY(infos.count() >= 1);
+ QVERIFY(infos.size() >= 1);
QCOMPARE(infos.at(0).requestUrl, requestUrl);
QCOMPARE(infos.at(0).firstPartyUrl, firstPartyUrl);
QCOMPARE(infos.at(0).resourceType, resourceType);
}
-void tst_QWebEngineUrlRequestInterceptor::firstPartyUrlHttp_data()
-{
- interceptRequest_data();
-}
-
void tst_QWebEngineUrlRequestInterceptor::firstPartyUrlHttp()
{
- QFETCH(InterceptorSetter, setter);
QWebEngineProfile profile;
TestRequestInterceptor interceptor(/* intercept */ false);
- (profile.*setter)(&interceptor);
+ profile.setUrlRequestInterceptor(&interceptor);
QWebEnginePage page(&profile);
QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
@@ -578,12 +569,6 @@ void tst_QWebEngineUrlRequestInterceptor::firstPartyUrlHttp()
QList<RequestInfo> infos;
- // SubFrame
- QTRY_VERIFY(interceptor.hasUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeSubFrame));
- infos = interceptor.getUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeSubFrame);
- foreach (auto info, infos)
- QCOMPARE(info.firstPartyUrl, firstPartyUrl);
-
// Stylesheet
QTRY_VERIFY(interceptor.hasUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeStylesheet));
infos = interceptor.getUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeStylesheet);
@@ -627,56 +612,105 @@ void tst_QWebEngineUrlRequestInterceptor::firstPartyUrlHttp()
QCOMPARE(info.firstPartyUrl, firstPartyUrl);
}
-void tst_QWebEngineUrlRequestInterceptor::passRefererHeader_data()
+void tst_QWebEngineUrlRequestInterceptor::headers()
{
- interceptRequest_data();
+ HttpServer httpServer;
+ httpServer.setResourceDirs({ QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + "/resources" });
+ QVERIFY(httpServer.start());
+ QWebEngineProfile profile;
+ TestRequestInterceptor interceptor(false);
+ profile.setUrlRequestInterceptor(&interceptor);
+
+ QWebEnginePage page(&profile);
+ QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
+
+ QWebEngineHttpRequest request(httpServer.url("/content.html"));
+ request.setHeader("X-HEADERNAME", "HEADERVALUE");
+ page.load(request);
+ QVERIFY(spy.wait());
+ QVERIFY(interceptor.requestInfos.last().headers.contains("X-HEADERNAME"));
+ QCOMPARE(interceptor.requestInfos.last().headers.value("X-HEADERNAME"),
+ QByteArray("HEADERVALUE"));
+
+ bool jsFinished = false;
+
+ page.runJavaScript(R"(
+var request = new XMLHttpRequest();
+request.open('GET', 'resource.html', /* async = */ false);
+request.setRequestHeader('X-FOO', 'BAR');
+request.send();
+)",
+ [&](const QVariant &) { jsFinished = true; });
+ QTRY_VERIFY(jsFinished);
+ QVERIFY(interceptor.requestInfos.last().headers.contains("X-FOO"));
+ QCOMPARE(interceptor.requestInfos.last().headers.value("X-FOO"), QByteArray("BAR"));
}
-void tst_QWebEngineUrlRequestInterceptor::passRefererHeader()
+void tst_QWebEngineUrlRequestInterceptor::customHeaders()
{
- QFETCH(InterceptorSetter, setter);
// Create HTTP Server to parse the request.
HttpServer httpServer;
-
- if (!httpServer.start())
- QSKIP("Failed to start http server");
-
- bool succeeded = false;
- connect(&httpServer, &HttpServer::newRequest, [&succeeded](HttpReqRep *rr) {
- const QByteArray headerValue = rr->requestHeader(kHttpHeaderRefererName);
- QCOMPARE(headerValue, kHttpHeaderReferrerValue);
- succeeded = headerValue == kHttpHeaderReferrerValue;
- rr->sendResponse();
- });
+ httpServer.setResourceDirs({ QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources" });
+ QVERIFY(httpServer.start());
QWebEngineProfile profile;
TestRequestInterceptor interceptor(false);
- interceptor.headers.insert(kHttpHeaderRefererName, kHttpHeaderReferrerValue);
- (profile.*setter)(&interceptor);
+ profile.setUrlRequestInterceptor(&interceptor);
QWebEnginePage page(&profile);
QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
- QWebEngineHttpRequest httpRequest;
- QUrl requestUrl = httpServer.url();
- httpRequest.setUrl(requestUrl);
- page.load(httpRequest);
+ interceptor.headers = {
+ { "referer", "http://somereferrer.com/" },
+ { "from", "user@example.com" },
+ { "user-agent", "mozilla/5.0 (x11; linux x86_64; rv:12.0) gecko/20100101 firefox/12.0" },
+ };
+
+ QMap<QByteArray, QByteArray> actual, expected;
+ connect(&httpServer, &HttpServer::newRequest, [&] (HttpReqRep *rr) {
+ for (auto it = expected.begin(); it != expected.end(); ++it) {
+ auto headerValue = rr->requestHeader(it.key());
+ actual[it.key()] = headerValue;
+ QCOMPARE(headerValue, it.value());
+ }
+ });
+
+ auto dumpHeaders = [&] () {
+ QString s; QDebug d(&s);
+ for (auto it = expected.begin(); it != expected.end(); ++it)
+ d << "\n\tHeader:" << it.key() << "| actual:" << actual[it.key()] << "expected:" << it.value();
+ return s;
+ };
+
+ expected = interceptor.headers;
+ page.load(httpServer.url("/content.html"));
QVERIFY(spy.wait());
- (void) httpServer.stop();
- QVERIFY(succeeded);
-}
+ QVERIFY2(actual == expected, qPrintable(dumpHeaders()));
-void tst_QWebEngineUrlRequestInterceptor::initiator_data()
-{
- interceptRequest_data();
+ // test that custom headers are also applied on redirect
+ interceptor.shouldRedirect = true;
+ interceptor.redirectUrl = httpServer.url("/content2.html");
+ interceptor.headers = {
+ { "referer", "http://somereferrer2.com/" },
+ { "from", "user2@example.com" },
+ { "user-agent", "mozilla/5.0 (compatible; googlebot/2.1; +http://www.google.com/bot.html)" },
+ };
+
+ actual.clear();
+ expected = interceptor.headers;
+ page.triggerAction(QWebEnginePage::Reload);
+ QVERIFY(spy.wait());
+ QVERIFY2(actual == expected, qPrintable(dumpHeaders()));
+
+ (void) httpServer.stop();
}
void tst_QWebEngineUrlRequestInterceptor::initiator()
{
- QFETCH(InterceptorSetter, setter);
QWebEngineProfile profile;
TestRequestInterceptor interceptor(/* intercept */ false);
- (profile.*setter)(&interceptor);
+ profile.setUrlRequestInterceptor(&interceptor);
QWebEnginePage page(&profile);
QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
@@ -687,12 +721,6 @@ void tst_QWebEngineUrlRequestInterceptor::initiator()
QList<RequestInfo> infos;
- // SubFrame
- QTRY_VERIFY(interceptor.hasUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeSubFrame));
- infos = interceptor.getUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeSubFrame);
- foreach (auto info, infos)
- QVERIFY(interceptor.requestInitiatorUrls[info.requestUrl].contains(info.initiator));
-
// Stylesheet
QTRY_VERIFY(interceptor.hasUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeStylesheet));
infos = interceptor.getUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeStylesheet);
@@ -736,31 +764,31 @@ void tst_QWebEngineUrlRequestInterceptor::initiator()
QVERIFY(interceptor.requestInitiatorUrls[info.requestUrl].contains(info.initiator));
}
-void tst_QWebEngineUrlRequestInterceptor::jsServiceWorker_data()
-{
- interceptRequest_data();
-}
-
void tst_QWebEngineUrlRequestInterceptor::jsServiceWorker()
{
- QFETCH(InterceptorSetter, setter);
HttpServer server;
- server.setResourceDirs({ TESTS_SOURCE_DIR "qwebengineurlrequestinterceptor/resources" });
+ server.setResourceDirs({ QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + "/resources" });
QVERIFY(server.start());
-
- QWebEngineProfile profile(QStringLiteral("Test"));
+ QWebEngineProfile profile;
std::unique_ptr<ConsolePage> page;
page.reset(new ConsolePage(&profile));
TestRequestInterceptor interceptor(/* intercept */ false);
- (profile.*setter)(&interceptor);
+ profile.setUrlRequestInterceptor(&interceptor);
QVERIFY(loadSync(page.get(), server.url("/sw.html")));
// We expect only one message here, because logging of services workers is not exposed in our API.
- QTRY_COMPARE(page->messages.count(), 1);
- QCOMPARE(page->levels.at(0), QWebEnginePage::InfoMessageLevel);
+ // Note this is very fragile setup , you need fresh profile otherwise install event might not get triggered
+ // and this in turn can lead to incorrect intercepted requests, therefore we should keep this off the record.
+ QTRY_COMPARE_WITH_TIMEOUT(page->messages.size(), 5, 20000);
- QUrl firstPartyUrl = QUrl(server.url().toString(QUrl::RemovePort));
+ QCOMPARE(page->levels.at(0), QWebEnginePage::InfoMessageLevel);
+ QCOMPARE(page->messages.at(0),QLatin1String("Service worker installing"));
+ QCOMPARE(page->messages.at(1),QLatin1String("Service worker installed"));
+ QCOMPARE(page->messages.at(2),QLatin1String("Service worker activating"));
+ QCOMPARE(page->messages.at(3),QLatin1String("Service worker activated"));
+ QCOMPARE(page->messages.at(4),QLatin1String("Service worker done"));
+ QUrl firstPartyUrl = QUrl(server.url().toString() + "sw.html");
QList<RequestInfo> infos;
// Service Worker
QTRY_VERIFY(interceptor.hasUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeServiceWorker));
@@ -771,5 +799,324 @@ void tst_QWebEngineUrlRequestInterceptor::jsServiceWorker()
QVERIFY(server.stop());
}
+void tst_QWebEngineUrlRequestInterceptor::replaceInterceptor_data()
+{
+ QTest::addColumn<bool>("firstInterceptIsInPage");
+ QTest::addColumn<bool>("keepInterceptionPoint");
+ QTest::newRow("page") << true << true;
+ QTest::newRow("page-profile") << true << false;
+ QTest::newRow("profile") << false << true;
+ QTest::newRow("profile-page") << false << false;
+}
+
+void tst_QWebEngineUrlRequestInterceptor::replaceInterceptor()
+{
+ QFETCH(bool, firstInterceptIsInPage);
+ QFETCH(bool, keepInterceptionPoint);
+
+ HttpServer server;
+ server.setResourceDirs({ ":/resources" });
+ QVERIFY(server.start());
+
+ QWebEngineProfile profile;
+ profile.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+ QWebEnginePage page(&profile);
+ QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
+ bool fetchFinished = false;
+
+ auto setInterceptor = [&] (QWebEngineUrlRequestInterceptor *interceptor, bool interceptInPage) {
+ interceptInPage ? page.setUrlRequestInterceptor(interceptor) : profile.setUrlRequestInterceptor(interceptor);
+ };
+
+ std::vector<TestRequestInterceptor> interceptors(3);
+ std::vector<int> requestsOnReplace;
+ setInterceptor(&interceptors.front(), firstInterceptIsInPage);
+
+ auto sc = connect(&page, &QWebEnginePage::loadFinished, [&] () {
+ auto currentInterceptorIndex = requestsOnReplace.size();
+ requestsOnReplace.push_back(interceptors[currentInterceptorIndex].requestInfos.size());
+
+ bool isFirstReinstall = currentInterceptorIndex == 0;
+ bool interceptInPage = keepInterceptionPoint ? firstInterceptIsInPage : (isFirstReinstall ^ firstInterceptIsInPage);
+ setInterceptor(&interceptors[++currentInterceptorIndex], interceptInPage);
+ if (!keepInterceptionPoint)
+ setInterceptor(nullptr, !interceptInPage);
+
+ if (isFirstReinstall) {
+ page.triggerAction(QWebEnginePage::Reload);
+ } else {
+ page.runJavaScript("fetch('http://nonexistent.invalid').catch(() => {})", [&, interceptInPage] (const QVariant &) {
+ requestsOnReplace.push_back(interceptors.back().requestInfos.size());
+ setInterceptor(nullptr, interceptInPage);
+ fetchFinished = true;
+ });
+ }
+ });
+
+ page.setUrl(server.url("/favicon.html"));
+ QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 2, 20000);
+ QTRY_VERIFY(fetchFinished);
+
+ QString s; QDebug d(&s);
+ for (auto i = 0u; i < interceptors.size(); ++i) {
+ auto &&interceptor = interceptors[i];
+ auto &&requests = interceptor.requestInfos;
+ d << "\nInterceptor [" << i << "] with" << requestsOnReplace[i] << "requests on replace and" << requests.size() << "in the end:";
+ for (int j = 0; j < requests.size(); ++j) {
+ auto &&r = requests[j];
+ d << "\n\t" << j << "| url:" << r.requestUrl << "firstPartyUrl:" << r.firstPartyUrl;
+ }
+ QVERIFY2(!requests.isEmpty(), qPrintable(s));
+ QVERIFY2(requests.size() == requestsOnReplace[i], qPrintable(s));
+ }
+}
+
+void tst_QWebEngineUrlRequestInterceptor::replaceOnIntercept()
+{
+ HttpServer server;
+ server.setResourceDirs({ ":/resources" });
+ QVERIFY(server.start());
+
+ QWebEngineProfile profile;
+ profile.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+ QWebEnginePage page(&profile);
+ QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
+
+ struct Interceptor : QWebEngineUrlRequestInterceptor {
+ Interceptor(const std::function<void ()> &a) : action(a) { }
+ void interceptRequest(QWebEngineUrlRequestInfo &) override { action(); }
+ std::function<void ()> action;
+ int interceptRequestReceived = 0;
+ };
+
+ TestRequestInterceptor profileInterceptor, pageInterceptor1, pageInterceptor2;
+ page.setUrlRequestInterceptor(&pageInterceptor1);
+ profile.setUrlRequestInterceptor(&profileInterceptor);
+ profileInterceptor.onIntercept = [&] (QWebEngineUrlRequestInfo &) {
+ page.setUrlRequestInterceptor(&pageInterceptor2);
+ return true;
+ };
+
+ page.setUrl(server.url("/favicon.html"));
+ QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 20000);
+ QTRY_COMPARE(profileInterceptor.requestInfos.size(), 2);
+
+ // if interceptor for page was replaced on intercept call in profile then, since request first
+ // comes to profile, forward to page's interceptor should land to second one
+ QCOMPARE(pageInterceptor1.requestInfos.size(), 0);
+ QCOMPARE(profileInterceptor.requestInfos.size(), pageInterceptor2.requestInfos.size());
+
+ page.setUrlRequestInterceptor(&pageInterceptor1);
+ bool fetchFinished = false;
+ page.runJavaScript("fetch('http://nonexistent.invalid').catch(() => {})", [&] (const QVariant &) {
+ page.setUrlRequestInterceptor(&pageInterceptor2);
+ fetchFinished = true;
+ });
+
+ QTRY_VERIFY(fetchFinished);
+ QCOMPARE(profileInterceptor.requestInfos.size(), 3);
+ QCOMPARE(pageInterceptor1.requestInfos.size(), 0);
+ QCOMPARE(profileInterceptor.requestInfos.size(), pageInterceptor2.requestInfos.size());
+}
+
+void tst_QWebEngineUrlRequestInterceptor::multipleRedirects()
+{
+ HttpServer server;
+ server.setResourceDirs({ ":/resources" });
+ QVERIFY(server.start());
+
+ TestMultipleRedirectsInterceptor multiInterceptor;
+ multiInterceptor.redirectPairs.insert(QUrl(server.url("/content.html")), QUrl(server.url("/content2.html")));
+ multiInterceptor.redirectPairs.insert(QUrl(server.url("/content2.html")), QUrl(server.url("/content3.html")));
+
+ QWebEngineProfile profile;
+ profile.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+ profile.setUrlRequestInterceptor(&multiInterceptor);
+ QWebEnginePage page(&profile);
+ QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
+
+ page.setUrl(server.url("/content.html"));
+
+ QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 20000);
+ QTRY_COMPARE(multiInterceptor.redirectCount, 2);
+ QTRY_COMPARE(multiInterceptor.requestInfos.size(), 2);
+}
+
+class TestPostRequestInterceptor : public QWebEngineUrlRequestInterceptor
+{
+public:
+ TestPostRequestInterceptor(QString expected, bool isAppendFile, QObject *parent = nullptr)
+ : QWebEngineUrlRequestInterceptor(parent)
+ , m_expected(expected)
+ , m_isAppendFile(isAppendFile)
+ {};
+
+ void interceptRequest(QWebEngineUrlRequestInfo &info) override
+ {
+ info.block(true);
+ isCalled = true;
+
+ QIODevice *requestBodyDevice = info.requestBody();
+
+ if (m_isAppendFile) {
+ info.d_ptr->appendFileToResourceRequestBodyForTest(":/resources/postBodyFile.txt");
+ }
+
+ requestBodyDevice->open(QIODevice::ReadOnly);
+
+ const QString webKitBoundary = requestBodyDevice->read(40);
+ QVERIFY(webKitBoundary.contains("------WebKitFormBoundary"));
+
+ const QString fullBodyWithoutBoundaries = (webKitBoundary + requestBodyDevice->readAll())
+ .replace(webKitBoundary, "")
+ .replace("\r", "")
+ .replace("\n", "")
+ .replace(" ", "");
+
+ QCOMPARE(fullBodyWithoutBoundaries, m_expected);
+
+ requestBodyDevice->close();
+ }
+
+ bool isCalled = false;
+ QString m_expected;
+ bool m_isAppendFile;
+};
+
+void tst_QWebEngineUrlRequestInterceptor::postWithBody_data()
+{
+ QTest::addColumn<QString>("input");
+ QTest::addColumn<QString>("output");
+ QTest::addColumn<bool>("isAppendFile");
+ QTest::addRow("FormData append (DataElementByte)")
+ << "fd.append('userId', 1);"
+ "fd.append('title',' Test123');"
+ "fd.append('completed', false);"
+ << "Content-Disposition:form-data;name=\"userId"
+ "\"1Content-Disposition:form-data"
+ ";name=\"title\"Test123Content-Di"
+ "sposition:form-data;name=\"completed\"f"
+ "alse--"
+ << false;
+ QTest::addRow("FormData blob (DataElementPipe)")
+ << "const blob1 = new Blob(['blob1thisisablob'],"
+ "{type: 'text/plain'});"
+ "fd.append('blob1', blob1);"
+ << "Content-Disposition:form-data;name=\"blob1"
+ "\";filename=\"blob\"Content-Type:text/plai"
+ "nblob1thisisablob--"
+ << false;
+ QTest::addRow("Append file (DataElementFile)") << ""
+ << "--{\"test\":\"1234\"}\"1234\"}" << true;
+ QTest::addRow("All combined") << "fd.append('userId', 1);"
+ "fd.append('title', 'Test123');"
+ "fd.append('completed', false);"
+ "const blob1 = new Blob(['blob1thisisablob'],"
+ "{type: 'text/plain'});"
+ "const blob2 = new Blob(['blob2thisisanotherblob'],"
+ "{type: 'text/plain'});"
+ "fd.append('blob1', blob1);"
+ "fd.append('userId', 2);"
+ "fd.append('title', 'Test456');"
+ "fd.append('completed', true);"
+ "fd.append('blob2', blob2);"
+ << "Content-Disposition:form-data;name=\"userId\""
+ "1Content-Disposition:form-data;na"
+ "me=\"title\"Test123Content-Disposit"
+ "ion:form-data;name=\"completed\"false"
+ "Content-Disposition:form-data;name=\"blob1\";"
+ "filename=\"blob\"Content-Type:text/plain"
+ "blob1thisisablobContent-Disposition:form-"
+ "data;name=\"userId\"2Content-Dispos"
+ "ition:form-data;name=\"title\"Test456"
+ "Content-Disposition:form-data;name=\"complete"
+ "d\"trueContent-Disposition:form-da"
+ "ta;name=\"blob2\";filename=\"blob\"Content-Ty"
+ "pe:text/plainblob2thisisanotherblob--"
+ "{\"test\":\"1234\"}\"1234\"}"
+ << true;
+}
+
+void tst_QWebEngineUrlRequestInterceptor::postWithBody()
+{
+ QFETCH(QString, input);
+ QFETCH(QString, output);
+ QFETCH(bool, isAppendFile);
+
+ QString script;
+ script.append("const fd = new FormData();");
+ script.append(input);
+ script.append("fetch('http://127.0.0.1', {method: 'POST',body: fd});");
+
+ QWebEngineProfile profile;
+ profile.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+ TestPostRequestInterceptor interceptor(output, isAppendFile);
+ profile.setUrlRequestInterceptor(&interceptor);
+ QWebEnginePage page(&profile);
+ bool ok = false;
+
+ page.runJavaScript(script, [&ok](const QVariant) { ok = true; });
+
+ QTRY_VERIFY(ok);
+ QVERIFY(interceptor.isCalled);
+}
+
+class PageOrProfileInterceptor : public QWebEngineUrlRequestInterceptor
+{
+public:
+ PageOrProfileInterceptor(const QString &profileAction)
+ : profileAction(profileAction)
+ {
+ }
+
+ void interceptRequest(QWebEngineUrlRequestInfo &info) override
+ {
+ if (profileAction == "block")
+ info.block(true);
+ else if (profileAction == "redirect")
+ info.redirect(QUrl("data:text/html,<p>redirected"));
+ else if (profileAction == "add header")
+ info.setHttpHeader("Custom-Header", "Value");
+ else
+ QVERIFY(info.httpHeaders().contains("Custom-Header"));
+ ran = true;
+ }
+
+ QString profileAction;
+ bool ran = false;
+};
+
+void tst_QWebEngineUrlRequestInterceptor::profilePreventsPageInterception_data()
+{
+ QTest::addColumn<QString>("profileAction");
+ QTest::addColumn<bool>("interceptInProfile");
+ QTest::addColumn<bool>("interceptInPage");
+ QTest::newRow("block") << "block" << true << false;
+ QTest::newRow("redirect") << "redirect" << true << false;
+ QTest::newRow("add header") << "add header" << true << true;
+}
+
+void tst_QWebEngineUrlRequestInterceptor::profilePreventsPageInterception()
+{
+ QFETCH(QString, profileAction);
+ QFETCH(bool, interceptInProfile);
+ QFETCH(bool, interceptInPage);
+
+ QWebEngineProfile profile;
+ PageOrProfileInterceptor profileInterceptor(profileAction);
+ profile.setUrlRequestInterceptor(&profileInterceptor);
+ profile.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+
+ QWebEnginePage page(&profile);
+ PageOrProfileInterceptor pageInterceptor("");
+ page.setUrlRequestInterceptor(&pageInterceptor);
+ QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
+
+ page.load(QUrl("qrc:///resources/index.html"));
+ QTRY_COMPARE(loadSpy.size(), 1);
+ QCOMPARE(profileInterceptor.ran, interceptInProfile);
+ QCOMPARE(pageInterceptor.ran, interceptInPage);
+}
+
QTEST_MAIN(tst_QWebEngineUrlRequestInterceptor)
#include "tst_qwebengineurlrequestinterceptor.moc"
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.qrc b/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.qrc
deleted file mode 100644
index 6a34635f7..000000000
--- a/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.qrc
+++ /dev/null
@@ -1,24 +0,0 @@
-<!DOCTYPE RCC><RCC version="1.0">
- <qresource prefix="/">
- <file>resources/content.html</file>
- <file>resources/favicon.html</file>
- <file>resources/firstparty.html</file>
- <file>resources/fontawesome.woff</file>
- <file>resources/iframe.html</file>
- <file>resources/iframe2.html</file>
- <file>resources/iframe3.html</file>
- <file>resources/image.html</file>
- <file>resources/image_in_iframe.html</file>
- <file>resources/index.html</file>
- <file>resources/media.html</file>
- <file>resources/media.mp4</file>
- <file>resources/media_in_iframe.html</file>
- <file>resources/resource.html</file>
- <file>resources/resource_in_iframe.html</file>
- <file>resources/script.js</file>
- <file>resources/style.css</file>
- <file>resources/sw.html</file>
- <file>resources/sw.js</file>
- <file>resources/icons/favicon.png</file>
- </qresource>
-</RCC>
diff --git a/tests/auto/core/qwebengineurlrequestjob/CMakeLists.txt b/tests/auto/core/qwebengineurlrequestjob/CMakeLists.txt
new file mode 100644
index 000000000..02b668313
--- /dev/null
+++ b/tests/auto/core/qwebengineurlrequestjob/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_internal_add_test(tst_qwebengineurlrequestjob
+ SOURCES
+ tst_qwebengineurlrequestjob.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+)
+
+# Resources:
+set(tst_qwebengineurlrequestjob_resource_files
+ "additionalResponseHeadersScript.html"
+ "requestBodyScript.html"
+)
+
+qt_add_resources(tst_qwebengineurlrequestjob "tst_qwebengineurlrequestjob"
+ PREFIX
+ "/"
+ FILES
+ ${tst_qwebengineurlrequestjob_resource_files}
+)
diff --git a/tests/auto/core/qwebengineurlrequestjob/additionalResponseHeadersScript.html b/tests/auto/core/qwebengineurlrequestjob/additionalResponseHeadersScript.html
new file mode 100644
index 000000000..a1b8a92d3
--- /dev/null
+++ b/tests/auto/core/qwebengineurlrequestjob/additionalResponseHeadersScript.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+<body>
+ <script type="text/javascript">
+ var request = new XMLHttpRequest();
+ request.open('GET', 'additionalresponseheadershandler:about', /* async = */ false);
+ request.send();
+ var headers = request.getAllResponseHeaders();
+ console.log("TST_ADDITIONALRESPONSEHEADERS;" + headers);
+ </script>
+</body>
+</html>
diff --git a/tests/auto/core/qwebengineurlrequestjob/requestBodyScript.html b/tests/auto/core/qwebengineurlrequestjob/requestBodyScript.html
new file mode 100644
index 000000000..95b7ff1bf
--- /dev/null
+++ b/tests/auto/core/qwebengineurlrequestjob/requestBodyScript.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+<body>
+ <script type="text/javascript">
+ var request = new XMLHttpRequest();
+ request.open('POST', 'requestbodyhandler:about', /* async = */ false);
+ request.setRequestHeader('Content-Type', 'text/plain');
+ request.send('reading request body successful');
+ console.log("TST_REQUESTBODY;" + request.response);
+ </script>
+</body>
+</html>
diff --git a/tests/auto/core/qwebengineurlrequestjob/tst_qwebengineurlrequestjob.cpp b/tests/auto/core/qwebengineurlrequestjob/tst_qwebengineurlrequestjob.cpp
new file mode 100644
index 000000000..d48da2c44
--- /dev/null
+++ b/tests/auto/core/qwebengineurlrequestjob/tst_qwebengineurlrequestjob.cpp
@@ -0,0 +1,187 @@
+// 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/qwebengineurlschemehandler.h>
+#include <QtWebEngineCore/qwebengineurlscheme.h>
+#include <QtWebEngineCore/qwebengineurlrequestjob.h>
+#include <QtWebEngineCore/qwebengineprofile.h>
+#include <QtWebEngineCore/qwebenginepage.h>
+
+class CustomPage : public QWebEnginePage
+{
+ Q_OBJECT
+
+public:
+ CustomPage(QWebEngineProfile *profile, QString compareStringPrefix, QString compareStringSuffix,
+ QObject *parent = nullptr)
+ : QWebEnginePage(profile, parent)
+ , m_compareStringPrefix(compareStringPrefix)
+ , m_compareStringSuffix(compareStringSuffix)
+ , m_comparedMessageCount(0)
+ {
+ }
+
+ int comparedMessageCount() const { return m_comparedMessageCount; }
+
+signals:
+ void receivedMessage();
+
+protected:
+ void javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel level,
+ const QString &message, int lineNumber,
+ const QString &sourceID) override
+ {
+ Q_UNUSED(level);
+ Q_UNUSED(lineNumber);
+ Q_UNUSED(sourceID);
+
+ auto splitMessage = message.split(";");
+ if (splitMessage[0] == m_compareStringPrefix) {
+ QCOMPARE(splitMessage[1], m_compareStringSuffix);
+ m_comparedMessageCount++;
+ emit receivedMessage();
+ }
+ }
+
+private:
+ QString m_compareStringPrefix;
+ QString m_compareStringSuffix;
+ int m_comparedMessageCount;
+};
+
+class AdditionalResponseHeadersHandler : public QWebEngineUrlSchemeHandler
+{
+ Q_OBJECT
+public:
+ AdditionalResponseHeadersHandler(bool addAdditionalResponseHeaders, QObject *parent = nullptr)
+ : QWebEngineUrlSchemeHandler(parent)
+ , m_addAdditionalResponseHeaders(addAdditionalResponseHeaders)
+ {
+ }
+
+ void requestStarted(QWebEngineUrlRequestJob *job) override
+ {
+ QCOMPARE(job->requestUrl(), QUrl(schemeName + ":about"));
+
+ QMultiMap<QByteArray, QByteArray> additionalResponseHeaders;
+ if (m_addAdditionalResponseHeaders) {
+ additionalResponseHeaders.insert(QByteArray::fromStdString("test1"),
+ QByteArray::fromStdString("test1VALUE"));
+ additionalResponseHeaders.insert(QByteArray::fromStdString("test2"),
+ QByteArray::fromStdString("test2VALUE"));
+ }
+ job->setAdditionalResponseHeaders(additionalResponseHeaders);
+
+ QFile *file = new QFile(QStringLiteral(":additionalResponseHeadersScript.html"), job);
+ file->open(QIODevice::ReadOnly);
+
+ job->reply(QByteArrayLiteral("text/html"), file);
+ }
+
+ static void registerUrlScheme()
+ {
+ QWebEngineUrlScheme webUiScheme(schemeName);
+ webUiScheme.setFlags(QWebEngineUrlScheme::CorsEnabled);
+ QWebEngineUrlScheme::registerScheme(webUiScheme);
+ }
+
+ const static inline QByteArray schemeName =
+ QByteArrayLiteral("additionalresponseheadershandler");
+
+private:
+ bool m_addAdditionalResponseHeaders;
+};
+
+class RequestBodyHandler : public QWebEngineUrlSchemeHandler
+{
+ Q_OBJECT
+public:
+ void requestStarted(QWebEngineUrlRequestJob *job) override
+ {
+ QCOMPARE(job->requestUrl(), QUrl(schemeName + ":about"));
+ QCOMPARE(job->requestMethod(), QByteArrayLiteral("POST"));
+
+ QIODevice *requestBodyDevice = job->requestBody();
+ requestBodyDevice->open(QIODevice::ReadOnly);
+ QByteArray requestBody = requestBodyDevice->readAll();
+ requestBodyDevice->close();
+
+ QBuffer *buf = new QBuffer(job);
+ buf->open(QBuffer::ReadWrite);
+ buf->write(requestBody);
+ job->reply(QByteArrayLiteral("text/plain"), buf);
+ buf->close();
+ }
+
+ static void registerUrlScheme()
+ {
+ QWebEngineUrlScheme webUiScheme(schemeName);
+ webUiScheme.setFlags(QWebEngineUrlScheme::CorsEnabled
+ | QWebEngineUrlScheme::FetchApiAllowed);
+ QWebEngineUrlScheme::registerScheme(webUiScheme);
+ }
+
+ const static inline QByteArray schemeName = QByteArrayLiteral("requestbodyhandler");
+};
+
+class tst_QWebEngineUrlRequestJob : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QWebEngineUrlRequestJob() { }
+
+private Q_SLOTS:
+ void initTestCase()
+ {
+ AdditionalResponseHeadersHandler::registerUrlScheme();
+ RequestBodyHandler::registerUrlScheme();
+ }
+
+ void withAdditionalResponseHeaders_data()
+ {
+ QTest::addColumn<bool>("withHeaders");
+ QTest::addColumn<QString>("expectedHeaders");
+ QTest::newRow("headers enabled")
+ << true << "content-type: text/html\r\ntest1: test1value\r\ntest2: test2value\r\n";
+ QTest::newRow("headers disabled") << false << "content-type: text/html\r\n";
+ }
+
+ void withAdditionalResponseHeaders()
+ {
+ QFETCH(bool, withHeaders);
+ QFETCH(QString, expectedHeaders);
+
+ QWebEngineProfile profile;
+
+ AdditionalResponseHeadersHandler handler(withHeaders);
+ profile.installUrlSchemeHandler(AdditionalResponseHeadersHandler::schemeName, &handler);
+
+ CustomPage page(&profile, "TST_ADDITIONALRESPONSEHEADERS", expectedHeaders);
+ QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
+
+ page.load(QUrl("qrc:///additionalResponseHeadersScript.html"));
+ QVERIFY(spy.wait());
+ QCOMPARE(page.comparedMessageCount(), 1);
+ }
+
+ void requestBody()
+ {
+ QWebEngineProfile profile;
+
+ RequestBodyHandler handler;
+ profile.installUrlSchemeHandler(RequestBodyHandler::schemeName, &handler);
+
+ const QString expected = "reading request body successful";
+ CustomPage page(&profile, "TST_REQUESTBODY", expected);
+ QSignalSpy spy(&page, SIGNAL(receivedMessage()));
+
+ page.load(QUrl("qrc:///requestBodyScript.html"));
+ QVERIFY(spy.wait());
+ QCOMPARE(page.comparedMessageCount(), 1);
+ }
+};
+
+QTEST_MAIN(tst_QWebEngineUrlRequestJob)
+#include "tst_qwebengineurlrequestjob.moc"
diff --git a/tests/auto/core/tests.pri b/tests/auto/core/tests.pri
deleted file mode 100644
index 59d6c0865..000000000
--- a/tests/auto/core/tests.pri
+++ /dev/null
@@ -1,17 +0,0 @@
-TEMPLATE = app
-
-CONFIG += testcase
-
-VPATH += $$_PRO_FILE_PWD_
-TARGET = tst_$$TARGET
-
-SOURCES += $${TARGET}.cpp
-INCLUDEPATH += $$PWD
-
-exists($$_PRO_FILE_PWD_/$${TARGET}.qrc): RESOURCES += $${TARGET}.qrc
-
-QT += testlib network webenginewidgets widgets
-
-# This define is used by some tests to look up resources in the source tree
-DEFINES += TESTS_SOURCE_DIR=\\\"$$PWD/\\\"
-include(../embed_info_plist.pri)
diff --git a/tests/auto/core/webenginedriver/CMakeLists.txt b/tests/auto/core/webenginedriver/CMakeLists.txt
new file mode 100644
index 000000000..c8cf8b3ab
--- /dev/null
+++ b/tests/auto/core/webenginedriver/CMakeLists.txt
@@ -0,0 +1,15 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_webenginedriver LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+add_subdirectory(test)
+add_subdirectory(browser)
+
+add_dependencies(tst_webenginedriver
+ testbrowser
+)
diff --git a/tests/auto/core/webenginedriver/browser/CMakeLists.txt b/tests/auto/core/webenginedriver/browser/CMakeLists.txt
new file mode 100644
index 000000000..25e162e7b
--- /dev/null
+++ b/tests/auto/core/webenginedriver/browser/CMakeLists.txt
@@ -0,0 +1,13 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_internal_add_executable(testbrowser
+ SOURCES
+ main.cpp
+ LIBRARIES
+ Qt::Core
+ Qt::Gui
+ Qt::WebEngineWidgets
+ OUTPUT_DIRECTORY
+ ${CMAKE_CURRENT_BINARY_DIR}/..
+)
diff --git a/tests/auto/core/webenginedriver/browser/main.cpp b/tests/auto/core/webenginedriver/browser/main.cpp
new file mode 100644
index 000000000..4b8f3513f
--- /dev/null
+++ b/tests/auto/core/webenginedriver/browser/main.cpp
@@ -0,0 +1,21 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QtWebEngineCore/qwebenginepage.h>
+#include <QtWebEngineWidgets/qwebengineview.h>
+#include <QtWidgets/qapplication.h>
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+
+ QWebEngineView view;
+ QObject::connect(view.page(), &QWebEnginePage::windowCloseRequested, &app, &QApplication::quit);
+ QObject::connect(&app, &QApplication::aboutToQuit,
+ []() { fprintf(stderr, "Test browser is about to quit.\n"); });
+
+ view.resize(100, 100);
+ view.show();
+
+ return app.exec();
+}
diff --git a/tests/auto/core/webenginedriver/resources/input.html b/tests/auto/core/webenginedriver/resources/input.html
new file mode 100644
index 000000000..c21458350
--- /dev/null
+++ b/tests/auto/core/webenginedriver/resources/input.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ <input type="text" id="text_input">
+</body>
+</html>
diff --git a/tests/auto/core/webenginedriver/test/CMakeLists.txt b/tests/auto/core/webenginedriver/test/CMakeLists.txt
new file mode 100644
index 000000000..041bf955b
--- /dev/null
+++ b/tests/auto/core/webenginedriver/test/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+include(../../../util/util.cmake)
+
+qt_internal_add_test(tst_webenginedriver
+ SOURCES
+ ../tst_webenginedriver.cpp
+ LIBRARIES
+ Qt::Network
+ Qt::WebEngineCore
+ Qt::WebEngineWidgets
+ Test::Util
+ OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/.."
+)
+
+set(tst_webenginedriver_resource_files
+ "../resources/input.html"
+)
+
+qt_internal_add_resource(tst_webenginedriver "tst_webenginedriver"
+ PREFIX
+ "/"
+ FILES
+ ${tst_webenginedriver_resource_files}
+)
diff --git a/tests/auto/core/webenginedriver/tst_webenginedriver.cpp b/tests/auto/core/webenginedriver/tst_webenginedriver.cpp
new file mode 100644
index 000000000..cd3098b25
--- /dev/null
+++ b/tests/auto/core/webenginedriver/tst_webenginedriver.cpp
@@ -0,0 +1,631 @@
+// 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 <QtCore/qjsondocument.h>
+#include <QtCore/qlibraryinfo.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qprocess.h>
+#include <QtGui/qimage.h>
+#include <QtNetwork/qnetworkaccessmanager.h>
+#include <QtNetwork/qnetworkreply.h>
+#include <QtNetwork/qnetworkrequest.h>
+#include <QtWebEngineCore/qtwebenginecoreglobal.h>
+#include <QtWebEngineCore/qwebenginepage.h>
+#include <QtWebEngineCore/qwebengineprofile.h>
+#include <QtWebEngineWidgets/qwebengineview.h>
+
+#include <widgetutil.h>
+
+#define REMOTE_DEBUGGING_PORT 12345
+#define WEBENGINEDRIVER_PORT 9515
+
+class DriverServer : public QObject
+{
+ Q_OBJECT
+
+public:
+ DriverServer(QProcessEnvironment processEnvironment = {}, QStringList processArguments = {})
+ : m_serverURL(QUrl(
+ QLatin1String("http://localhost:%1").arg(QString::number(WEBENGINEDRIVER_PORT))))
+ {
+ QString driverPath = QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath)
+ + QLatin1String("/webenginedriver");
+#if defined(Q_OS_WIN)
+ driverPath += QLatin1String(".exe");
+#endif
+ m_process.setProgram(driverPath);
+
+ if (processArguments.isEmpty())
+ processArguments
+ << QLatin1String("--port=%1").arg(QString::number(WEBENGINEDRIVER_PORT));
+ m_process.setArguments(processArguments);
+
+ if (!processEnvironment.isEmpty())
+ m_process.setProcessEnvironment(processEnvironment);
+
+ connect(&m_process, &QProcess::errorOccurred, [this](QProcess::ProcessError error) {
+ qWarning() << "WebEngineDriver error occurred:" << error;
+ dumpConsoleMessages();
+ });
+
+ connect(&m_process, &QProcess::readyReadStandardError, [this]() {
+ QProcess::ProcessChannel tmp = m_process.readChannel();
+ m_process.setReadChannel(QProcess::StandardError);
+ while (m_process.canReadLine()) {
+ QString line = QString::fromUtf8(m_process.readLine());
+ if (line.endsWith(QLatin1Char('\n')))
+ line.truncate(line.size() - 1);
+ m_stderr << line;
+ }
+ m_process.setReadChannel(tmp);
+ });
+
+ connect(&m_process, &QProcess::readyReadStandardOutput, [this]() {
+ QProcess::ProcessChannel tmp = m_process.readChannel();
+ m_process.setReadChannel(QProcess::StandardOutput);
+ while (m_process.canReadLine()) {
+ QString line = QString::fromUtf8(m_process.readLine());
+ if (line.endsWith(QLatin1Char('\n')))
+ line.truncate(line.size() - 1);
+ m_stdout << line;
+ }
+ m_process.setReadChannel(tmp);
+ });
+ }
+
+ ~DriverServer()
+ {
+ if (m_process.state() != QProcess::Running)
+ return;
+
+ if (!m_sessionId.isEmpty())
+ deleteSession();
+
+ shutdown();
+ }
+
+ bool start()
+ {
+ if (!QFileInfo::exists(m_process.program())) {
+ qWarning() << "WebEngineDriver executable not found:" << m_process.program();
+ return false;
+ }
+
+ connect(&m_process, &QProcess::finished,
+ [this](int exitCode, QProcess::ExitStatus exitStatus) {
+ qWarning().nospace()
+ << "WebEngineDriver exited unexpectedly (exitCode: " << exitCode
+ << " exitStatus: " << exitStatus << "):";
+ dumpConsoleMessages();
+ });
+
+ m_process.start();
+
+ bool started = m_process.waitForStarted();
+ if (!started) {
+ qWarning() << "Failed to start WebEngineDriver:" << m_process.errorString();
+ return false;
+ }
+
+ bool ready = QTest::qWaitFor(
+ [this]() {
+ if (m_process.state() != QProcess::Running)
+ return true;
+
+ for (QString line : m_stdout) {
+ if (line.contains(
+ QLatin1String("WebEngineDriver was started successfully.")))
+ return true;
+ }
+
+ return false;
+ },
+ 10000);
+
+ if (ready && m_process.state() != QProcess::Running) {
+ // Warning is already reported by handler of QProcess::finished() signal.
+ return false;
+ }
+
+ if (!ready) {
+ if (m_stdout.empty())
+ qWarning("Starting WebEngineDriver timed out.");
+ else
+ qWarning("Something went wrong while starting WebEngineDriver:");
+
+ dumpConsoleMessages();
+ return false;
+ }
+
+ return true;
+ }
+
+ bool shutdown()
+ {
+ // Do not warn about unexpected exit.
+ disconnect(&m_process, &QProcess::finished, nullptr, nullptr);
+
+ bool sent = sendCommand(QLatin1String("/shutdown"));
+
+ bool finished = (m_process.state() == QProcess::NotRunning) || m_process.waitForFinished();
+ if (!finished || !sent)
+ qWarning() << "Failed to properly shutdown WebEngineDriver:" << m_process.errorString();
+
+ return finished;
+ }
+
+ bool sendCommand(QString command, const QJsonDocument &params = {},
+ QJsonDocument *result = nullptr)
+ {
+ if (command.contains(QLatin1String(":sessionId"))) {
+ if (m_sessionId.isEmpty()) {
+ qWarning("Unable to execute session command without session.");
+ return false;
+ }
+
+ QStringList commandList = command.split(QLatin1Char('/'));
+ for (int i = 0; i < commandList.size(); ++i) {
+ if (commandList[i] == QLatin1String(":sessionId")) {
+ commandList[i] = m_sessionId;
+ break;
+ }
+ }
+
+ command = commandList.join(QLatin1Char('/'));
+ }
+
+ QNetworkReply::NetworkError replyError = QNetworkReply::NoError;
+ QString replyString;
+
+ connect(
+ &m_qnam, &QNetworkAccessManager::finished, this,
+ [&replyError, &replyString](QNetworkReply *reply) {
+ replyError = reply->error();
+ replyString = QString::fromUtf8(reply->readAll());
+ },
+ static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
+
+ QNetworkRequest request;
+ QUrl requestURL = m_serverURL;
+
+ requestURL.setPath(command);
+ request.setUrl(requestURL);
+
+ if (params.isEmpty()) {
+ m_qnam.get(request);
+ } else {
+ request.setHeader(QNetworkRequest::ContentTypeHeader,
+ QVariant::fromValue(QStringLiteral("application/json")));
+ m_qnam.post(request, params.toJson(QJsonDocument::Compact));
+ }
+
+ bool ready = QTest::qWaitFor(
+ [&replyError, &replyString]() {
+ return replyError != QNetworkReply::NoError || !replyString.isEmpty();
+ },
+ 10000);
+
+ if (!ready) {
+ qWarning() << "Command" << command << "timed out.";
+ dumpConsoleMessages();
+ return false;
+ }
+
+ if (replyError != QNetworkReply::NoError) {
+ qWarning() << "Network error:" << replyError;
+ if (!replyString.isEmpty()) {
+ QJsonDocument errorReply = QJsonDocument::fromJson(replyString.toLatin1());
+ if (!errorReply.isNull()) {
+ QString error = errorReply["value"]["error"].toString();
+ QString message = errorReply["value"]["message"].toString();
+ if (!error.isEmpty() || message.isEmpty()) {
+ qWarning() << "error:" << error;
+ qWarning() << "message:" << message;
+ return false;
+ }
+ }
+
+ qWarning() << replyString;
+ return false;
+ }
+
+ dumpConsoleMessages();
+ return false;
+ }
+
+ if (result) {
+ if (replyString.isEmpty()) {
+ qWarning("Network reply is empty.");
+ return false;
+ }
+
+ QJsonParseError jsonError;
+ *result = QJsonDocument::fromJson(replyString.toLatin1(), &jsonError);
+
+ if (jsonError.error != QJsonParseError::NoError) {
+ qWarning() << "Unable to parse reply:" << jsonError.errorString();
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool createSession(QJsonObject chromeOptions = {}, QString *sessionId = nullptr)
+ {
+ if (!m_sessionId.isEmpty()) {
+ qWarning("A session already exists.");
+ return false;
+ }
+
+ QJsonObject root;
+
+ if (chromeOptions.isEmpty()) {
+ // Connect to the test by default.
+ chromeOptions.insert(
+ QLatin1String("debuggerAddress"),
+ QLatin1String("localhost:%1").arg(QString::number(REMOTE_DEBUGGING_PORT)));
+ chromeOptions.insert(QLatin1String("w3c"), true);
+ }
+
+ QJsonObject alwaysMatch;
+ alwaysMatch.insert(QLatin1String("goog:chromeOptions"), chromeOptions);
+
+ QJsonObject seOptions;
+ seOptions.insert(QLatin1String("loggingPrefs"), QJsonObject());
+ alwaysMatch.insert(QLatin1String("se:options"), seOptions);
+
+ QJsonObject capabilities;
+ capabilities.insert(QLatin1String("alwaysMatch"), alwaysMatch);
+ root.insert(QLatin1String("capabilities"), capabilities);
+
+ QJsonDocument params;
+ params.setObject(root);
+
+ QJsonDocument sessionReply;
+ bool sent = sendCommand(QLatin1String("/session"), params, &sessionReply);
+ if (sent) {
+ m_sessionId = sessionReply["value"]["sessionId"].toString();
+ if (sessionId)
+ *sessionId = m_sessionId;
+ }
+
+ return sent;
+ }
+
+ bool deleteSession()
+ {
+ if (m_sessionId.isEmpty()) {
+ qWarning("There is no active session.");
+ return false;
+ }
+
+ QNetworkReply::NetworkError replyError = QNetworkReply::NoError;
+ QString replyString;
+
+ connect(
+ &m_qnam, &QNetworkAccessManager::finished, this,
+ [&replyError, &replyString](QNetworkReply *reply) {
+ replyError = reply->error();
+ replyString = QString::fromUtf8(reply->readAll());
+ },
+ static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
+
+ QNetworkRequest request;
+ QUrl requestURL = m_serverURL;
+ requestURL.setPath(QLatin1String("/session/%1").arg(m_sessionId));
+ request.setUrl(requestURL);
+ m_qnam.deleteResource(request);
+
+ bool ready = QTest::qWaitFor(
+ [&replyError, &replyString]() {
+ return replyError != QNetworkReply::NoError || !replyString.isEmpty();
+ },
+ 10000);
+
+ if (!ready) {
+ qWarning("Deleting session timed out.");
+ return false;
+ }
+
+ if (replyError != QNetworkReply::NoError) {
+ qWarning() << "Network error:" << replyError;
+ return false;
+ }
+
+ return true;
+ }
+
+ QStringList stderrLines() const { return m_stderr; }
+
+ bool waitForMessageOnStderr(const QRegularExpression &re, QString *message = nullptr) const
+ {
+ return QTest::qWaitFor(
+ [this, &re, message]() {
+ for (QString line : m_stderr) {
+ if (line.contains(re)) {
+ if (message)
+ *message = line;
+ return true;
+ }
+ }
+
+ return false;
+ },
+ 10000);
+ }
+
+private:
+ void dumpConsoleMessages()
+ {
+ auto dumpLines = [](QStringList *lines) {
+ if (lines->empty())
+ return;
+
+ for (QString line : *lines)
+ qWarning() << qPrintable(line);
+
+ lines->clear();
+ };
+
+ dumpLines(&m_stdout);
+ dumpLines(&m_stderr);
+ }
+
+ QProcess m_process;
+ QStringList m_stdout;
+ QStringList m_stderr;
+
+ QUrl m_serverURL;
+ QString m_sessionId;
+
+ QNetworkAccessManager m_qnam;
+};
+
+class tst_WebEngineDriver : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void status();
+ void startBrowser();
+ void navigate();
+ void typeElement();
+ void executeScript();
+ void screenshot();
+};
+
+void tst_WebEngineDriver::status()
+{
+ DriverServer driverServer;
+ QVERIFY(driverServer.start());
+
+ QJsonDocument statusReply;
+ QVERIFY(driverServer.sendCommand(QLatin1String("/status"), {}, &statusReply));
+
+ QString versionString = statusReply["value"]["build"]["version"].toString();
+ QCOMPARE(qWebEngineChromiumVersion(), versionString.split(QLatin1Char(' '))[0]);
+
+ bool ready = statusReply["value"]["ready"].toBool();
+ QVERIFY(ready);
+}
+
+void tst_WebEngineDriver::startBrowser()
+{
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ env.insert(QLatin1String("QTWEBENGINE_REMOTE_DEBUGGING"),
+ QString::number(REMOTE_DEBUGGING_PORT));
+
+ QStringList args;
+ args << QLatin1String("--port=%1").arg(QString::number(WEBENGINEDRIVER_PORT));
+ args << QLatin1String("--log-level=ALL");
+
+ DriverServer driverServer(env, args);
+ QVERIFY(driverServer.start());
+
+ QString testBrowserPath =
+ QCoreApplication::applicationDirPath() + QLatin1String("/testbrowser");
+#if defined(Q_OS_WIN)
+ testBrowserPath += QLatin1String(".exe");
+#endif
+
+ QJsonArray chromeArgs;
+ chromeArgs.append(QLatin1String("enable-logging=stderr"));
+ chromeArgs.append(QLatin1String("v=1"));
+ // To force graceful shutdown.
+ chromeArgs.append(QLatin1String("log-net-log"));
+
+ QJsonObject chromeOptions;
+ chromeOptions.insert(QLatin1String("binary"), testBrowserPath);
+ chromeOptions.insert(QLatin1String("args"), chromeArgs);
+ chromeOptions.insert(QLatin1String("w3c"), true);
+ QString sessionId;
+ QVERIFY(driverServer.createSession(chromeOptions, &sessionId));
+
+ // Test if the browser is started.
+ QVERIFY(driverServer.waitForMessageOnStderr(QRegularExpression(
+ QLatin1String("^DevTools listening on ws://127.0.0.1:%1/devtools/browser/")
+ .arg(QString::number(REMOTE_DEBUGGING_PORT)))));
+ QVERIFY(driverServer.waitForMessageOnStderr(QRegularExpression(
+ QLatin1String("^Remote debugging server started successfully. "
+ "Try pointing a Chromium-based browser to http://127.0.0.1:%1")
+ .arg(QString::number(REMOTE_DEBUGGING_PORT)))));
+
+ // Check custom chromeArgs via logging.
+ QVERIFY(driverServer.waitForMessageOnStderr(QRegularExpression(QLatin1String("VERBOSE1"))));
+
+ QJsonDocument statusReply;
+ QVERIFY(driverServer.sendCommand(QLatin1String("/status"), {}, &statusReply));
+ bool ready = statusReply["value"]["ready"].toBool();
+ QVERIFY(ready);
+
+ QVERIFY(driverServer.deleteSession());
+
+ // Test if the browser is stopped.
+ QVERIFY(driverServer.waitForMessageOnStderr(
+ QRegularExpression(QLatin1String("Test browser is about to quit."))));
+ QVERIFY(driverServer.waitForMessageOnStderr(
+ QRegularExpression(QLatin1String("\\[%1\\] RESPONSE Quit").arg(sessionId))));
+}
+
+void tst_WebEngineDriver::navigate()
+{
+ DriverServer driverServer;
+ QVERIFY(driverServer.start());
+
+ QWebEnginePage page;
+ QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished);
+
+ page.load(QUrl(QLatin1String("about:blank")));
+ QTRY_COMPARE(loadSpy.size(), 1);
+
+ QVERIFY(driverServer.createSession());
+
+ QUrl url = QUrl(QLatin1String("qrc:///resources/input.html"));
+ QString paramsString = QLatin1String("{\"url\": \"%1\"}").arg(url.toString());
+ QJsonDocument params = QJsonDocument::fromJson(paramsString.toUtf8());
+ QVERIFY(driverServer.sendCommand(QLatin1String("/session/:sessionId/url"), params));
+ QTRY_COMPARE(loadSpy.size(), 2);
+ QVERIFY(loadSpy.at(1).at(0).toBool());
+ QCOMPARE(url, page.url());
+
+ QVERIFY(driverServer.deleteSession());
+}
+
+void tst_WebEngineDriver::typeElement()
+{
+ DriverServer driverServer;
+ QVERIFY(driverServer.start());
+
+ QWebEngineView view;
+ view.resize(300, 100);
+ view.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
+
+ QUrl url = QUrl(QLatin1String("qrc:///resources/input.html"));
+ QSignalSpy loadSpy(view.page(), &QWebEnginePage::loadFinished);
+ view.load(url);
+ QTRY_COMPARE(loadSpy.size(), 1);
+
+ QVERIFY(driverServer.createSession());
+
+ // Find <input id="text_input"> element and extract its id from the reply.
+ QString textInputId;
+ {
+ QString paramsString = QLatin1String(
+ "{\"using\": \"css selector\", \"value\": \"[id=\\\"text_input\\\"]\"}");
+ QJsonDocument params = QJsonDocument::fromJson(paramsString.toUtf8());
+ QJsonDocument elementReply;
+ QVERIFY(driverServer.sendCommand(QLatin1String("/session/:sessionId/element"), params,
+ &elementReply));
+
+ QVERIFY(!elementReply.isEmpty());
+ QJsonObject value = elementReply["value"].toObject();
+ QVERIFY(!value.isEmpty());
+ QStringList keys = value.keys();
+ QCOMPARE(keys.size(), 1);
+ textInputId = value[keys[0]].toString();
+ QVERIFY(!textInputId.isEmpty());
+ }
+
+ // Type text into the input field.
+ QString inputText = QLatin1String("WebEngineDriver");
+ {
+ QString command = QLatin1String("/session/:sessionId/element/%1/value").arg(textInputId);
+
+ QJsonObject root;
+ root.insert(QLatin1String("text"), inputText);
+ QJsonArray value;
+ for (QChar ch : inputText) {
+ value.append(QJsonValue(ch));
+ }
+ root.insert(QLatin1String("value"), value);
+ root.insert(QLatin1String("id"), textInputId);
+ QJsonDocument params;
+ params.setObject(root);
+
+ QVERIFY(driverServer.sendCommand(command, params));
+
+ QTRY_COMPARE(
+ evaluateJavaScriptSync(view.page(), "document.getElementById('text_input').value")
+ .toString(),
+ inputText);
+ }
+
+ QVERIFY(driverServer.deleteSession());
+}
+
+void tst_WebEngineDriver::executeScript()
+{
+ DriverServer driverServer;
+ QVERIFY(driverServer.start());
+
+ QUrl url = QUrl(QLatin1String("qrc:///resources/input.html"));
+ QWebEnginePage page;
+ QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished);
+
+ page.load(url);
+ QTRY_COMPARE(loadSpy.size(), 1);
+ QCOMPARE(page.title(), url.toString());
+ QSignalSpy titleSpy(&page, &QWebEnginePage::titleChanged);
+
+ QString newTitle = QLatin1String("WebEngineDriver Test Page");
+ QString script = QLatin1String("document.title = '%1';"
+ "return document.title;")
+ .arg(newTitle);
+
+ QVERIFY(driverServer.createSession());
+ QString paramsString = QLatin1String("{\"script\": \"%1\", \"args\": []}").arg(script);
+ QJsonDocument params = QJsonDocument::fromJson(paramsString.toUtf8());
+ QJsonDocument executeReply;
+ QVERIFY(driverServer.sendCommand(QLatin1String("/session/:sessionId/execute/sync"), params,
+ &executeReply));
+
+ QTRY_COMPARE(titleSpy.size(), 1);
+ QCOMPARE(executeReply["value"].toString(), newTitle);
+ QCOMPARE(page.title(), newTitle);
+
+ QVERIFY(driverServer.deleteSession());
+}
+
+void tst_WebEngineDriver::screenshot()
+{
+ DriverServer driverServer;
+ QVERIFY(driverServer.start());
+
+ QWebEngineView view;
+ view.resize(300, 100);
+ view.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
+
+ QSignalSpy loadSpy(view.page(), &QWebEnginePage::loadFinished);
+
+ view.setHtml(QLatin1String("<html><head><style>"
+ "html {background-color:red;}"
+ "</style></head><body></body></html>"));
+ QTRY_COMPARE(loadSpy.size(), 1);
+
+ QVERIFY(driverServer.createSession());
+ QJsonDocument screenshotReply;
+ QVERIFY(driverServer.sendCommand(QLatin1String("/session/:sessionId/screenshot"), {},
+ &screenshotReply));
+
+ QByteArray base64 = screenshotReply["value"].toString().toLocal8Bit();
+ QVERIFY(!base64.isEmpty());
+ QImage screenshot;
+ screenshot.loadFromData(QByteArray::fromBase64(base64));
+ QVERIFY(!screenshot.isNull());
+ QCOMPARE(screenshot.pixel(screenshot.width() / 2, screenshot.height() / 2), 0xFFFF0000);
+
+ QVERIFY(driverServer.deleteSession());
+}
+
+#define STRINGIFY_LITERAL(x) #x
+#define STRINGIFY_EXPANDED(x) STRINGIFY_LITERAL(x)
+static QByteArrayList params = QByteArrayList()
+ << "--remote-debugging-port=" STRINGIFY_EXPANDED(REMOTE_DEBUGGING_PORT);
+W_QTEST_MAIN(tst_WebEngineDriver, params)
+
+#include "tst_webenginedriver.moc"