/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtWebEngine module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include "httpserver.h" #include "../util.h" #include "qdebug.h" #include "qwebenginepage.h" #include "qwebengineprofile.h" #include "qwebenginesettings.h" #include "qwebengineview.h" class tst_LoadSignals : public QObject { Q_OBJECT public Q_SLOTS: void initTestCase(); void init(); private Q_SLOTS: void monotonicity(); void loadStartedAndFinishedCount_data(); void loadStartedAndFinishedCount(); void loadAfterInPageNavigation_qtbug66869(); void fileDownloadDoesNotTriggerLoadSignals_qtbug66661(); void numberOfStartedAndFinishedSignalsIsSame(); void loadFinishedAfterNotFoundError_data(); void loadFinishedAfterNotFoundError(); void errorPageTriggered_data(); void errorPageTriggered(); private: QWebEngineProfile profile; QWebEnginePage page{&profile}; QWebEngineView view; QSignalSpy loadStartedSpy{&page, &QWebEnginePage::loadStarted}; QSignalSpy loadProgressSpy{&page, &QWebEnginePage::loadProgress}; QSignalSpy loadFinishedSpy{&page, &QWebEnginePage::loadFinished}; }; void tst_LoadSignals::initTestCase() { view.setPage(&page); view.resize(1024,768); view.show(); } void tst_LoadSignals::init() { // Reset content loadFinishedSpy.clear(); view.load(QUrl("about:blank")); QTRY_COMPARE(loadFinishedSpy.count(), 1); loadStartedSpy.clear(); loadProgressSpy.clear(); loadFinishedSpy.clear(); } /** * Test that we get the expected number of loadStarted and loadFinished signals */ void tst_LoadSignals::loadStartedAndFinishedCount_data() { QTest::addColumn("url"); QTest::addColumn("expectedLoadCount"); QTest::newRow("Normal") << QUrl("qrc:///resources/page1.html") << 1; QTest::newRow("WithAnchor") << QUrl("qrc:///resources/page2.html#anchor") << 1; // In this case, we get an unexpected additional loadStarted, but no corresponding // loadFinished, so expectedLoadCount=2 would also not work. See also QTBUG-65223 QTest::newRow("WithAnchorClickedFromJS") << QUrl("qrc:///resources/page3.html") << 1; } void tst_LoadSignals::loadStartedAndFinishedCount() { QFETCH(QUrl, url); QFETCH(int, expectedLoadCount); view.load(url); QTRY_COMPARE(loadFinishedSpy.size(), expectedLoadCount); QVERIFY(loadFinishedSpy[0][0].toBool()); // Wait for 10 seconds (abort waiting if another loadStarted or loadFinished occurs) QTRY_LOOP_IMPL((loadStartedSpy.size() != expectedLoadCount) || (loadFinishedSpy.size() != expectedLoadCount), 10000, 100); // No further loadStarted should have occurred within this time QCOMPARE(loadStartedSpy.size(), expectedLoadCount); QCOMPARE(loadFinishedSpy.size(), expectedLoadCount); } /** * Test monotonicity of loadProgress signals */ void tst_LoadSignals::monotonicity() { HttpServer server; server.setResourceDirs({ TESTS_SHARED_DATA_DIR }); connect(&server, &HttpServer::newRequest, [] (HttpReqRep *) { QTest::qWait(250); // just add delay to trigger some progress for every sub resource }); QVERIFY(server.start()); view.load(server.url("/loadprogress/main.html")); QTRY_COMPARE(loadFinishedSpy.size(), 1); QVERIFY(loadFinishedSpy[0][0].toBool()); // first loadProgress should have 0% progress QCOMPARE(loadProgressSpy.takeFirst()[0].toInt(), 0); // every loadProgress should have more progress than the one before int progress = 0; for (const auto &item : loadProgressSpy) { QVERIFY(progress < item[0].toInt()); progress = item[0].toInt(); } // last loadProgress should have 100% progress QCOMPARE(loadProgressSpy.last()[0].toInt(), 100); } /** * Test that a second load after an in-page navigation receives its expected loadStarted and * loadFinished signal. */ void tst_LoadSignals::loadAfterInPageNavigation_qtbug66869() { view.load(QUrl("qrc:///resources/page3.html")); QTRY_COMPARE(loadFinishedSpy.size(), 1); QVERIFY(loadFinishedSpy[0][0].toBool()); // page3 does an in-page navigation after 500ms QTest::qWait(2000); loadFinishedSpy.clear(); loadProgressSpy.clear(); loadStartedSpy.clear(); // second load view.load(QUrl("qrc:///resources/page1.html")); QTRY_COMPARE(loadFinishedSpy.size(), 1); QVERIFY(loadFinishedSpy[0][0].toBool()); // loadStarted and loadFinished should have been signalled QCOMPARE(loadStartedSpy.size(), 1); // reminder that we still need to solve the core issue QFAIL("https://codereview.qt-project.org/#/c/222112/ only hides the symptom, the core issue still needs to be solved"); } /** * Test that file-downloads don't trigger loadStarted or loadFinished signals. * See QTBUG-66661 */ void tst_LoadSignals::fileDownloadDoesNotTriggerLoadSignals_qtbug66661() { view.load(QUrl("qrc:///resources/page4.html")); QTRY_COMPARE(loadFinishedSpy.size(), 1); QVERIFY(loadFinishedSpy[0][0].toBool()); // allow the download QTemporaryDir tempDir; QVERIFY(tempDir.isValid()); QWebEngineDownloadItem::DownloadState downloadState = QWebEngineDownloadItem::DownloadRequested; ScopedConnection sc1 = connect(&profile, &QWebEngineProfile::downloadRequested, [&downloadState, &tempDir](QWebEngineDownloadItem *item) { connect(item, &QWebEngineDownloadItem::stateChanged, [&downloadState](QWebEngineDownloadItem::DownloadState newState) { downloadState = newState; }); item->setDownloadDirectory(tempDir.path()); item->accept(); }); // trigger the download link that becomes focused on page4 QTest::qWait(1000); QTest::sendKeyEvent(QTest::Press, view.focusProxy(), Qt::Key_Return, QString("\r"), Qt::NoModifier); QTest::sendKeyEvent(QTest::Release, view.focusProxy(), Qt::Key_Return, QString("\r"), Qt::NoModifier); // Wait for 10 seconds (abort waiting if another loadStarted or loadFinished occurs) QTRY_LOOP_IMPL((loadStartedSpy.size() != 1) || (loadFinishedSpy.size() != 1), 10000, 100); // Download must have occurred QTRY_COMPARE(downloadState, QWebEngineDownloadItem::DownloadCompleted); // No further loadStarted should have occurred within this time QCOMPARE(loadStartedSpy.size(), 1); QCOMPARE(loadFinishedSpy.size(), 1); } void tst_LoadSignals::numberOfStartedAndFinishedSignalsIsSame() { HttpServer server; server.setResourceDirs({ TESTS_SOURCE_DIR "/qwebengineprofile/resources" }); connect(&server, &HttpServer::newRequest, [] (HttpReqRep *) { QTest::qWait(250); // just add delay to trigger some progress for every sub resource }); QVERIFY(server.start()); view.load(server.url("/hedgehog.png")); QTRY_COMPARE(loadFinishedSpy.size(), 1); QVERIFY(loadFinishedSpy[0][0].toBool()); loadStartedSpy.clear(); loadFinishedSpy.clear(); loadProgressSpy.clear(); view.page()->setHtml("" "" "
" "" ""); QTRY_COMPARE(loadStartedSpy.size(), 2); QTRY_COMPARE(loadFinishedSpy.size(), 2); QTRY_VERIFY(!loadFinishedSpy[0][0].toBool()); QTRY_VERIFY(loadFinishedSpy[1][0].toBool()); view.page()->setHtml("" "" "" ""); QTRY_COMPARE(loadStartedSpy.size(), 4); QTRY_COMPARE(loadFinishedSpy.size(), 4); QVERIFY(loadFinishedSpy[2][0].toBool()); QVERIFY(loadFinishedSpy[3][0].toBool()); } void tst_LoadSignals::loadFinishedAfterNotFoundError_data() { QTest::addColumn("rfcInvalid"); QTest::addColumn("withServer"); QTest::addRow("rfc_invalid") << true << false; QTest::addRow("non_existent") << false << false; QTest::addRow("server_404") << false << true; } void tst_LoadSignals::loadFinishedAfterNotFoundError() { QFETCH(bool, withServer); QFETCH(bool, rfcInvalid); QScopedPointer server; if (withServer) { server.reset(new HttpServer); QVERIFY(server->start()); } view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); auto url = server ? server->url("/not-found-page.html") : QUrl(rfcInvalid ? "http://some.invalid" : "http://non.existent/url"); view.load(url); QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 20000); QVERIFY(!loadFinishedSpy.at(0).at(0).toBool()); QCOMPARE(toPlainTextSync(view.page()), QString()); QCOMPARE(loadFinishedSpy.count(), 1); view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, true); url = server ? server->url("/another-missing-one.html") : QUrl(rfcInvalid ? "http://some.other.invalid" : "http://another.non.existent/url"); view.load(url); QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.count(), 2, 20000); QVERIFY(!loadFinishedSpy.at(1).at(0).toBool()); QEXPECT_FAIL("", "No more loads (like separate load for error pages) are expected", Continue); QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.count(), 3, 1000); } void tst_LoadSignals::errorPageTriggered_data() { QTest::addColumn("urlPath"); QTest::addColumn("loadSucceed"); QTest::addColumn("triggersErrorPage"); QTest::newRow("/content/200") << QStringLiteral("/content/200") << true << false; QTest::newRow("/empty/200") << QStringLiteral("/content/200") << true << false; QTest::newRow("/content/404") << QStringLiteral("/content/404") << false << false; QTest::newRow("/empty/404") << QStringLiteral("/empty/404") << false << true; } void tst_LoadSignals::errorPageTriggered() { HttpServer server; connect(&server, &HttpServer::newRequest, [] (HttpReqRep *rr) { QList parts = rr->requestPath().split('/'); if (parts.length() != 3) { // For example, /favicon.ico rr->sendResponse(404); return; } bool isDocumentEmpty = (parts[1] == "empty"); int httpStatusCode = parts[2].toInt(); rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html")); if (!isDocumentEmpty) { rr->setResponseBody(QByteArrayLiteral("")); } rr->sendResponse(httpStatusCode); }); QVERIFY(server.start()); QFETCH(QString, urlPath); QFETCH(bool, loadSucceed); QFETCH(bool, triggersErrorPage); view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, true); view.load(server.url(urlPath)); QTRY_COMPARE(loadFinishedSpy.size(), 1); QCOMPARE(loadFinishedSpy[0][0].toBool(), loadSucceed); if (triggersErrorPage) QVERIFY(toPlainTextSync(view.page()).contains("HTTP ERROR 404")); else QVERIFY(toPlainTextSync(view.page()).isEmpty()); loadFinishedSpy.clear(); view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); view.load(server.url(urlPath)); QTRY_COMPARE(loadFinishedSpy.size(), 1); QCOMPARE(loadFinishedSpy[0][0].toBool(), loadSucceed); QVERIFY(toPlainTextSync(view.page()).isEmpty()); loadFinishedSpy.clear(); } QTEST_MAIN(tst_LoadSignals) #include "tst_loadsignals.moc"