diff options
Diffstat (limited to 'tests/auto/core')
62 files changed, 3660 insertions, 472 deletions
diff --git a/tests/auto/core/CMakeLists.txt b/tests/auto/core/CMakeLists.txt index ecb3b2cf9..eb8e9266f 100644 --- a/tests/auto/core/CMakeLists.txt +++ b/tests/auto/core/CMakeLists.txt @@ -1,10 +1,26 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + add_subdirectory(qwebenginecookiestore) +add_subdirectory(qwebengineframe) +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 index 57801e195..6223ca25c 100644 --- a/tests/auto/core/certificateerror/CMakeLists.txt +++ b/tests/auto/core/certificateerror/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + include(../../httpserver/httpserver.cmake) include(../../util/util.cmake) diff --git a/tests/auto/core/certificateerror/tst_certificateerror.cpp b/tests/auto/core/certificateerror/tst_certificateerror.cpp index 3c43d997f..61201e250 100644 --- a/tests/auto/core/certificateerror/tst_certificateerror.cpp +++ b/tests/auto/core/certificateerror/tst_certificateerror.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** 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> @@ -44,6 +19,7 @@ private Q_SLOTS: void handleError_data(); void handleError(); void fatalError(); + void resourceError(); }; struct PageWithCertificateErrorHandler : QWebEnginePage @@ -92,8 +68,8 @@ void tst_CertificateError::handleError_data() void tst_CertificateError::handleError() { - HttpsServer server(":/resources/server.pem",":/resources/server.key"); - server.setExpectError(true); + HttpsServer server(":/resources/server.pem", ":/resources/server.key", ""); + server.setExpectError(false); QVERIFY(server.start()); connect(&server, &HttpsServer::newRequest, [&] (HttpReqRep *rr) { @@ -117,7 +93,7 @@ void tst_CertificateError::handleError() 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.count(), 0); + QCOMPARE(page.loadSpy.size(), 0); QCOMPARE(toPlainTextSync(&page), QString()); if (acceptCertificate) @@ -127,9 +103,10 @@ void tst_CertificateError::handleError() page.error.reset(); } - QTRY_COMPARE_WITH_TIMEOUT(page.loadSpy.count(), 1, 30000); + 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() @@ -154,5 +131,20 @@ void tst_CertificateError::fatalError() } } +void tst_CertificateError::resourceError() +{ + PageWithCertificateErrorHandler page(false, false); + page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); + + page.setHtml("<img src=\"https://expired.badssl.com\">"); + if (!page.loadSpy.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."); + } + + QTRY_VERIFY(page.error); + QCOMPARE(page.error->isMainFrame(), false); +} + QTEST_MAIN(tst_CertificateError) #include <tst_certificateerror.moc> diff --git a/tests/auto/core/devtools/CMakeLists.txt b/tests/auto/core/devtools/CMakeLists.txt index fd8d850f0..efde75240 100644 --- a/tests/auto/core/devtools/CMakeLists.txt +++ b/tests/auto/core/devtools/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + qt_internal_add_test(tst_devtools SOURCES tst_devtools.cpp diff --git a/tests/auto/core/devtools/tst_devtools.cpp b/tests/auto/core/devtools/tst_devtools.cpp index 3026b3931..57a2b83a3 100644 --- a/tests/auto/core/devtools/tst_devtools.cpp +++ b/tests/auto/core/devtools/tst_devtools.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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) 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> @@ -46,7 +21,10 @@ void tst_DevTools::attachAndDestroyPageFirst() QSignalSpy spy(page, &QWebEnginePage::loadFinished); page->load(QUrl("data:text/plain,foobarbaz")); - QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 12000); + 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); @@ -62,12 +40,16 @@ 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.count(), 1, 12000); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 12000); page->triggerAction(QWebEnginePage::InspectElement); 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 index 79b8278a7..306074994 100644 --- a/tests/auto/core/origins/CMakeLists.txt +++ b/tests/auto/core/origins/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + include(../../httpserver/httpserver.cmake) include(../../util/util.cmake) @@ -6,6 +9,7 @@ qt_internal_add_test(tst_origins tst_origins.cpp LIBRARIES Qt::WebEngineCore + Qt::WebEngineWidgets Test::HttpServer Test::Util ) @@ -29,6 +33,8 @@ set(tst_origins_resource_files "resources/viewSource.html" "resources/websocket.html" "resources/websocket2.html" + "resources/red.png" + "resources/fetchApi.html" ) qt_internal_add_resource(tst_origins "tst_origins" 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 index c73e9ecdc..3e50c2c3b 100644 --- a/tests/auto/core/origins/resources/mixedSchemes.html +++ b/tests/auto/core/origins/resources/mixedSchemes.html @@ -6,23 +6,34 @@ var result; var canary; - function setIFrameUrl(url) { + function setIFrameUrl(frameUrl,imgUrl) { 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); + 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) - result = "canLoadAndAccess"; - else - result = "canLoadButNotAccess"; + 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> diff --git a/tests/auto/core/origins/resources/mixedSchemes_frame.html b/tests/auto/core/origins/resources/mixedSchemes_frame.html index 00c20ba37..9499caa1f 100644 --- a/tests/auto/core/origins/resources/mixedSchemes_frame.html +++ b/tests/auto/core/origins/resources/mixedSchemes_frame.html @@ -3,8 +3,12 @@ <head> <title>Mixed - Frame</title> <script> - var canary = true; - parent.canary = true; + try{ + var canary = true; + parent.canary = true; + }catch(exception){ + }; + </script> </head> <body></body> diff --git a/tests/auto/core/origins/resources/red.png b/tests/auto/core/origins/resources/red.png Binary files differnew file mode 100644 index 000000000..5ae85192b --- /dev/null +++ b/tests/auto/core/origins/resources/red.png diff --git a/tests/auto/core/origins/resources/redirect.css b/tests/auto/core/origins/resources/redirect.css index 41d7560cc..a6a03d8f5 100644 --- a/tests/auto/core/origins/resources/redirect.css +++ b/tests/auto/core/origins/resources/redirect.css @@ -1,8 +1,3 @@ -@font-face { - font-family: 'MyWebFont'; - src: url('redirect1:/resources/Akronim-Regular.woff2') format('woff2'); -} - body { - font-family: 'MyWebFont', Fallback, sans-serif; + font-family: serif; } diff --git a/tests/auto/core/origins/resources/redirect.html b/tests/auto/core/origins/resources/redirect.html index 04948e14b..603cb76f0 100644 --- a/tests/auto/core/origins/resources/redirect.html +++ b/tests/auto/core/origins/resources/redirect.html @@ -2,7 +2,14 @@ <html> <head> <title>redirect</title> - <link rel="stylesheet" href="redirect1:/resources/redirect.css"> + <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 diff --git a/tests/auto/core/origins/tst_origins.cpp b/tests/auto/core/origins/tst_origins.cpp index 518b20045..81385701f 100644 --- a/tests/auto/core/origins/tst_origins.cpp +++ b/tests/auto/core/origins/tst_origins.cpp @@ -1,42 +1,19 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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) 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> @@ -48,6 +25,8 @@ #define QSL QStringLiteral #define QBAL QByteArrayLiteral +Q_LOGGING_CATEGORY(lc, "qt.webengine.tests") + void registerSchemes() { { @@ -125,14 +104,20 @@ void registerSchemes() } { - QWebEngineUrlScheme scheme(QBAL("redirect1")); + QWebEngineUrlScheme scheme(QBAL("redirect")); scheme.setFlags(QWebEngineUrlScheme::CorsEnabled); QWebEngineUrlScheme::registerScheme(scheme); } { - QWebEngineUrlScheme scheme(QBAL("redirect2")); - scheme.setFlags(QWebEngineUrlScheme::CorsEnabled); + 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); } @@ -141,7 +126,40 @@ void registerSchemes() 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) @@ -165,9 +183,15 @@ public: profile->installUrlSchemeHandler(QBAL("HostSyntax-ContentSecurityPolicyIgnored"), this); profile->installUrlSchemeHandler(QBAL("HostAndPortSyntax"), this); profile->installUrlSchemeHandler(QBAL("HostPortAndUserInformationSyntax"), this); - profile->installUrlSchemeHandler(QBAL("redirect1"), this); - profile->installUrlSchemeHandler(QBAL("redirect2"), 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; } @@ -178,18 +202,24 @@ private: QUrl url = job->requestUrl(); m_requests << url; - if (url.scheme() == QBAL("redirect1")) { - url.setScheme(QBAL("redirect2")); - job->redirect(url); - return; + 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(); - QFile *file = new QFile(pathPrefix + pathSuffix, job); + 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; } @@ -198,12 +228,69 @@ private: mimeType = QBAL("application/javascript"); else if (pathSuffix.endsWith(QSL(".css"))) mimeType = QBAL("text/css"); - job->reply(mimeType, file); + 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 @@ -220,10 +307,17 @@ private Q_SLOTS: 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 @@ -232,7 +326,17 @@ private Q_SLOTS: void serviceWorker(); void viewSource(); void createObjectURL(); - void redirect(); + 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) @@ -249,7 +353,7 @@ private: } QWebEngineProfile m_profile; - QWebEnginePage *m_page = nullptr; + TestPage *m_page = nullptr; TstUrlSchemeHandler *m_handler = nullptr; }; @@ -270,7 +374,7 @@ void tst_Origins::cleanupTestCase() void tst_Origins::init() { - m_page = new QWebEnginePage(&m_profile, nullptr); + m_page = new TestPage(&m_profile); } void tst_Origins::cleanup() @@ -370,8 +474,8 @@ void tst_Origins::jsUrlRelative() // 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()); - QCOMPARE(eval(QSL("new URL('bar', 'qrc:///foo').href")), QVariant()); + 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"))); @@ -488,8 +592,6 @@ void tst_Origins::subdirWithoutAccess() { ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, false); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + "/resources/subdir/index.html")); QCOMPARE(eval(QSL("msg[0]")), QVariant()); @@ -507,27 +609,112 @@ void tst_Origins::subdirWithoutAccess() void tst_Origins::fileAccessRemoteUrl_data() { QTest::addColumn<bool>("EnableAccess"); - QTest::addRow("enabled") << true; - QTest::addRow("disabled") << false; + 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 sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessRemoteUrls, EnableAccess); - if (!EnableAccess) - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("blocked by CORS policy"))); + ScopedAttribute sa1(m_page->settings(), QWebEngineSettings::LocalContentCanAccessRemoteUrls, EnableAccess); + ScopedAttribute sa2(m_page->settings(), QWebEngineSettings::ErrorPageEnabled, false); - QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() - + "/resources/mixedXHR.html")); + 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(); - eval("sendXHR('" + server.url("/mixedXHR.txt").toString() + "')"); - QTRY_COMPARE(eval("result"), (EnableAccess ? QString("ok") : QString("error"))); + 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. @@ -538,105 +725,149 @@ void tst_Origins::fileAccessRemoteUrl() // 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() +void tst_Origins::mixedSchemes_data() { - QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() - + "/resources/mixedSchemes.html")); - eval("setIFrameUrl('file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() - + "/resources/mixedSchemes_frame.html')"); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - - QVERIFY(verifyLoad(QSL("qrc:/resources/mixedSchemes.html"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Not allowed to load local resource"))); - eval("setIFrameUrl('file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() - + "/resources/mixedSchemes_frame.html')"); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); - eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - - QVERIFY(verifyLoad(QSL("tst:/resources/mixedSchemes.html"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Not allowed to load local resource"))); - eval("setIFrameUrl('file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() - + "/resources/mixedSchemes_frame.html')"); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - - QVERIFY(verifyLoad(QSL("PathSyntax:/resources/mixedSchemes.html"))); - eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Not allowed to load local resource"))); - eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); - eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + 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; + } +} - QVERIFY(verifyLoad(QSL("PathSyntax-LocalAccessAllowed:/resources/mixedSchemes.html"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); +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, "" }; +} - QVERIFY(verifyLoad(QSL("PathSyntax-NoAccessAllowed:/resources/mixedSchemes.html"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Not allowed to load local resource"))); - eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); +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)); + } - QVERIFY(verifyLoad(QSL("HostSyntax://a/resources/mixedSchemes.html"))); - eval(QSL("setIFrameUrl('HostSyntax://a/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('HostSyntax://b/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + 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"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("violates the following Content Security Policy"))); eval(QSL("setIFrameUrl('HostSyntax://a/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("violates the following Content Security Policy"))); 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"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); eval(QSL("setIFrameUrl('HostSyntax-ContentSecurityPolicyIgnored://b/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); } @@ -649,79 +880,325 @@ void tst_Origins::mixedSchemesWithCsp() // schemes with the CorsEnabled flag. void tst_Origins::mixedXHR_data() { - QTest::addColumn<QString>("url"); - QTest::addColumn<QString>("command"); - QTest::addColumn<QVariant>("result"); - QTest::newRow("file->file") << QString("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() - + "/resources/mixedXHR.html") - << QString("sendXHR('file:" - + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() - + "/resources/mixedXHR.txt')") - << QVariant(QString("ok")); - QTest::newRow("file->qrc") << QString("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() - + "/resources/mixedXHR.html") - << QString("sendXHR('qrc:/resources/mixedXHR.txt')") - << QVariant(QString("error")); - QTest::newRow("file->tst") << QString("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() - + "/resources/mixedXHR.html") - << QString("sendXHR('tst:/resources/mixedXHR.txt')") - << QVariant(QString("error")); - QTest::newRow("file->data") << QString("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() - + "/resources/mixedXHR.html") - << QString("sendXHR('data:,ok')") << QVariant(QString("ok")); - QTest::newRow("file->cors") << QString("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() - + "/resources/mixedXHR.html") - << QString("sendXHR('cors:/resources/mixedXHR.txt')") - << QVariant(QString("ok")); - - QTest::newRow("qrc->file") << QString("qrc:/resources/mixedXHR.html") - << QString("sendXHR('file:" - + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() - + "/resources/mixedXHR.txt')") - << QVariant(QString("error")); - QTest::newRow("qrc->qrc") << QString("qrc:/resources/mixedXHR.html") - << QString("sendXHR('qrc:/resources/mixedXHR.txt')") - << QVariant(QString("ok")); - QTest::newRow("qrc->tst") << QString("qrc:/resources/mixedXHR.html") - << QString("sendXHR('tst:/resources/mixedXHR.txt')") - << QVariant(QString("error")); - QTest::newRow("qrc->data") << QString("qrc:/resources/mixedXHR.html") - << QString("sendXHR('data:,ok')") - << QVariant(QString("ok")); - QTest::newRow("qrc->cors") << QString("qrc:/resources/mixedXHR.html") - << QString("sendXHR('cors:/resources/mixedXHR.txt')") - << QVariant(QString("ok")); - - QTest::newRow("tst->file") << QString("tst:/resources/mixedXHR.html") - << QString("sendXHR('file:" - + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() - + "/resources/mixedXHR.txt')") - << QVariant(QString("error")); - QTest::newRow("tst->qrc") << QString("tst:/resources/mixedXHR.html") - << QString("sendXHR('qrc:/resources/mixedXHR.txt')") - << QVariant(QString("error")); - QTest::newRow("tst->tst") << QString("tst:/resources/mixedXHR.html") - << QString("sendXHR('tst:/resources/mixedXHR.txt')") - << QVariant(QString("ok")); - QTest::newRow("tst->data") << QString("tst:/resources/mixedXHR.html") - << QString("sendXHR('data:,ok')") - << QVariant(QString("ok")); - QTest::newRow("tst->cors") << QString("tst:/resources/mixedXHR.html") - << QString("sendXHR('cors:/resources/mixedXHR.txt')") - << QVariant(QString("ok")); - + 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, url); - QFETCH(QString, command); - QFETCH(QVariant, result); + 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(' ')))); +} - QVERIFY(verifyLoad(url)); - eval(command); - QTRY_COMPARE(eval(QString("result")), result); +// 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) @@ -877,12 +1354,12 @@ void tst_Origins::serviceWorker() QVERIFY(verifyLoad(QSL("tst:/resources/serviceWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); QVERIFY(eval(QSL("error")).toString() - .contains(QSL("Cannot read property 'register' of undefined"))); + .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 property 'register' of undefined"))); + .contains(QSL("Cannot read properties of undefined"))); QVERIFY(verifyLoad(QSL("PathSyntax-Secure:/resources/serviceWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); @@ -892,7 +1369,7 @@ void tst_Origins::serviceWorker() QVERIFY(verifyLoad(QSL("PathSyntax-ServiceWorkersAllowed:/resources/serviceWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); QVERIFY(eval(QSL("error")).toString() - .contains(QSL("Cannot read property 'register' of undefined"))); + .contains(QSL("Cannot read properties of undefined"))); QVERIFY(verifyLoad(QSL("PathSyntax-Secure-ServiceWorkersAllowed:/resources/serviceWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); @@ -901,7 +1378,7 @@ void tst_Origins::serviceWorker() QVERIFY(verifyLoad(QSL("PathSyntax-NoAccessAllowed:/resources/serviceWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); QVERIFY(eval(QSL("error")).toString() - .contains(QSL("Cannot read property 'register' of undefined"))); + .contains(QSL("Cannot read properties of undefined"))); } // Support for view-source must be enabled explicitly. @@ -943,17 +1420,310 @@ void tst_Origins::createObjectURL() QVERIFY(eval(QSL("result")).toString().startsWith(QSL("blob:tst:"))); } -void tst_Origins::redirect() +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() { - QVERIFY(verifyLoad(QSL("redirect1:/resources/redirect.html"))); - QTRY_COMPARE(m_handler->requests().size(), 7); - QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("redirect1:/resources/redirect.html"))); - QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("redirect2:/resources/redirect.html"))); - QCOMPARE(m_handler->requests()[2], QUrl(QStringLiteral("redirect1:/resources/redirect.css"))); - QCOMPARE(m_handler->requests()[3], QUrl(QStringLiteral("redirect2:/resources/redirect.css"))); - QCOMPARE(m_handler->requests()[4], QUrl(QStringLiteral("redirect1:/resources/Akronim-Regular.woff2"))); - QCOMPARE(m_handler->requests()[5], QUrl(QStringLiteral("redirect1:/resources/Akronim-Regular.woff2"))); - QCOMPARE(m_handler->requests()[6], QUrl(QStringLiteral("redirect2:/resources/Akronim-Regular.woff2"))); + 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) 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 index fa2d5e538..8cee7f630 100644 --- a/tests/auto/core/qwebengineclientcertificatestore/CMakeLists.txt +++ b/tests/auto/core/qwebengineclientcertificatestore/CMakeLists.txt @@ -1,8 +1,16 @@ +# 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 @@ -10,6 +18,13 @@ set(tst_qwebengineclientcertificatestore_resource_files "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" @@ -19,3 +34,47 @@ qt_internal_add_resource(tst_qwebengineclientcertificatestore "tst_qwebenginecli ${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/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 Binary files differnew file mode 100644 index 000000000..81e7eb624 --- /dev/null +++ b/tests/auto/core/qwebengineclientcertificatestore/resources/client2.p12 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 f288a2c75..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 <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/qwebenginecookiestore/CMakeLists.txt b/tests/auto/core/qwebenginecookiestore/CMakeLists.txt index 33ba5ff1a..cc14940f1 100644 --- a/tests/auto/core/qwebenginecookiestore/CMakeLists.txt +++ b/tests/auto/core/qwebenginecookiestore/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + include(../../httpserver/httpserver.cmake) include(../../util/util.cmake) diff --git a/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp b/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp index a712fb288..3fff2cd45 100644 --- a/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp +++ b/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// 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> @@ -35,6 +10,10 @@ #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 @@ -55,6 +34,7 @@ private Q_SLOTS: // as it checks storage manipulation without navigation void setAndDeleteCookie(); + void setInvalidCookie(); void cookieSignals(); void batchCookieTasks(); void basicFilter(); @@ -104,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() @@ -140,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() @@ -185,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() @@ -224,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() @@ -280,25 +291,25 @@ void tst_QWebEngineCookieStore::basicFilterOverHTTP() 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(), 4); + QWE_TRY_COMPARE(cookieAddedSpy.size(), 1); + QWE_TRY_COMPARE(accessTested.loadAcquire(), 4); QVERIFY(cookieRequestHeader.isEmpty()); - QTRY_COMPARE(serverSpy.count(), 3); + 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(), 6); + QWE_TRY_COMPARE(cookieAddedSpy.size(), 1); + QWE_TRY_COMPARE(accessTested.loadAcquire(), 6); - QTRY_COMPARE(serverSpy.count(), 5); + QWE_TRY_COMPARE(serverSpy.size(), 5); client->deleteAllCookies(); - QTRY_COMPARE(cookieRemovedSpy.count(), 1); + QWE_TRY_COMPARE(cookieRemovedSpy.size(), 1); client->setCookieFilter([&](const QWebEngineCookieStore::FilterRequest &request) { resourceFirstParty.append(qMakePair(request.origin, request.firstPartyUrl)); @@ -306,28 +317,28 @@ void tst_QWebEngineCookieStore::basicFilterOverHTTP() 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(), 9); + QCOMPARE(cookieAddedSpy.size(), 1); + QWE_TRY_COMPARE(accessTested.loadAcquire(), 9); - QTRY_COMPARE(serverSpy.count(), 7); + 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(), 9); + QWE_TRY_COMPARE(serverSpy.size(), 9); (void) httpServer.stop(); QCOMPARE(resourceFirstParty.size(), accessTested.loadAcquire()); - for (auto &&p : qAsConst(resourceFirstParty)) + 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()))); } @@ -344,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/qwebengineframe/CMakeLists.txt b/tests/auto/core/qwebengineframe/CMakeLists.txt new file mode 100644 index 000000000..d02b4307d --- /dev/null +++ b/tests/auto/core/qwebengineframe/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../util/util.cmake) + +qt_internal_add_test(tst_qwebengineframe + SOURCES + tst_qwebengineframe.cpp + LIBRARIES + Qt::WebEngineCore + Qt::WebEngineWidgets + Test::Util +) + +qt_internal_add_resource(tst_qwebengineframe "tst_qwebengineframe" + PREFIX + "/" + FILES + "resources/frameset.html" + "resources/iframes.html" + "resources/nesting-iframe.html" +) diff --git a/tests/auto/core/qwebengineframe/resources/frameset.html b/tests/auto/core/qwebengineframe/resources/frameset.html new file mode 100644 index 000000000..53f5e6638 --- /dev/null +++ b/tests/auto/core/qwebengineframe/resources/frameset.html @@ -0,0 +1,20 @@ +<!doctype html> +<html> + <head><title>Test-title</title></head> + <script> + window.name = 'test-main-frame' + onload = (e) => { + const frames = window.frames; + for (let i = 0; i < frames.length; i++) { + frames[i].name = 'test-subframe' + i; + } + }; + </script> + <frameset cols="50%, 50%"> + <frameset cols="50%, 50%"> + <frame style="border: red dashed 1em;"/> + <frame style="border: green dashed 1em;"/> + </frameset> + <frame style="border: blue solid 1em;"/> + </frameset> +</html> diff --git a/tests/auto/core/qwebengineframe/resources/iframes.html b/tests/auto/core/qwebengineframe/resources/iframes.html new file mode 100644 index 000000000..648acb166 --- /dev/null +++ b/tests/auto/core/qwebengineframe/resources/iframes.html @@ -0,0 +1,17 @@ +<!doctype html> +<html> + <head><title>Test-title</title></head> + <script> + window.name = 'test-main-frame' + onload = (e) => { + const frames = window.frames; + for (let i = 0; i < frames.length; i++) { + frames[i].name = 'test-subframe' + i; + } + }; + </script> + <body> + <iframe name="iframe0-300x200" width="300" height="200"></iframe> + <iframe name="iframe1-350x250" width="350" height="250"></iframe> + </body> +</html> diff --git a/tests/auto/core/qwebengineframe/resources/nesting-iframe.html b/tests/auto/core/qwebengineframe/resources/nesting-iframe.html new file mode 100644 index 000000000..cd784e3dd --- /dev/null +++ b/tests/auto/core/qwebengineframe/resources/nesting-iframe.html @@ -0,0 +1,7 @@ +<!doctype html> +<html> + <head><title>Test-title</title></head> + <body> + <iframe name="iframe2-parent" src="iframes.html"></iframe> + </body> +</html> diff --git a/tests/auto/core/qwebengineframe/tst_qwebengineframe.cpp b/tests/auto/core/qwebengineframe/tst_qwebengineframe.cpp new file mode 100644 index 000000000..7cd075443 --- /dev/null +++ b/tests/auto/core/qwebengineframe/tst_qwebengineframe.cpp @@ -0,0 +1,194 @@ +/* + Copyright (C) 2024 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 <QtWebEngineCore/qwebengineframe.h> + +class tst_QWebEngineFrame : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void mainFrame(); + void findFrameByName(); + void isValid(); + void name(); + void htmlName(); + void children(); + void childrenOfInvalidFrame(); + void url(); + void size(); + void runJavaScript(); + +private: +}; + +void tst_QWebEngineFrame::mainFrame() +{ + QWebEnginePage page; + QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) }; + page.load(QUrl("qrc:/resources/frameset.html")); + QTRY_COMPARE(loadSpy.size(), 1); + auto frame = page.mainFrame(); + QVERIFY(frame.isValid()); +} + +void tst_QWebEngineFrame::findFrameByName() +{ + QWebEnginePage page; + QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) }; + page.load(QUrl("qrc:/resources/iframes.html")); + QTRY_COMPARE(loadSpy.size(), 1); + auto maybeFrame = page.findFrameByName("test-subframe0"); + QVERIFY(maybeFrame.has_value()); + QCOMPARE(maybeFrame->name(), "test-subframe0"); + QVERIFY(!page.findFrameByName("foobar").has_value()); +} + +void tst_QWebEngineFrame::isValid() +{ + QWebEnginePage page; + QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) }; + page.load(QUrl("qrc:/resources/iframes.html")); + QTRY_COMPARE(loadSpy.size(), 1); + auto firstPageSubframe = page.findFrameByName("test-subframe0"); + QVERIFY(firstPageSubframe && firstPageSubframe->isValid()); + + page.load(QUrl("qrc:/resources/frameset.html")); + QTRY_COMPARE(loadSpy.size(), 2); + QVERIFY(!firstPageSubframe->isValid()); +} + +void tst_QWebEngineFrame::name() +{ + QWebEnginePage page; + QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) }; + page.load(QUrl("qrc:/resources/frameset.html")); + QTRY_COMPARE(loadSpy.size(), 1); + QCOMPARE(page.mainFrame().name(), "test-main-frame"); + auto children = page.mainFrame().children(); + QCOMPARE(children.at(0).name(), "test-subframe0"); + QCOMPARE(children.at(1).name(), "test-subframe1"); + QCOMPARE(children.at(2).name(), "test-subframe2"); + + page.load(QUrl("qrc:/resources/iframes.html")); + QTRY_COMPARE(loadSpy.size(), 2); + QVERIFY(!children.at(0).isValid()); + QCOMPARE(children.at(0).name(), QString()); +} + +void tst_QWebEngineFrame::htmlName() +{ + QWebEnginePage page; + QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) }; + page.load(QUrl("qrc:/resources/iframes.html")); + QTRY_COMPARE(loadSpy.size(), 1); + auto children = page.mainFrame().children(); + QCOMPARE(children.at(0).name(), "test-subframe0"); + QCOMPARE(children.at(0).htmlName(), "iframe0-300x200"); + QCOMPARE(children.at(1).name(), "test-subframe1"); + QCOMPARE(children.at(1).htmlName(), "iframe1-350x250"); + + page.load(QUrl("qrc:/resources/frameset.html")); + QTRY_COMPARE(loadSpy.size(), 2); + QVERIFY(!children.at(0).isValid()); + QCOMPARE(children.at(0).htmlName(), QString()); +} + +void tst_QWebEngineFrame::children() +{ + QWebEnginePage page; + QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) }; + page.load(QUrl("qrc:/resources/frameset.html")); + QTRY_COMPARE(loadSpy.size(), 1); + auto frame = page.mainFrame(); + auto children = frame.children(); + QCOMPARE(children.size(), 3); + for (auto child : children) { + QVERIFY(child.isValid()); + } +} + +void tst_QWebEngineFrame::childrenOfInvalidFrame() +{ + QWebEnginePage page; + QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) }; + page.load(QUrl("qrc:/resources/nesting-iframe.html")); + QTRY_COMPARE(loadSpy.size(), 1); + auto nestedFrame = page.mainFrame().children().at(0); + QCOMPARE(nestedFrame.children().size(), 2); + + page.load(QUrl("qrc:/resources/frameset.html")); + QTRY_COMPARE(loadSpy.size(), 2); + QVERIFY(!nestedFrame.isValid()); + QVERIFY(nestedFrame.children().empty()); +} + +void tst_QWebEngineFrame::url() +{ + QWebEnginePage page; + QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) }; + page.load(QUrl("qrc:/resources/nesting-iframe.html")); + QTRY_COMPARE(loadSpy.size(), 1); + auto children = page.mainFrame().children(); + QCOMPARE(children.at(0).url(), QUrl("qrc:/resources/iframes.html")); + + page.load(QUrl("qrc:/resources/frameset.html")); + QTRY_COMPARE(loadSpy.size(), 2); + QVERIFY(!children.at(0).isValid()); + QCOMPARE(children.at(0).url(), QUrl()); +} + +void tst_QWebEngineFrame::size() +{ + QWebEnginePage page; + QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) }; + page.load(QUrl("qrc:/resources/iframes.html")); + QTRY_COMPARE(loadSpy.size(), 1); + auto frame1 = *page.findFrameByName("test-subframe0"); + auto size1 = frame1.size(); + auto size2 = page.findFrameByName("test-subframe1")->size(); + QCOMPARE(size1, QSizeF(300, 200)); + QCOMPARE(size2, QSizeF(350, 250)); + + page.load(QUrl("qrc:/resources/frameset.html")); + QTRY_COMPARE(loadSpy.size(), 2); + QVERIFY(!frame1.isValid()); + QCOMPARE(frame1.size(), QSizeF()); +} + +void tst_QWebEngineFrame::runJavaScript() +{ + QWebEnginePage page; + QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) }; + page.load(QUrl("qrc:/resources/iframes.html")); + QTRY_COMPARE(loadSpy.size(), 1); + auto children = page.mainFrame().children(); + CallbackSpy<QVariant> spy; + children[0].runJavaScript("window.name", spy.ref()); + auto result = spy.waitForResult(); + QCOMPARE(result, QString("test-subframe0")); +} + +QTEST_MAIN(tst_QWebEngineFrame) + +#include "tst_qwebengineframe.moc" 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/CMakeLists.txt b/tests/auto/core/qwebenginesettings/CMakeLists.txt index 7f8b49d1b..756b99bbb 100644 --- a/tests/auto/core/qwebenginesettings/CMakeLists.txt +++ b/tests/auto/core/qwebenginesettings/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + include(../../util/util.cmake) qt_internal_add_test(tst_qwebenginesettings @@ -5,5 +8,6 @@ qt_internal_add_test(tst_qwebenginesettings 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 index 4220f496b..e856dd094 100644 --- a/tests/auto/core/qwebenginesettings/tst_qwebenginesettings.cpp +++ b/tests/auto/core/qwebenginesettings/tst_qwebenginesettings.cpp @@ -27,6 +27,7 @@ #include <QtGui/qclipboard.h> #include <QtGui/qguiapplication.h> +#include <QtWebEngineWidgets/qwebengineview.h> class tst_QWebEngineSettings: public QObject { Q_OBJECT @@ -38,6 +39,10 @@ private Q_SLOTS: void javascriptClipboard_data(); void javascriptClipboard(); void setInAcceptNavigationRequest(); + void disableReadingFromCanvas_data(); + void disableReadingFromCanvas(); + void forceDarkMode(); + void forceDarkModeMultiView(); }; void tst_QWebEngineSettings::resetAttributes() @@ -143,7 +148,7 @@ void tst_QWebEngineSettings::javascriptClipboard() // - return value of queryCommandEnabled and // - return value of execCommand // - comparing the clipboard / input field - QGuiApplication::clipboard()->clear(); + QGuiApplication::clipboard()->setText(QString()); QCOMPARE(evaluateJavaScriptSync(&page, "document.queryCommandEnabled('copy')").toBool(), copyResult); QCOMPARE(evaluateJavaScriptSync(&page, "document.execCommand('copy')").toBool(), copyResult); @@ -193,6 +198,110 @@ void tst_QWebEngineSettings::setInAcceptNavigationRequest() 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 index 0f8d90a08..c12c0c45c 100644 --- a/tests/auto/core/qwebengineurlrequestinterceptor/CMakeLists.txt +++ b/tests/auto/core/qwebengineurlrequestinterceptor/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + include(../../util/util.cmake) include(../../httpserver/httpserver.cmake) @@ -6,12 +9,16 @@ qt_internal_add_test(tst_qwebengineurlrequestinterceptor 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" @@ -31,6 +38,7 @@ set(tst_qwebengineurlrequestinterceptor_resource_files "resources/style.css" "resources/sw.html" "resources/sw.js" + "resources/postBodyFile.txt" ) qt_internal_add_resource(tst_qwebengineurlrequestinterceptor "tst_qwebengineurlrequestinterceptor" 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 94669c4ca..7cea14c0c 100644 --- a/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp +++ b/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp @@ -1,38 +1,17 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// 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 <QtWebEngineCore/qwebenginesettings.h> #include <QtWebEngineCore/qwebengineprofile.h> #include <QtWebEngineCore/qwebenginepage.h> +#include <QtWebEngineCore/qwebenginehttprequest.h> #include <httpserver.h> #include <httpreqrep.h> @@ -64,12 +43,18 @@ private Q_SLOTS: void requestInterceptorByResourceType_data(); void requestInterceptorByResourceType(); void firstPartyUrlHttp(); + void headers(); void customHeaders(); void initiator(); 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() @@ -102,12 +87,14 @@ 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 QUrl kRedirectUrl = QUrl("qrc:///resources/content.html"); @@ -154,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) @@ -202,6 +189,29 @@ public: } }; +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() + { + } +}; + class ConsolePage : public QWebEnginePage { Q_OBJECT public: @@ -231,7 +241,7 @@ void tst_QWebEngineUrlRequestInterceptor::interceptRequest() 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(); @@ -239,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()); @@ -247,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.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 @@ -295,15 +305,15 @@ void tst_QWebEngineUrlRequestInterceptor::ipv6HostEncoding() 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")); } @@ -331,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")); @@ -340,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")); @@ -376,23 +386,23 @@ 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); + QCOMPARE(spy.size(), 4); } void tst_QWebEngineUrlRequestInterceptor::firstPartyUrl() @@ -406,12 +416,12 @@ void tst_QWebEngineUrlRequestInterceptor::firstPartyUrl() 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() @@ -444,21 +454,21 @@ void tst_QWebEngineUrlRequestInterceptor::firstPartyUrlNestedIframes() 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); @@ -534,11 +544,11 @@ void tst_QWebEngineUrlRequestInterceptor::requestInterceptorByResourceType() 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); @@ -602,6 +612,40 @@ void tst_QWebEngineUrlRequestInterceptor::firstPartyUrlHttp() QCOMPARE(info.firstPartyUrl, firstPartyUrl); } +void tst_QWebEngineUrlRequestInterceptor::headers() +{ + 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::customHeaders() { // Create HTTP Server to parse the request. @@ -726,8 +770,7 @@ void tst_QWebEngineUrlRequestInterceptor::jsServiceWorker() HttpServer server; 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); @@ -735,10 +778,17 @@ void tst_QWebEngineUrlRequestInterceptor::jsServiceWorker() 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() + "sw.js"); + 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)); @@ -804,7 +854,7 @@ void tst_QWebEngineUrlRequestInterceptor::replaceInterceptor() }); page.setUrl(server.url("/favicon.html")); - QTRY_COMPARE(spy.count(), 2); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 2, 20000); QTRY_VERIFY(fetchFinished); QString s; QDebug d(&s); @@ -848,7 +898,7 @@ void tst_QWebEngineUrlRequestInterceptor::replaceOnIntercept() }; page.setUrl(server.url("/favicon.html")); - QTRY_COMPARE(spy.count(), 1); + 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 @@ -869,5 +919,204 @@ void tst_QWebEngineUrlRequestInterceptor::replaceOnIntercept() 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/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/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 ¶ms = {}, + 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" |