blob: a7a5ba62a4715036cc1465eaa2a92790ea666166 [file] [log] [blame]
/****************************************************************************
**
** 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"