| /**************************************************************************** |
| ** |
| ** 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" |
| #include <QtCore/qbuffer.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 <QtWebEngineWidgets/qwebengineview.h> |
| #include <QtWebEngineWidgets/qwebenginedownloaditem.h> |
| |
| #if QT_CONFIG(webengine_webchannel) |
| #include <QWebChannel> |
| #endif |
| |
| #include <map> |
| #include <mutex> |
| |
| class tst_QWebEngineProfile : public QObject |
| { |
| Q_OBJECT |
| |
| private Q_SLOTS: |
| void initTestCase(); |
| void init(); |
| void cleanup(); |
| void privateProfile(); |
| void testProfile(); |
| void clearDataFromCache(); |
| void disableCache(); |
| void urlSchemeHandlers(); |
| void urlSchemeHandlerFailRequest(); |
| void urlSchemeHandlerFailOnRead(); |
| void urlSchemeHandlerStreaming(); |
| void urlSchemeHandlerRequestHeaders(); |
| void urlSchemeHandlerInstallation(); |
| void urlSchemeHandlerXhrStatus(); |
| void urlSchemeHandlerScriptModule(); |
| void customUserAgent(); |
| void httpAcceptLanguage(); |
| void downloadItem(); |
| void changePersistentPath(); |
| void initiator(); |
| void badDeleteOrder(); |
| void qtbug_71895(); // this should be the last test |
| }; |
| |
| void tst_QWebEngineProfile::initTestCase() |
| { |
| QWebEngineUrlScheme foo("foo"); |
| QWebEngineUrlScheme stream("stream"); |
| QWebEngineUrlScheme letterto("letterto"); |
| QWebEngineUrlScheme aviancarrier("aviancarrier"); |
| 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); |
| } |
| |
| 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")); |
| } |
| |
| void tst_QWebEngineProfile::cleanup() |
| { |
| QWebEngineProfile *profile = QWebEngineProfile::defaultProfile(); |
| profile->setCachePath(QString()); |
| profile->setPersistentStoragePath(QString()); |
| profile->setHttpCacheType(QWebEngineProfile::DiskHttpCache); |
| profile->removeAllUrlSchemeHandlers(); |
| } |
| |
| void tst_QWebEngineProfile::privateProfile() |
| { |
| QWebEngineProfile otrProfile; |
| QVERIFY(otrProfile.isOffTheRecord()); |
| QCOMPARE(otrProfile.httpCacheType(), QWebEngineProfile::MemoryHttpCache); |
| QCOMPARE(otrProfile.persistentCookiesPolicy(), QWebEngineProfile::NoPersistentCookies); |
| QCOMPARE(otrProfile.cachePath(), QString()); |
| QCOMPARE(otrProfile.persistentStoragePath(), QString()); |
| // 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); |
| } |
| |
| |
| void tst_QWebEngineProfile::testProfile() |
| { |
| 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")); |
| } |
| |
| void tst_QWebEngineProfile::clearDataFromCache() |
| { |
| QWebEnginePage page; |
| |
| QDir cacheDir("./tst_QWebEngineProfile_cacheDir"); |
| cacheDir.makeAbsolute(); |
| if (cacheDir.exists()) |
| cacheDir.removeRecursively(); |
| cacheDir.mkpath(cacheDir.path()); |
| |
| QWebEngineProfile *profile = page.profile(); |
| profile->setCachePath(cacheDir.path()); |
| profile->setHttpCacheType(QWebEngineProfile::DiskHttpCache); |
| |
| 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."); |
| |
| cacheDir.refresh(); |
| QVERIFY(cacheDir.entryList().contains("Cache")); |
| cacheDir.cd("./Cache"); |
| int filesBeforeClear = cacheDir.entryList().count(); |
| |
| QFileSystemWatcher fileSystemWatcher; |
| fileSystemWatcher.addPath(cacheDir.path()); |
| QSignalSpy directoryChangedSpy(&fileSystemWatcher, SIGNAL(directoryChanged(const QString &))); |
| |
| // It deletes most of the files, but not all of them. |
| profile->clearHttpCache(); |
| QTest::qWait(1000); |
| QTRY_VERIFY(directoryChangedSpy.count() > 0); |
| |
| cacheDir.refresh(); |
| QVERIFY(filesBeforeClear > cacheDir.entryList().count()); |
| |
| cacheDir.removeRecursively(); |
| } |
| |
| void tst_QWebEngineProfile::disableCache() |
| { |
| QWebEnginePage page; |
| QDir cacheDir("./tst_QWebEngineProfile_cacheDir"); |
| if (cacheDir.exists()) |
| cacheDir.removeRecursively(); |
| cacheDir.mkpath(cacheDir.path()); |
| |
| 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."); |
| |
| cacheDir.refresh(); |
| QVERIFY(!cacheDir.entryList().contains("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."); |
| |
| cacheDir.refresh(); |
| QVERIFY(cacheDir.entryList().contains("Cache")); |
| |
| cacheDir.removeRecursively(); |
| } |
| |
| class RedirectingUrlSchemeHandler : public QWebEngineUrlSchemeHandler |
| { |
| public: |
| void requestStarted(QWebEngineUrlRequestJob *job) |
| { |
| job->redirect(QUrl(QStringLiteral("data:text/plain;charset=utf-8,") |
| + job->requestUrl().fileName())); |
| } |
| }; |
| |
| class ReplyingUrlSchemeHandler : public QWebEngineUrlSchemeHandler |
| { |
| public: |
| ReplyingUrlSchemeHandler(QObject *parent = nullptr) |
| : QWebEngineUrlSchemeHandler(parent) |
| { |
| } |
| ~ReplyingUrlSchemeHandler() |
| { |
| } |
| |
| void requestStarted(QWebEngineUrlRequestJob *job) |
| { |
| QBuffer *buffer = new QBuffer(job); |
| buffer->setData(job->requestUrl().toString().toUtf8()); |
| m_buffers.append(buffer); |
| job->reply("text/plain;charset=utf-8", buffer); |
| } |
| |
| QList<QPointer<QBuffer>> m_buffers; |
| }; |
| |
| class StreamingIODevice : public QIODevice { |
| Q_OBJECT |
| public: |
| StreamingIODevice(QObject *parent) : QIODevice(parent), m_bytesRead(0), m_bytesAvailable(0) |
| { |
| setOpenMode(QIODevice::ReadOnly); |
| m_timer.start(100, this); |
| } |
| bool isSequential() const override { return true; } |
| qint64 bytesAvailable() const override |
| { |
| const std::lock_guard<QRecursiveMutex> lock(m_mutex); |
| return m_bytesAvailable; |
| } |
| bool atEnd() const override |
| { |
| const std::lock_guard<QRecursiveMutex> lock(m_mutex); |
| return (m_data.size() >= 1000 && m_bytesRead >= 1000); |
| } |
| protected: |
| void timerEvent(QTimerEvent *) override |
| { |
| const std::lock_guard<QRecursiveMutex> lock(m_mutex); |
| m_bytesAvailable += 200; |
| m_data.append(200, 'c'); |
| emit readyRead(); |
| if (m_data.size() >= 1000) { |
| m_timer.stop(); |
| emit readChannelFinished(); |
| } |
| } |
| |
| qint64 readData(char *data, qint64 maxlen) override |
| { |
| 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) |
| return -1; |
| |
| return len; |
| } |
| qint64 writeData(const char *, qint64) override |
| { |
| return 0; |
| } |
| |
| private: |
| #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) |
| mutable QMutex m_mutex{QMutex::Recursive}; |
| using QRecursiveMutex = QMutex; |
| #else |
| mutable QRecursiveMutex m_mutex; |
| #endif |
| QByteArray m_data; |
| QBasicTimer m_timer; |
| int m_bytesRead; |
| int m_bytesAvailable; |
| }; |
| |
| class StreamingUrlSchemeHandler : public QWebEngineUrlSchemeHandler |
| { |
| public: |
| StreamingUrlSchemeHandler(QObject *parent = nullptr) |
| : QWebEngineUrlSchemeHandler(parent) |
| { |
| } |
| |
| void requestStarted(QWebEngineUrlRequestJob *job) |
| { |
| job->reply("text/plain;charset=utf-8", new StreamingIODevice(job)); |
| } |
| }; |
| |
| static bool loadSync(QWebEngineView *view, const QUrl &url, int timeout = 5000) |
| { |
| // 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); |
| } |
| return false; |
| } |
| |
| void tst_QWebEngineProfile::urlSchemeHandlers() |
| { |
| RedirectingUrlSchemeHandler lettertoHandler; |
| QWebEngineProfile profile(QStringLiteral("urlSchemeHandlers")); |
| profile.installUrlSchemeHandler("letterto", &lettertoHandler); |
| QWebEngineView view; |
| 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))); |
| 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)); |
| 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(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)); |
| QCOMPARE(toPlainTextSync(view.page()), url.toString()); |
| |
| // Does removeAll work? |
| profile.removeAllUrlSchemeHandlers(); |
| url = QUrl(QStringLiteral("gopher://olsen-banden.dk/harry")); |
| QVERIFY(loadSync(&view, url)); |
| 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)); |
| 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) |
| QVERIFY(gopherHandler.m_buffers.at(i).isNull()); |
| } |
| |
| class FailingUrlSchemeHandler : public QWebEngineUrlSchemeHandler |
| { |
| public: |
| void requestStarted(QWebEngineUrlRequestJob *job) override |
| { |
| job->fail(QWebEngineUrlRequestJob::UrlInvalid); |
| } |
| }; |
| |
| class FailingIODevice : public QIODevice |
| { |
| public: |
| FailingIODevice(QWebEngineUrlRequestJob *job) : m_job(job) |
| { |
| } |
| |
| qint64 readData(char *, qint64) override |
| { |
| m_job->fail(QWebEngineUrlRequestJob::RequestFailed); |
| return -1; |
| } |
| qint64 writeData(const char *, qint64) override |
| { |
| m_job->fail(QWebEngineUrlRequestJob::RequestFailed); |
| return -1; |
| } |
| void close() override |
| { |
| QIODevice::close(); |
| deleteLater(); |
| } |
| |
| private: |
| QWebEngineUrlRequestJob *m_job; |
| }; |
| |
| class FailOnReadUrlSchemeHandler : public QWebEngineUrlSchemeHandler |
| { |
| public: |
| void requestStarted(QWebEngineUrlRequestJob *job) override |
| { |
| job->reply(QByteArrayLiteral("text/plain"), new FailingIODevice(job)); |
| } |
| }; |
| |
| |
| void tst_QWebEngineProfile::urlSchemeHandlerFailRequest() |
| { |
| FailingUrlSchemeHandler handler; |
| QWebEngineProfile profile; |
| profile.installUrlSchemeHandler("foo", &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("foo://bar"))); |
| QVERIFY(loadFinishedSpy.wait()); |
| QCOMPARE(toPlainTextSync(view.page()), QString()); |
| } |
| |
| void tst_QWebEngineProfile::urlSchemeHandlerFailOnRead() |
| { |
| FailOnReadUrlSchemeHandler handler; |
| QWebEngineProfile profile; |
| profile.installUrlSchemeHandler("foo", &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("foo://bar"))); |
| QVERIFY(loadFinishedSpy.wait()); |
| QCOMPARE(toPlainTextSync(view.page()), QString()); |
| } |
| |
| void tst_QWebEngineProfile::urlSchemeHandlerStreaming() |
| { |
| StreamingUrlSchemeHandler 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"))); |
| QVERIFY(loadFinishedSpy.wait()); |
| 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"))); |
| QVERIFY(loadFinishedSpy.wait()); |
| } |
| |
| void tst_QWebEngineProfile::urlSchemeHandlerInstallation() |
| { |
| FailingUrlSchemeHandler handler; |
| QWebEngineProfile profile; |
| |
| // Builtin schemes that *cannot* be overridden. |
| for (auto scheme : { "about", "blob", "data", "javascript", "qrc", "https", "http", "file", |
| "ftp", "wss", "ws", "filesystem", "FileSystem" }) { |
| 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), prevHandler); |
| } |
| |
| // Builtin schemes that *can* be overridden. |
| for (auto scheme : { "gopher", "GOPHER" }) { |
| profile.installUrlSchemeHandler(scheme, &handler); |
| QCOMPARE(profile.urlSchemeHandler(scheme), &handler); |
| profile.removeUrlScheme(scheme); |
| } |
| |
| // Other schemes should be registered with QWebEngineUrlScheme first, but |
| // handler installation still succeeds to preserve backwards compatibility. |
| QTest::ignoreMessage( |
| QtWarningMsg, |
| QRegularExpression("Please register the custom scheme.*")); |
| profile.installUrlSchemeHandler("tst", &handler); |
| QCOMPARE(profile.urlSchemeHandler("tst"), &handler); |
| |
| // Existing handler cannot be overridden. |
| FailingUrlSchemeHandler handler2; |
| QTest::ignoreMessage( |
| QtWarningMsg, |
| QRegularExpression("URL scheme handler already installed.*")); |
| profile.installUrlSchemeHandler("tst", &handler2); |
| QCOMPARE(profile.urlSchemeHandler("tst"), &handler); |
| 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) |
| { |
| 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(host.isReady()); |
| 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) |
| { |
| 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.count(), 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.count(), 1); |
| QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("test")).toString(), QStringLiteral("SUCCESS")); |
| } |
| |
| 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); |
| |
| // First test the user-agent is default |
| QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("navigator.userAgent")).toString(), defaultUserAgent); |
| |
| const QString testUserAgent = QStringLiteral("tst_QWebEngineProfile 1.0"); |
| QWebEngineProfile testProfile; |
| testProfile.setHttpUserAgent(testUserAgent); |
| |
| // Test a new profile with custom user-agent works |
| QWebEnginePage page2(&testProfile); |
| QSignalSpy loadFinishedSpy2(&page2, SIGNAL(loadFinished(bool))); |
| page2.setHtml(QStringLiteral("<html><body>Hello again!</body></html>")); |
| QTRY_COMPARE(loadFinishedSpy2.count(), 1); |
| QCOMPARE(evaluateJavaScriptSync(&page2, QStringLiteral("navigator.userAgent")).toString(), testUserAgent); |
| QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("navigator.userAgent")).toString(), defaultUserAgent); |
| |
| // Test an existing page and profile with custom user-agent works |
| QWebEngineProfile::defaultProfile()->setHttpUserAgent(testUserAgent); |
| QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("navigator.userAgent")).toString(), testUserAgent); |
| } |
| |
| 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); |
| |
| QStringList defaultLanguages = evaluateJavaScriptSync(&page, QStringLiteral("navigator.languages")).toStringList(); |
| |
| const QString testLang = QStringLiteral("xx-YY"); |
| QWebEngineProfile testProfile; |
| testProfile.setHttpAcceptLanguage(testLang); |
| |
| // Test a completely new profile |
| QWebEnginePage page2(&testProfile); |
| QSignalSpy loadFinishedSpy2(&page2, SIGNAL(loadFinished(bool))); |
| page2.setHtml(QStringLiteral("<html><body>Hello again!</body></html>")); |
| QTRY_COMPARE(loadFinishedSpy2.count(), 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); |
| |
| // Test changing an existing page and profile |
| QWebEngineProfile::defaultProfile()->setHttpAcceptLanguage(testLang); |
| QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("navigator.languages")).toStringList(), QStringList(testLang)); |
| } |
| |
| void tst_QWebEngineProfile::downloadItem() |
| { |
| qRegisterMetaType<QWebEngineDownloadItem *>(); |
| QWebEngineProfile testProfile; |
| QWebEnginePage page(&testProfile); |
| QSignalSpy downloadSpy(&testProfile, SIGNAL(downloadRequested(QWebEngineDownloadItem *))); |
| connect(&testProfile, &QWebEngineProfile::downloadRequested, this, [=] (QWebEngineDownloadItem *item) { item->accept(); }); |
| page.load(QUrl::fromLocalFile(QCoreApplication::applicationFilePath())); |
| QTRY_COMPARE(downloadSpy.count(), 1); |
| } |
| |
| void tst_QWebEngineProfile::changePersistentPath() |
| { |
| QWebEngineProfile testProfile(QStringLiteral("Test")); |
| const QString oldPath = testProfile.persistentStoragePath(); |
| QVERIFY(oldPath.endsWith(QStringLiteral("Test"))); |
| |
| // Make sure the profile has been used and the url-request-context-getter instantiated: |
| 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."); |
| |
| // Test we do not crash (QTBUG-55322): |
| testProfile.setPersistentStoragePath(oldPath + QLatin1Char('2')); |
| const QString newPath = testProfile.persistentStoragePath(); |
| QVERIFY(newPath.endsWith(QStringLiteral("Test2"))); |
| } |
| |
| class InitiatorSpy : public QWebEngineUrlSchemeHandler |
| { |
| public: |
| QUrl initiator; |
| void requestStarted(QWebEngineUrlRequestJob *job) override |
| { |
| initiator = job->initiator(); |
| job->fail(QWebEngineUrlRequestJob::RequestDenied); |
| } |
| }; |
| |
| void tst_QWebEngineProfile::initiator() |
| { |
| InitiatorSpy handler; |
| QWebEngineProfile profile; |
| profile.installUrlSchemeHandler("foo", &handler); |
| QWebEnginePage page(&profile); |
| QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); |
| |
| // about:blank has a unique origin, so initiator should be QUrl("null") |
| evaluateJavaScriptSync(&page, "window.location = 'foo:bar'"); |
| QVERIFY(loadFinishedSpy.wait()); |
| QCOMPARE(handler.initiator, QUrl("null")); |
| |
| page.setHtml("", QUrl("http://test:123/foo%20bar")); |
| QVERIFY(loadFinishedSpy.wait()); |
| |
| // baseUrl determines the origin, so QUrl("http://test:123") |
| evaluateJavaScriptSync(&page, "window.location = 'foo:bar'"); |
| QVERIFY(loadFinishedSpy.wait()); |
| 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()); |
| QCOMPARE(handler.initiator, QUrl()); |
| } |
| |
| void tst_QWebEngineProfile::badDeleteOrder() |
| { |
| QWebEngineProfile *profile = new QWebEngineProfile(); |
| QWebEngineView *view = new QWebEngineView(); |
| view->resize(640, 480); |
| view->show(); |
| QVERIFY(QTest::qWaitForWindowExposed(view)); |
| QWebEnginePage *page = new QWebEnginePage(profile, view); |
| view->setPage(page); |
| |
| QSignalSpy spyLoadFinished(page, SIGNAL(loadFinished(bool))); |
| page->setHtml(QStringLiteral("<html><body><h1>Badly handled page!</h1></body></html>")); |
| QTRY_COMPARE(spyLoadFinished.count(), 1); |
| |
| delete profile; |
| delete view; |
| } |
| |
| void tst_QWebEngineProfile::qtbug_71895() |
| { |
| QWebEngineView view; |
| QSignalSpy loadSpy(view.page(), SIGNAL(loadFinished(bool))); |
| view.setUrl(QUrl("https://www.qt.io")); |
| view.show(); |
| view.page()->profile()->clearHttpCache(); |
| view.page()->profile()->setHttpCacheType(QWebEngineProfile::NoCache); |
| view.page()->profile()->cookieStore()->deleteAllCookies(); |
| view.page()->profile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); |
| bool gotSignal = loadSpy.count() || loadSpy.wait(20000); |
| if (!gotSignal) |
| QSKIP("Couldn't load page from network, skipping test."); |
| } |
| |
| |
| QTEST_MAIN(tst_QWebEngineProfile) |
| #include "tst_qwebengineprofile.moc" |