diff options
Diffstat (limited to 'tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp')
-rw-r--r-- | tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp | 757 |
1 files changed, 559 insertions, 198 deletions
diff --git a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp index 3fc8fb45a..095d4c8f2 100644 --- a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp +++ b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp @@ -1,43 +1,30 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#include "../util.h" +// 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 <util.h> #include <QtCore/qbuffer.h> +#include <QtCore/qmimedatabase.h> #include <QtTest/QtTest> +#include <QtWebEngineCore/qwebengineurlrequestinterceptor.h> #include <QtWebEngineCore/qwebengineurlrequestjob.h> #include <QtWebEngineCore/qwebenginecookiestore.h> #include <QtWebEngineCore/qwebengineurlscheme.h> #include <QtWebEngineCore/qwebengineurlschemehandler.h> -#include <QtWebEngineWidgets/qwebengineprofile.h> -#include <QtWebEngineWidgets/qwebenginepage.h> -#include <QtWebEngineWidgets/qwebenginesettings.h> +#include <QtWebEngineCore/qwebenginesettings.h> +#include <QtWebEngineCore/qwebengineprofile.h> +#include <QtWebEngineCore/qwebenginepage.h> +#include <QtWebEngineCore/qwebenginedownloadrequest.h> #include <QtWebEngineWidgets/qwebengineview.h> -#include <QtWebEngineWidgets/qwebenginedownloaditem.h> + +#if QT_CONFIG(webengine_webchannel) +#include <QWebChannel> +#endif + +#include <httpserver.h> +#include <httpreqrep.h> + +#include <map> +#include <mutex> class tst_QWebEngineProfile : public QObject { @@ -45,24 +32,31 @@ class tst_QWebEngineProfile : public QObject private Q_SLOTS: void initTestCase(); - void init(); - void cleanup(); - void privateProfile(); - void testProfile(); + void defaultProfile_data(); + void defaultProfile(); + void userDefinedProfile(); void clearDataFromCache(); void disableCache(); void urlSchemeHandlers(); void urlSchemeHandlerFailRequest(); void urlSchemeHandlerFailOnRead(); void urlSchemeHandlerStreaming(); + void urlSchemeHandlerStreaming2(); + void urlSchemeHandlerRequestHeaders(); void urlSchemeHandlerInstallation(); + void urlSchemeHandlerXhrStatus(); + void urlSchemeHandlerScriptModule(); + void urlSchemeHandlerLongReply(); void customUserAgent(); void httpAcceptLanguage(); void downloadItem(); void changePersistentPath(); + void changeHttpUserAgent(); + void changeHttpAcceptLanguage(); + void changePersistentCookiesPolicy(); void initiator(); void badDeleteOrder(); - void qtbug_72299(); // this should be the last test + void qtbug_71895(); // this should be the last test }; void tst_QWebEngineProfile::initTestCase() @@ -71,149 +65,178 @@ void tst_QWebEngineProfile::initTestCase() QWebEngineUrlScheme stream("stream"); QWebEngineUrlScheme letterto("letterto"); QWebEngineUrlScheme aviancarrier("aviancarrier"); + QWebEngineUrlScheme myscheme("myscheme"); foo.setSyntax(QWebEngineUrlScheme::Syntax::Host); stream.setSyntax(QWebEngineUrlScheme::Syntax::HostAndPort); stream.setDefaultPort(8080); letterto.setSyntax(QWebEngineUrlScheme::Syntax::Path); aviancarrier.setSyntax(QWebEngineUrlScheme::Syntax::Path); + aviancarrier.setFlags(QWebEngineUrlScheme::CorsEnabled); QWebEngineUrlScheme::registerScheme(foo); QWebEngineUrlScheme::registerScheme(stream); QWebEngineUrlScheme::registerScheme(letterto); QWebEngineUrlScheme::registerScheme(aviancarrier); + QWebEngineUrlScheme::registerScheme(myscheme); } -void tst_QWebEngineProfile::init() -{ - //make sure defualt global profile is 'default' across all the tests - QWebEngineProfile *profile = QWebEngineProfile::defaultProfile(); - QVERIFY(profile); - QVERIFY(!profile->isOffTheRecord()); - QCOMPARE(profile->storageName(), QStringLiteral("Default")); - QCOMPARE(profile->httpCacheType(), QWebEngineProfile::DiskHttpCache); - QCOMPARE(profile->persistentCookiesPolicy(), QWebEngineProfile::AllowPersistentCookies); - QCOMPARE(profile->cachePath(), QStandardPaths::writableLocation(QStandardPaths::CacheLocation) - + QStringLiteral("/QtWebEngine/Default")); - QCOMPARE(profile->persistentStoragePath(), QStandardPaths::writableLocation(QStandardPaths::DataLocation) - + QStringLiteral("/QtWebEngine/Default")); -} +static QString StandardCacheLocation() { static auto p = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); return p; } +static QString StandardAppDataLocation() { static auto p = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); return p; } -void tst_QWebEngineProfile::cleanup() +void tst_QWebEngineProfile::defaultProfile_data() { - QWebEngineProfile *profile = QWebEngineProfile::defaultProfile(); - profile->setCachePath(QString()); - profile->setPersistentStoragePath(QString()); - profile->setHttpCacheType(QWebEngineProfile::DiskHttpCache); - profile->removeAllUrlSchemeHandlers(); + QTest::addColumn<bool>("userCreated"); + QTest::addRow("global") << false; + QTest::addRow("user") << true; } -void tst_QWebEngineProfile::privateProfile() +void tst_QWebEngineProfile::defaultProfile() { - QWebEngineProfile otrProfile; - QVERIFY(otrProfile.isOffTheRecord()); - QCOMPARE(otrProfile.httpCacheType(), QWebEngineProfile::MemoryHttpCache); - QCOMPARE(otrProfile.persistentCookiesPolicy(), QWebEngineProfile::NoPersistentCookies); - QCOMPARE(otrProfile.cachePath(), QString()); - QCOMPARE(otrProfile.persistentStoragePath(), QString()); + QFETCH(bool, userCreated); + QScopedPointer<QWebEngineProfile> p(userCreated ? new QWebEngineProfile : nullptr); + QWebEngineProfile *profile = userCreated ? p.get() : QWebEngineProfile::defaultProfile(); + QVERIFY(profile); + QVERIFY(profile->isOffTheRecord()); + QCOMPARE(profile->storageName(), QString()); + QCOMPARE(profile->httpCacheType(), QWebEngineProfile::MemoryHttpCache); + QCOMPARE(profile->persistentCookiesPolicy(), QWebEngineProfile::NoPersistentCookies); + QCOMPARE(profile->cachePath(), QString()); + QCOMPARE(profile->persistentStoragePath(), StandardAppDataLocation() + QStringLiteral("/QtWebEngine/OffTheRecord")); // TBD: setters do not really work - otrProfile.setCachePath(QStringLiteral("/home/foo/bar")); - QCOMPARE(otrProfile.cachePath(), QString()); - otrProfile.setPersistentStoragePath(QStringLiteral("/home/foo/bar")); - QCOMPARE(otrProfile.persistentStoragePath(), QString()); - otrProfile.setHttpCacheType(QWebEngineProfile::DiskHttpCache); - QCOMPARE(otrProfile.httpCacheType(), QWebEngineProfile::MemoryHttpCache); - otrProfile.setPersistentCookiesPolicy(QWebEngineProfile::ForcePersistentCookies); - QCOMPARE(otrProfile.persistentCookiesPolicy(), QWebEngineProfile::NoPersistentCookies); + profile->setCachePath(QStringLiteral("/home/foo/bar")); + QCOMPARE(profile->cachePath(), QString()); + profile->setPersistentStoragePath(QStringLiteral("/home/foo/bar")); + QCOMPARE(profile->persistentStoragePath(), QStringLiteral("/home/foo/bar")); + profile->setHttpCacheType(QWebEngineProfile::DiskHttpCache); + QCOMPARE(profile->httpCacheType(), QWebEngineProfile::MemoryHttpCache); + profile->setPersistentCookiesPolicy(QWebEngineProfile::ForcePersistentCookies); + QCOMPARE(profile->persistentCookiesPolicy(), QWebEngineProfile::NoPersistentCookies); } - -void tst_QWebEngineProfile::testProfile() +void tst_QWebEngineProfile::userDefinedProfile() { QWebEngineProfile profile(QStringLiteral("Test")); QVERIFY(!profile.isOffTheRecord()); QCOMPARE(profile.storageName(), QStringLiteral("Test")); QCOMPARE(profile.httpCacheType(), QWebEngineProfile::DiskHttpCache); QCOMPARE(profile.persistentCookiesPolicy(), QWebEngineProfile::AllowPersistentCookies); - QCOMPARE(profile.cachePath(), QStandardPaths::writableLocation(QStandardPaths::CacheLocation) - + QStringLiteral("/QtWebEngine/Test")); - QCOMPARE(profile.persistentStoragePath(), QStandardPaths::writableLocation(QStandardPaths::DataLocation) - + QStringLiteral("/QtWebEngine/Test")); + QCOMPARE(profile.cachePath(), StandardCacheLocation() + QStringLiteral("/QtWebEngine/Test")); + QCOMPARE(profile.persistentStoragePath(), StandardAppDataLocation() + QStringLiteral("/QtWebEngine/Test")); } -void tst_QWebEngineProfile::clearDataFromCache() +class AutoDir : public QDir { - QWebEnginePage page; +public: + AutoDir(const QString &p) : QDir(p) + { + makeAbsolute(); + removeRecursively(); + } - QDir cacheDir("./tst_QWebEngineProfile_cacheDir"); - cacheDir.makeAbsolute(); - if (cacheDir.exists()) - cacheDir.removeRecursively(); - cacheDir.mkpath(cacheDir.path()); + ~AutoDir() { removeRecursively(); } +}; +qint64 totalSize(QDir dir) +{ + qint64 sum = 0; + const QDir::Filters filters{QDir::Dirs, QDir::Files, QDir::NoSymLinks, QDir::NoDotAndDotDot}; + for (const QFileInfo &entry : dir.entryInfoList(filters)) { + if (entry.isFile()) + sum += entry.size(); + else if (entry.isDir()) + sum += totalSize(entry.filePath()); + } + return sum; +} - QWebEngineProfile *profile = page.profile(); - profile->setCachePath(cacheDir.path()); - profile->setHttpCacheType(QWebEngineProfile::DiskHttpCache); +class TestServer : public HttpServer +{ +public: + TestServer() + { + connect(this, &HttpServer::newRequest, this, &TestServer::onNewRequest); + } - QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); - page.load(QUrl("http://qt-project.org")); - if (!loadFinishedSpy.wait(10000) || !loadFinishedSpy.at(0).at(0).toBool()) - QSKIP("Couldn't load page from network, skipping test."); +private: + void onNewRequest(HttpReqRep *rr) + { + const QDir resourceDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + "/resources"); + QString path = rr->requestPath(); + path.remove(0, 1); + + if (rr->requestMethod() != "GET" || !resourceDir.exists(path)) { + rr->sendResponse(404); + return; + } - cacheDir.refresh(); - QVERIFY(cacheDir.entryList().contains("Cache")); - cacheDir.cd("./Cache"); - int filesBeforeClear = cacheDir.entryList().count(); + QFile file(resourceDir.filePath(path)); + file.open(QIODevice::ReadOnly); + QByteArray data = file.readAll(); + rr->setResponseBody(data); + QMimeDatabase db; + QMimeType mime = db.mimeTypeForFileNameAndData(file.fileName(), data); + rr->setResponseHeader(QByteArrayLiteral("content-type"), mime.name().toUtf8()); + if (!mime.inherits("text/html")) + rr->setResponseHeader(QByteArrayLiteral("cache-control"), + QByteArrayLiteral("public, max-age=31536000")); + rr->sendResponse(); + } +}; + +void tst_QWebEngineProfile::clearDataFromCache() +{ + TestServer server; + QSignalSpy serverSpy(&server, &HttpServer::newRequest); + QVERIFY(server.start()); - QFileSystemWatcher fileSystemWatcher; - fileSystemWatcher.addPath(cacheDir.path()); - QSignalSpy directoryChangedSpy(&fileSystemWatcher, SIGNAL(directoryChanged(const QString &))); + AutoDir cacheDir("./tst_QWebEngineProfile_clearDataFromCache"); - // It deletes most of the files, but not all of them. - profile->clearHttpCache(); - QTest::qWait(1000); - QTRY_VERIFY(directoryChangedSpy.count() > 0); + QWebEngineProfile profile(QStringLiteral("clearDataFromCache")); + QSignalSpy cacheSpy(&profile, &QWebEngineProfile::clearHttpCacheCompleted); + profile.setCachePath(cacheDir.path()); + profile.setHttpCacheType(QWebEngineProfile::DiskHttpCache); - cacheDir.refresh(); - QVERIFY(filesBeforeClear > cacheDir.entryList().count()); + QWebEnginePage page(&profile); + QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); + // Wait for GET /favicon.ico + QTRY_COMPARE(serverSpy.size(), 3); + + QVERIFY(cacheDir.exists("Cache")); + qint64 sizeBeforeClear = totalSize(cacheDir); + profile.clearHttpCache(); + QTRY_COMPARE(cacheSpy.size(), 1); + QVERIFY(sizeBeforeClear > totalSize(cacheDir)); - cacheDir.removeRecursively(); + (void)server.stop(); } void tst_QWebEngineProfile::disableCache() { - QWebEnginePage page; - QDir cacheDir("./tst_QWebEngineProfile_cacheDir"); - if (cacheDir.exists()) - cacheDir.removeRecursively(); - cacheDir.mkpath(cacheDir.path()); + TestServer server; + QVERIFY(server.start()); - QWebEngineProfile *profile = page.profile(); - profile->setCachePath(cacheDir.path()); - QVERIFY(!cacheDir.entryList().contains("Cache")); - - profile->setHttpCacheType(QWebEngineProfile::NoCache); - QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); - page.load(QUrl("http://qt-project.org")); - if (!loadFinishedSpy.wait(10000) || !loadFinishedSpy.at(0).at(0).toBool()) - QSKIP("Couldn't load page from network, skipping test."); + AutoDir cacheDir("./tst_QWebEngineProfile_disableCache"); - cacheDir.refresh(); - QVERIFY(!cacheDir.entryList().contains("Cache")); + QWebEngineProfile profile("disableCache"); + QWebEnginePage page(&profile); + profile.setCachePath(cacheDir.path()); + QVERIFY(!cacheDir.exists("Cache")); - profile->setHttpCacheType(QWebEngineProfile::DiskHttpCache); - page.load(QUrl("http://qt-project.org")); - if (!loadFinishedSpy.wait(10000) || !loadFinishedSpy.at(1).at(0).toBool()) - QSKIP("Couldn't load page from network, skipping test."); + profile.setHttpCacheType(QWebEngineProfile::NoCache); + // Wait for cache to be cleared. + QTest::qWait(1000); + QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); + QVERIFY(!cacheDir.exists("Cache")); - cacheDir.refresh(); - QVERIFY(cacheDir.entryList().contains("Cache")); + profile.setHttpCacheType(QWebEngineProfile::DiskHttpCache); + QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); + QVERIFY(cacheDir.exists("Cache")); - cacheDir.removeRecursively(); + (void)server.stop(); } class RedirectingUrlSchemeHandler : public QWebEngineUrlSchemeHandler { public: - void requestStarted(QWebEngineUrlRequestJob *job) + void requestStarted(QWebEngineUrlRequestJob *job) override { job->redirect(QUrl(QStringLiteral("data:text/plain;charset=utf-8,") + job->requestUrl().fileName())); @@ -231,7 +254,7 @@ public: { } - void requestStarted(QWebEngineUrlRequestJob *job) + void requestStarted(QWebEngineUrlRequestJob *job) override { QBuffer *buffer = new QBuffer(job); buffer->setData(job->requestUrl().toString().toUtf8()); @@ -242,10 +265,12 @@ public: QList<QPointer<QBuffer>> m_buffers; }; -class StreamingIODevice : public QIODevice { +// an evil version constantly claiming to be at end, similar to QNetworkReply +class StreamingIODeviceBasic : public QIODevice +{ Q_OBJECT public: - StreamingIODevice(QObject *parent) : QIODevice(parent), m_bytesRead(0), m_bytesAvailable(0) + StreamingIODeviceBasic(QObject *parent) : QIODevice(parent), m_bytesRead(0), m_bytesAvailable(0) { setOpenMode(QIODevice::ReadOnly); m_timer.start(100, this); @@ -253,18 +278,17 @@ public: bool isSequential() const override { return true; } qint64 bytesAvailable() const override { - QMutexLocker lock(&m_mutex); + const std::lock_guard<QRecursiveMutex> lock(m_mutex); return m_bytesAvailable; } - bool atEnd() const override +protected: + bool internalAtEnd() const { - QMutexLocker lock(&m_mutex); return (m_data.size() >= 1000 && m_bytesRead >= 1000); } -protected: void timerEvent(QTimerEvent *) override { - QMutexLocker lock(&m_mutex); + const std::lock_guard<QRecursiveMutex> lock(m_mutex); m_bytesAvailable += 200; m_data.append(200, 'c'); emit readyRead(); @@ -276,13 +300,13 @@ protected: qint64 readData(char *data, qint64 maxlen) override { - QMutexLocker lock(&m_mutex); + const std::lock_guard<QRecursiveMutex> lock(m_mutex); qint64 len = qMin(qint64(m_bytesAvailable), maxlen); if (len) { memcpy(data, m_data.constData() + m_bytesRead, len); m_bytesAvailable -= len; m_bytesRead += len; - } else if (m_data.size() > 0) + } else if (internalAtEnd()) return -1; return len; @@ -292,14 +316,26 @@ protected: return 0; } + mutable QRecursiveMutex m_mutex; private: - mutable QMutex m_mutex{QMutex::Recursive}; QByteArray m_data; QBasicTimer m_timer; int m_bytesRead; int m_bytesAvailable; }; +// A nicer version implementing atEnd +class StreamingIODevice : public StreamingIODeviceBasic +{ +public: + StreamingIODevice(QObject *parent) : StreamingIODeviceBasic(parent) {} + bool atEnd() const override + { + const std::lock_guard<QRecursiveMutex> lock(m_mutex); + return internalAtEnd(); + } +}; + class StreamingUrlSchemeHandler : public QWebEngineUrlSchemeHandler { public: @@ -308,26 +344,25 @@ public: { } - void requestStarted(QWebEngineUrlRequestJob *job) + void requestStarted(QWebEngineUrlRequestJob *job) override { job->reply("text/plain;charset=utf-8", new StreamingIODevice(job)); } }; -static bool loadSync(QWebEngineView *view, const QUrl &url, int timeout = 5000) +class StreamingUrlSchemeHandler2 : public QWebEngineUrlSchemeHandler { - // Ripped off QTRY_VERIFY. - QSignalSpy loadFinishedSpy(view, SIGNAL(loadFinished(bool))); - view->load(url); - if (loadFinishedSpy.isEmpty()) - QTest::qWait(0); - for (int i = 0; i < timeout; i += 50) { - if (!loadFinishedSpy.isEmpty()) - return true; - QTest::qWait(50); +public: + StreamingUrlSchemeHandler2(QObject *parent = nullptr) + : QWebEngineUrlSchemeHandler(parent) + { } - return false; -} + + void requestStarted(QWebEngineUrlRequestJob *job) override + { + job->reply("text/plain;charset=utf-8", new StreamingIODeviceBasic(job)); + } +}; void tst_QWebEngineProfile::urlSchemeHandlers() { @@ -338,42 +373,42 @@ void tst_QWebEngineProfile::urlSchemeHandlers() view.setPage(new QWebEnginePage(&profile, &view)); view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); QString emailAddress = QStringLiteral("egon@olsen-banden.dk"); - QVERIFY(loadSync(&view, QUrl(QStringLiteral("letterto:") + emailAddress))); + QVERIFY(loadSync(view.page(), QUrl(QStringLiteral("letterto:") + emailAddress))); QCOMPARE(toPlainTextSync(view.page()), emailAddress); // Install a gopher handler after the view has been fully initialized. ReplyingUrlSchemeHandler gopherHandler; profile.installUrlSchemeHandler("gopher", &gopherHandler); QUrl url = QUrl(QStringLiteral("gopher://olsen-banden.dk/benny")); - QVERIFY(loadSync(&view, url)); + QVERIFY(loadSync(view.page(), url)); QCOMPARE(toPlainTextSync(view.page()), url.toString()); // Remove the letterto scheme, and check whether it is not handled anymore. profile.removeUrlScheme("letterto"); emailAddress = QStringLiteral("kjeld@olsen-banden.dk"); - QVERIFY(loadSync(&view, QUrl(QStringLiteral("letterto:") + emailAddress))); + QVERIFY(loadSync(view.page(), QUrl(QStringLiteral("letterto:") + emailAddress), false)); QVERIFY(toPlainTextSync(view.page()) != emailAddress); // Check if gopher is still working after removing letterto. url = QUrl(QStringLiteral("gopher://olsen-banden.dk/yvonne")); - QVERIFY(loadSync(&view, url)); + QVERIFY(loadSync(view.page(), url)); QCOMPARE(toPlainTextSync(view.page()), url.toString()); // Does removeAll work? profile.removeAllUrlSchemeHandlers(); url = QUrl(QStringLiteral("gopher://olsen-banden.dk/harry")); - QVERIFY(loadSync(&view, url)); + QVERIFY(loadSync(view.page(), url, false)); QVERIFY(toPlainTextSync(view.page()) != url.toString()); // Install a handler that is owned by the view. Make sure this doesn't crash on shutdown. profile.installUrlSchemeHandler("aviancarrier", new ReplyingUrlSchemeHandler(&view)); url = QUrl(QStringLiteral("aviancarrier:inspector.mortensen@politistyrke.dk")); - QVERIFY(loadSync(&view, url)); + QVERIFY(loadSync(view.page(), url)); QCOMPARE(toPlainTextSync(view.page()), url.toString()); // Check that all buffers got deleted - QCOMPARE(gopherHandler.m_buffers.count(), 2); - for (int i = 0; i < gopherHandler.m_buffers.count(); ++i) + QCOMPARE(gopherHandler.m_buffers.size(), 2); + for (int i = 0; i < gopherHandler.m_buffers.size(); ++i) QVERIFY(gopherHandler.m_buffers.at(i).isNull()); } @@ -393,17 +428,17 @@ public: { } - qint64 readData(char *, qint64) Q_DECL_OVERRIDE + qint64 readData(char *, qint64) override { m_job->fail(QWebEngineUrlRequestJob::RequestFailed); return -1; } - qint64 writeData(const char *, qint64) Q_DECL_OVERRIDE + qint64 writeData(const char *, qint64) override { m_job->fail(QWebEngineUrlRequestJob::RequestFailed); return -1; } - void close() Q_DECL_OVERRIDE + void close() override { QIODevice::close(); deleteLater(); @@ -433,7 +468,8 @@ void tst_QWebEngineProfile::urlSchemeHandlerFailRequest() view.setPage(new QWebEnginePage(&profile, &view)); view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); view.load(QUrl(QStringLiteral("foo://bar"))); - QVERIFY(loadFinishedSpy.wait()); + view.show(); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); QCOMPARE(toPlainTextSync(view.page()), QString()); } @@ -447,7 +483,8 @@ void tst_QWebEngineProfile::urlSchemeHandlerFailOnRead() view.setPage(new QWebEnginePage(&profile, &view)); view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); view.load(QUrl(QStringLiteral("foo://bar"))); - QVERIFY(loadFinishedSpy.wait()); + view.show(); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); QCOMPARE(toPlainTextSync(view.page()), QString()); } @@ -461,12 +498,88 @@ void tst_QWebEngineProfile::urlSchemeHandlerStreaming() view.setPage(new QWebEnginePage(&profile, &view)); view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); view.load(QUrl(QStringLiteral("stream://whatever"))); - QVERIFY(loadFinishedSpy.wait()); + view.show(); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); QByteArray result; result.append(1000, 'c'); QCOMPARE(toPlainTextSync(view.page()), QString::fromLatin1(result)); } +void tst_QWebEngineProfile::urlSchemeHandlerStreaming2() +{ + StreamingUrlSchemeHandler2 handler; + QWebEngineProfile profile; + profile.installUrlSchemeHandler("stream", &handler); + QWebEngineView view; + QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); + view.setPage(new QWebEnginePage(&profile, &view)); + view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); + view.load(QUrl(QStringLiteral("stream://whatever"))); + view.show(); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QByteArray result; + result.append(1000, 'c'); + QCOMPARE(toPlainTextSync(view.page()), QString::fromLatin1(result)); +} + +class ExtraHeaderInterceptor : public QWebEngineUrlRequestInterceptor +{ +public: + ExtraHeaderInterceptor() { } + + void setExtraHeader(const QByteArray &key, const QByteArray &value) + { + m_extraKey = key; + m_extraValue = value; + } + + void interceptRequest(QWebEngineUrlRequestInfo &info) override + { + if (info.requestUrl().scheme() == QLatin1String("myscheme")) + info.setHttpHeader(m_extraKey, m_extraValue); + } + + QByteArray m_extraKey; + QByteArray m_extraValue; +}; + +class RequestHeadersUrlSchemeHandler : public ReplyingUrlSchemeHandler +{ +public: + void setExpectedHeader(const QByteArray &key, const QByteArray &value) + { + m_expectedKey = key; + m_expectedValue = value; + } + void requestStarted(QWebEngineUrlRequestJob *job) override + { + const auto requestHeaders = job->requestHeaders(); + QVERIFY(requestHeaders.contains(m_expectedKey)); + QCOMPARE(requestHeaders.value(m_expectedKey), m_expectedValue); + ReplyingUrlSchemeHandler::requestStarted(job); + } + QByteArray m_expectedKey; + QByteArray m_expectedValue; +}; + +void tst_QWebEngineProfile::urlSchemeHandlerRequestHeaders() +{ + RequestHeadersUrlSchemeHandler handler; + ExtraHeaderInterceptor interceptor; + + handler.setExpectedHeader("Hello", "World"); + interceptor.setExtraHeader("Hello", "World"); + + QWebEngineProfile profile; + profile.installUrlSchemeHandler("myscheme", &handler); + profile.setUrlRequestInterceptor(&interceptor); + + QWebEnginePage page(&profile); + QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + page.load(QUrl(QStringLiteral("myscheme://whatever"))); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); +} + void tst_QWebEngineProfile::urlSchemeHandlerInstallation() { FailingUrlSchemeHandler handler; @@ -478,8 +591,9 @@ void tst_QWebEngineProfile::urlSchemeHandlerInstallation() QTest::ignoreMessage( QtWarningMsg, QRegularExpression("Cannot install a URL scheme handler overriding internal scheme.*")); + auto prevHandler = profile.urlSchemeHandler(scheme); profile.installUrlSchemeHandler(scheme, &handler); - QCOMPARE(profile.urlSchemeHandler(scheme), nullptr); + QCOMPARE(profile.urlSchemeHandler(scheme), prevHandler); } // Builtin schemes that *can* be overridden. @@ -507,13 +621,169 @@ void tst_QWebEngineProfile::urlSchemeHandlerInstallation() profile.removeUrlScheme("tst"); } +#if QT_CONFIG(webengine_webchannel) +class XhrStatusHost : public QObject +{ + Q_OBJECT +public: + std::map<QUrl, int> requests; + + bool isReady() + { + static const auto sig = QMetaMethod::fromSignal(&XhrStatusHost::load); + return isSignalConnected(sig); + } + +Q_SIGNALS: + void load(QUrl url); + +public Q_SLOTS: + void loadFinished(QUrl url, int status) + { + requests[url] = status; + } + +private: +}; + +class XhrStatusUrlSchemeHandler : public QWebEngineUrlSchemeHandler +{ +public: + void requestStarted(QWebEngineUrlRequestJob *job) override + { + QString path = job->requestUrl().path(); + if (path == "/") { + QBuffer *buffer = new QBuffer(job); + buffer->open(QBuffer::ReadWrite); + buffer->write(QByteArrayLiteral(R"( +<html> + <body> + <script src="qwebchannel.js"></script> + <script> + new QWebChannel(qt.webChannelTransport, (channel) => { + const host = channel.objects.host; + host.load.connect((url) => { + const xhr = new XMLHttpRequest(); + xhr.onload = () => { host.loadFinished(url, xhr.status); }; + xhr.onerror = () => { host.loadFinished(url, -1); }; + xhr.open("GET", url, true); + xhr.send(); + }); + }); + </script> + </body> +</html> +)")); + buffer->seek(0); + job->reply("text/html", buffer); + } else if (path == "/qwebchannel.js") { + QFile *file = new QFile(":/qtwebchannel/qwebchannel.js", job); + file->open(QFile::ReadOnly); + job->reply("application/javascript", file); + } else if (path == "/ok") { + QBuffer *buffer = new QBuffer(job); + buffer->buffer() = QByteArrayLiteral("ok"); + job->reply("text/plain", buffer); + } else if (path == "/redirect") { + QUrl url = job->requestUrl(); + url.setPath("/ok"); + job->redirect(url); + } else if (path == "/fail") { + job->fail(QWebEngineUrlRequestJob::RequestFailed); + } else { + job->fail(QWebEngineUrlRequestJob::UrlNotFound); + } + } +}; +#endif + +void tst_QWebEngineProfile::urlSchemeHandlerXhrStatus() +{ +#if QT_CONFIG(webengine_webchannel) + XhrStatusUrlSchemeHandler handler; + XhrStatusHost host; + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QWebChannel channel; + channel.registerObject("host", &host); + profile.installUrlSchemeHandler("aviancarrier", &handler); + page.setWebChannel(&channel); + page.load(QUrl("aviancarrier:/")); + QTRY_VERIFY_WITH_TIMEOUT(host.isReady(), 30000); + host.load(QUrl("aviancarrier:/ok")); + host.load(QUrl("aviancarrier:/redirect")); + host.load(QUrl("aviancarrier:/fail")); + host.load(QUrl("aviancarrier:/notfound")); + QTRY_COMPARE(host.requests.size(), 4u); + QCOMPARE(host.requests[QUrl("aviancarrier:/ok")], 200); + QCOMPARE(host.requests[QUrl("aviancarrier:/redirect")], 200); + QCOMPARE(host.requests[QUrl("aviancarrier:/fail")], -1); + QCOMPARE(host.requests[QUrl("aviancarrier:/notfound")], -1); +#else + QSKIP("No QtWebChannel"); +#endif +} + +class ScriptsUrlSchemeHandler : public QWebEngineUrlSchemeHandler +{ +public: + void requestStarted(QWebEngineUrlRequestJob *job) override + { + auto *script = new QBuffer(job); + script->setData(QByteArrayLiteral("window.test = 'SUCCESS';")); + job->reply("text/javascript", script); + } +}; + +void tst_QWebEngineProfile::urlSchemeHandlerScriptModule() +{ + ScriptsUrlSchemeHandler handler; + QWebEngineProfile profile; + profile.installUrlSchemeHandler("aviancarrier", &handler); + QWebEnginePage page(&profile); + QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + page.setHtml(QStringLiteral("<html><head><script src=\"aviancarrier:///\"></script></head><body>Test1</body></html>")); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("test")).toString(), QStringLiteral("SUCCESS")); + + loadFinishedSpy.clear(); + page.setHtml(QStringLiteral("<html><head><script type=\"module\" src=\"aviancarrier:///\"></script></head><body>Test2</body></html>")); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("test")).toString(), QStringLiteral("SUCCESS")); +} + +class LongReplyUrlSchemeHandler : public QWebEngineUrlSchemeHandler +{ +public: + LongReplyUrlSchemeHandler(QObject *parent = nullptr) : QWebEngineUrlSchemeHandler(parent) {} + ~LongReplyUrlSchemeHandler() {} + + void requestStarted(QWebEngineUrlRequestJob *job) override + { + QBuffer *buffer = new QBuffer(job); + buffer->setData(QByteArray(128 * 1024, ' ') + + "<html><head><title>Minify this!</title></head></html>"); + job->reply("text/html", buffer); + } +}; + +void tst_QWebEngineProfile::urlSchemeHandlerLongReply() +{ + LongReplyUrlSchemeHandler handler; + QWebEngineProfile profile; + profile.installUrlSchemeHandler("aviancarrier", &handler); + QWebEnginePage page(&profile); + page.load(QUrl("aviancarrier:/")); + QTRY_COMPARE(page.title(), QString("Minify this!")); +} + void tst_QWebEngineProfile::customUserAgent() { QString defaultUserAgent = QWebEngineProfile::defaultProfile()->httpUserAgent(); QWebEnginePage page; QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); page.setHtml(QStringLiteral("<html><body>Hello world!</body></html>")); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); // First test the user-agent is default QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("navigator.userAgent")).toString(), defaultUserAgent); @@ -526,7 +796,7 @@ void tst_QWebEngineProfile::customUserAgent() QWebEnginePage page2(&testProfile); QSignalSpy loadFinishedSpy2(&page2, SIGNAL(loadFinished(bool))); page2.setHtml(QStringLiteral("<html><body>Hello again!</body></html>")); - QTRY_COMPARE(loadFinishedSpy2.count(), 1); + QTRY_COMPARE(loadFinishedSpy2.size(), 1); QCOMPARE(evaluateJavaScriptSync(&page2, QStringLiteral("navigator.userAgent")).toString(), testUserAgent); QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("navigator.userAgent")).toString(), defaultUserAgent); @@ -540,7 +810,7 @@ void tst_QWebEngineProfile::httpAcceptLanguage() QWebEnginePage page; QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); page.setHtml(QStringLiteral("<html><body>Hello world!</body></html>")); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); QStringList defaultLanguages = evaluateJavaScriptSync(&page, QStringLiteral("navigator.languages")).toStringList(); @@ -552,7 +822,7 @@ void tst_QWebEngineProfile::httpAcceptLanguage() QWebEnginePage page2(&testProfile); QSignalSpy loadFinishedSpy2(&page2, SIGNAL(loadFinished(bool))); page2.setHtml(QStringLiteral("<html><body>Hello again!</body></html>")); - QTRY_COMPARE(loadFinishedSpy2.count(), 1); + QTRY_COMPARE(loadFinishedSpy2.size(), 1); QCOMPARE(evaluateJavaScriptSync(&page2, QStringLiteral("navigator.languages")).toStringList(), QStringList(testLang)); // Test the old one wasn't affected QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("navigator.languages")).toStringList(), defaultLanguages); @@ -564,32 +834,116 @@ void tst_QWebEngineProfile::httpAcceptLanguage() void tst_QWebEngineProfile::downloadItem() { - qRegisterMetaType<QWebEngineDownloadItem *>(); + qRegisterMetaType<QWebEngineDownloadRequest *>(); QWebEngineProfile testProfile; QWebEnginePage page(&testProfile); - QSignalSpy downloadSpy(&testProfile, SIGNAL(downloadRequested(QWebEngineDownloadItem *))); - connect(&testProfile, &QWebEngineProfile::downloadRequested, this, [=] (QWebEngineDownloadItem *item) { item->accept(); }); + QSignalSpy downloadSpy(&testProfile, SIGNAL(downloadRequested(QWebEngineDownloadRequest *))); page.load(QUrl::fromLocalFile(QCoreApplication::applicationFilePath())); - QTRY_COMPARE(downloadSpy.count(), 1); + QTRY_COMPARE(downloadSpy.size(), 1); } void tst_QWebEngineProfile::changePersistentPath() { - QWebEngineProfile testProfile(QStringLiteral("Test")); - const QString oldPath = testProfile.persistentStoragePath(); - QVERIFY(oldPath.endsWith(QStringLiteral("Test"))); + TestServer server; + QVERIFY(server.start()); - // Make sure the profile has been used and the url-request-context-getter instantiated: + AutoDir dataDir1(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + + QStringLiteral("/QtWebEngine/changePersistentPath1")); + AutoDir dataDir2(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + + QStringLiteral("/QtWebEngine/changePersistentPath2")); + + QWebEngineProfile testProfile(QStringLiteral("changePersistentPath1")); + QCOMPARE(testProfile.persistentStoragePath(), dataDir1.path()); + + // Make sure the profile has been used: QWebEnginePage page(&testProfile); - QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); - page.load(QUrl("http://qt-project.org")); - if (!loadFinishedSpy.wait(10000) || !loadFinishedSpy.at(0).at(0).toBool()) - QSKIP("Couldn't load page from network, skipping test."); + QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); // Test we do not crash (QTBUG-55322): - testProfile.setPersistentStoragePath(oldPath + QLatin1Char('2')); - const QString newPath = testProfile.persistentStoragePath(); - QVERIFY(newPath.endsWith(QStringLiteral("Test2"))); + testProfile.setPersistentStoragePath(dataDir2.path()); + QCOMPARE(testProfile.persistentStoragePath(), dataDir2.path()); + QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); + QVERIFY(dataDir2.exists()); + + (void)server.stop(); +} + +void tst_QWebEngineProfile::changeHttpUserAgent() +{ + TestServer server; + QVERIFY(server.start()); + + QList<QByteArray> userAgents; + connect(&server, &HttpServer::newRequest, [&](HttpReqRep *rr) { + if (rr->requestPath() == "/hedgehog.html") + userAgents.push_back(rr->requestHeader(QByteArrayLiteral("user-agent"))); + }); + + QWebEngineProfile profile(QStringLiteral("changeHttpUserAgent")); + std::unique_ptr<QWebEnginePage> page; + page.reset(new QWebEnginePage(&profile)); + QVERIFY(loadSync(page.get(), server.url("/hedgehog.html"))); + page.reset(); + profile.setHttpUserAgent("webturbine/42"); + page.reset(new QWebEnginePage(&profile)); + QVERIFY(loadSync(page.get(), server.url("/hedgehog.html"))); + + QCOMPARE(userAgents.size(), 2); + QCOMPARE(userAgents[1], "webturbine/42"); + QVERIFY(userAgents[0] != userAgents[1]); + + QVERIFY(server.stop()); +} + +void tst_QWebEngineProfile::changeHttpAcceptLanguage() +{ + TestServer server; + QVERIFY(server.start()); + + QList<QByteArray> languages; + connect(&server, &HttpServer::newRequest, [&](HttpReqRep *rr) { + if (rr->requestPath() == "/hedgehog.html") + languages.push_back(rr->requestHeader(QByteArrayLiteral("accept-language"))); + }); + + QWebEngineProfile profile(QStringLiteral("changeHttpAcceptLanguage")); + std::unique_ptr<QWebEnginePage> page; + page.reset(new QWebEnginePage(&profile)); + QVERIFY(loadSync(page.get(), server.url("/hedgehog.html"))); + page.reset(); + profile.setHttpAcceptLanguage("fi"); + page.reset(new QWebEnginePage(&profile)); + QVERIFY(loadSync(page.get(), server.url("/hedgehog.html"))); + + QCOMPARE(languages.size(), 2); + QCOMPARE(languages[1], "fi"); + QVERIFY(languages[0] != languages[1]); + + QVERIFY(server.stop()); +} + +void tst_QWebEngineProfile::changePersistentCookiesPolicy() +{ + TestServer server; + QVERIFY(server.start()); + + AutoDir dataDir("./tst_QWebEngineProfile_dataDir"); + + QWebEngineProfile profile(QStringLiteral("changePersistentCookiesPolicy")); + QWebEnginePage page(&profile); + + profile.setPersistentStoragePath(dataDir.path()); + profile.setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); + + QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); + QVERIFY(!dataDir.exists("Cookies")); + + profile.setPersistentCookiesPolicy(QWebEngineProfile::ForcePersistentCookies); + + QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); + QVERIFY(dataDir.exists("Cookies")); + + (void)server.stop(); } class InitiatorSpy : public QWebEngineUrlSchemeHandler @@ -608,26 +962,32 @@ void tst_QWebEngineProfile::initiator() InitiatorSpy handler; QWebEngineProfile profile; profile.installUrlSchemeHandler("foo", &handler); - QWebEnginePage page(&profile); + QWebEnginePage page(&profile, nullptr); QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + page.load(QUrl("about:blank")); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + loadFinishedSpy.clear(); // about:blank has a unique origin, so initiator should be QUrl("null") evaluateJavaScriptSync(&page, "window.location = 'foo:bar'"); - QVERIFY(loadFinishedSpy.wait()); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + loadFinishedSpy.clear(); QCOMPARE(handler.initiator, QUrl("null")); page.setHtml("", QUrl("http://test:123/foo%20bar")); - QVERIFY(loadFinishedSpy.wait()); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + loadFinishedSpy.clear(); // baseUrl determines the origin, so QUrl("http://test:123") evaluateJavaScriptSync(&page, "window.location = 'foo:bar'"); - QVERIFY(loadFinishedSpy.wait()); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + loadFinishedSpy.clear(); QCOMPARE(handler.initiator, QUrl("http://test:123")); // Directly calling load/setUrl should have initiator QUrl(), meaning // browser-initiated, trusted. page.load(QUrl("foo:bar")); - QVERIFY(loadFinishedSpy.wait()); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 10000); QCOMPARE(handler.initiator, QUrl()); } @@ -643,13 +1003,13 @@ void tst_QWebEngineProfile::badDeleteOrder() QSignalSpy spyLoadFinished(page, SIGNAL(loadFinished(bool))); page->setHtml(QStringLiteral("<html><body><h1>Badly handled page!</h1></body></html>")); - QTRY_COMPARE(spyLoadFinished.count(), 1); + QTRY_COMPARE(spyLoadFinished.size(), 1); delete profile; delete view; } -void tst_QWebEngineProfile::qtbug_72299() +void tst_QWebEngineProfile::qtbug_71895() { QWebEngineView view; QSignalSpy loadSpy(view.page(), SIGNAL(loadFinished(bool))); @@ -659,8 +1019,9 @@ void tst_QWebEngineProfile::qtbug_72299() view.page()->profile()->setHttpCacheType(QWebEngineProfile::NoCache); view.page()->profile()->cookieStore()->deleteAllCookies(); view.page()->profile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); - QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 20000); - QVERIFY(loadSpy.front().front().toBool()); + bool gotSignal = loadSpy.size() || loadSpy.wait(20000); + if (!gotSignal) + QSKIP("Couldn't load page from network, skipping test."); } |