diff options
Diffstat (limited to 'tests/auto/widgets/qwebenginepage')
-rw-r--r-- | tests/auto/widgets/qwebenginepage/qwebenginepage.pro | 1 | ||||
-rw-r--r-- | tests/auto/widgets/qwebenginepage/resources/fullscreen.html | 10 | ||||
-rw-r--r-- | tests/auto/widgets/qwebenginepage/resources/image.png | bin | 0 -> 14743 bytes | |||
-rw-r--r-- | tests/auto/widgets/qwebenginepage/resources/style.css | 1 | ||||
-rw-r--r-- | tests/auto/widgets/qwebenginepage/resources/test1.html | 1 | ||||
-rw-r--r-- | tests/auto/widgets/qwebenginepage/resources/test2.html | 1 | ||||
-rw-r--r-- | tests/auto/widgets/qwebenginepage/resources/testiframe.html | 53 | ||||
-rw-r--r-- | tests/auto/widgets/qwebenginepage/resources/testiframe2.html | 20 | ||||
-rw-r--r-- | tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp | 1785 | ||||
-rw-r--r-- | tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc | 9 |
10 files changed, 1650 insertions, 231 deletions
diff --git a/tests/auto/widgets/qwebenginepage/qwebenginepage.pro b/tests/auto/widgets/qwebenginepage/qwebenginepage.pro index e56bbe8f7..70786e70f 100644 --- a/tests/auto/widgets/qwebenginepage/qwebenginepage.pro +++ b/tests/auto/widgets/qwebenginepage/qwebenginepage.pro @@ -1,3 +1,2 @@ include(../tests.pri) -exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc QT *= core-private gui-private diff --git a/tests/auto/widgets/qwebenginepage/resources/fullscreen.html b/tests/auto/widgets/qwebenginepage/resources/fullscreen.html new file mode 100644 index 000000000..84771ca85 --- /dev/null +++ b/tests/auto/widgets/qwebenginepage/resources/fullscreen.html @@ -0,0 +1,10 @@ +<html> +<body onkeypress='onKeyPress()'> +<a>This is test content</a> +<script> +function onKeyPress() { + document.documentElement.webkitRequestFullScreen(); +} +</script> +</body> +</html> diff --git a/tests/auto/widgets/qwebenginepage/resources/image.png b/tests/auto/widgets/qwebenginepage/resources/image.png Binary files differnew file mode 100644 index 000000000..8d703640c --- /dev/null +++ b/tests/auto/widgets/qwebenginepage/resources/image.png diff --git a/tests/auto/widgets/qwebenginepage/resources/style.css b/tests/auto/widgets/qwebenginepage/resources/style.css new file mode 100644 index 000000000..c05b747f1 --- /dev/null +++ b/tests/auto/widgets/qwebenginepage/resources/style.css @@ -0,0 +1 @@ +#idP {color: red !important} diff --git a/tests/auto/widgets/qwebenginepage/resources/test1.html b/tests/auto/widgets/qwebenginepage/resources/test1.html new file mode 100644 index 000000000..b323f966e --- /dev/null +++ b/tests/auto/widgets/qwebenginepage/resources/test1.html @@ -0,0 +1 @@ +<html><body><p>Some text 1</p></body></html> diff --git a/tests/auto/widgets/qwebenginepage/resources/test2.html b/tests/auto/widgets/qwebenginepage/resources/test2.html new file mode 100644 index 000000000..63ac1f6ec --- /dev/null +++ b/tests/auto/widgets/qwebenginepage/resources/test2.html @@ -0,0 +1 @@ +<html><body> <p>Some text 2</p></body></html> diff --git a/tests/auto/widgets/qwebenginepage/resources/testiframe.html b/tests/auto/widgets/qwebenginepage/resources/testiframe.html new file mode 100644 index 000000000..4b0e30ca5 --- /dev/null +++ b/tests/auto/widgets/qwebenginepage/resources/testiframe.html @@ -0,0 +1,53 @@ +<html> +<head> +<title></title> +<style type="text/css"> +<!-- +#header { + background: #0f0; + position: absolute; + top: 0px; + left: 0px; + width: 800px; + height: 100px; +} +#content1 { + background: #ff0; + position: absolute; + top: 101px; + left: 0px; + width: 400px; + height: 400px; + overflow: scroll; +} +#content2 { + background: #ff7; + position: absolute; + top: 101px; + left: 401px; + width: 400px; + height: 400px; +} +#footer { + background: #0f0; + position: absolute; + top: 502px; + left: 0px; + width: 800px; + height: 200px; +} +--> +</style> +</head> +<body> +<div id="header"></div> +<div id="content1">You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible. +You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible. +You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible. +You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible. +You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible. +You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible.</div> +<iframe id="content2" name="control" src="testiframe2.html"> </iframe> +<div id="footer"></div> +</body> +</html>
\ No newline at end of file diff --git a/tests/auto/widgets/qwebenginepage/resources/testiframe2.html b/tests/auto/widgets/qwebenginepage/resources/testiframe2.html new file mode 100644 index 000000000..8957a5d8a --- /dev/null +++ b/tests/auto/widgets/qwebenginepage/resources/testiframe2.html @@ -0,0 +1,20 @@ +<html> +<head> +<title></title> +<style type="text/css"> +<!-- +#content { + background: #fff; + position: absolute; + top: 0px; + left: 0px; + width: 800px; + height: 800px; +} +--> +</style> +</head> +<body> +<div id="content"> </div> +</body> +</html>
\ No newline at end of file diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp index 9562871b3..ffd8ccba7 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp @@ -27,6 +27,7 @@ #include <QMainWindow> #include <QMenu> #include <QMimeDatabase> +#include <QPaintEngine> #include <QPushButton> #include <QStateMachine> #include <QStyle> @@ -37,8 +38,10 @@ #include <qnetworkreply.h> #include <qnetworkrequest.h> #include <qpa/qplatforminputcontext.h> +#include <qwebenginefullscreenrequest.h> #include <qwebenginehistory.h> #include <qwebenginepage.h> +#include <qwebengineprofile.h> #include <qwebenginesettings.h> #include <qwebengineview.h> #include <qimagewriter.h> @@ -95,6 +98,8 @@ public: tst_QWebEnginePage(); virtual ~tst_QWebEnginePage(); + bool eventFilter(QObject *watched, QEvent *event); + public Q_SLOTS: void init(); void cleanup(); @@ -108,6 +113,7 @@ private Q_SLOTS: void contextMenuPopulatedOnce(); void acceptNavigationRequest(); void acceptNavigationRequestNavigationType(); + void geolocationRequestJS_data(); void geolocationRequestJS(); void loadFinished(); void actionStates(); @@ -132,7 +138,6 @@ private Q_SLOTS: void textSelection(); void textEditing(); void backActionUpdate(); - void frameAt(); void protectBindingsRuntimeObjectsFromCollector(); void localURLSchemes(); void testOptionalJSObjects(); @@ -145,10 +150,7 @@ private Q_SLOTS: void inputMethodsTextFormat(); void defaultTextEncoding(); void errorPageExtension(); - void errorPageExtensionInIFrames(); - void errorPageExtensionInFrameset(); void errorPageExtensionLoadFinished(); - void userAgentApplicationName(); void userAgentNewlineStripping(); void undoActionHaveCustomText(); void renderWidgetHostViewNotShowTopLevel(); @@ -166,9 +168,6 @@ private Q_SLOTS: void unacceleratedWebGLScreenshotWithoutView(); #endif - void originatingObjectInNetworkRequests(); - void networkReplyParentDidntChange(); - void destroyQNAMBeforeAbortDoesntCrash(); void testJSPrompt(); void testStopScheduledPageRefresh(); void findText(); @@ -192,10 +191,57 @@ private Q_SLOTS: #endif void runJavaScript(); + void fullScreenRequested(); + + + // Tests from tst_QWebEngineFrame + void horizontalScrollAfterBack(); + void symmetricUrl(); + void progressSignal(); + void urlChange(); + void requestedUrlAfterSetAndLoadFailures(); + void javaScriptWindowObjectCleared_data(); + void javaScriptWindowObjectCleared(); + void javaScriptWindowObjectClearedOnEvaluate(); + void asyncAndDelete(); + void earlyToHtml(); + void setHtml(); + void setHtmlWithImageResource(); + void setHtmlWithStylesheetResource(); + void setHtmlWithBaseURL(); + void setHtmlWithJSAlert(); + void metaData(); +#if !defined(QT_NO_COMBOBOX) + void popupFocus(); +#endif + void inputFieldFocus(); + void hitTestContent(); + void baseUrl_data(); + void baseUrl(); + void renderHints(); + void scrollPosition(); + void scrollToAnchor(); + void scrollbarsOff(); + void evaluateWillCauseRepaint(); + void setContent_data(); + void setContent(); + void setCacheLoadControlAttribute(); + void setUrlWithPendingLoads(); + void setUrlToEmpty(); + void setUrlToInvalid(); + void setUrlHistory(); + void setUrlUsingStateObject(); + void setUrlThenLoads_data(); + void setUrlThenLoads(); + void loadFinishedAfterNotFoundError(); + void loadInSignalHandlers_data(); + void loadInSignalHandlers(); private: QWebEngineView* m_view; QWebEnginePage* m_page; + QWebEngineView* m_inputFieldsTestView; + int m_inputFieldTestPaintCount; QString tmpDirPath() const { static QString tmpd = QDir::tempPath() + "/tst_qwebenginepage-" @@ -212,10 +258,21 @@ tst_QWebEnginePage::~tst_QWebEnginePage() { } +bool tst_QWebEnginePage::eventFilter(QObject* watched, QEvent* event) +{ + // used on the inputFieldFocus test + if (watched == m_inputFieldsTestView) { + if (event->type() == QEvent::Paint) + m_inputFieldTestPaintCount++; + } + return QObject::eventFilter(watched, event); +} + void tst_QWebEnginePage::init() { m_view = new QWebEngineView(); m_page = m_view->page(); + m_page->settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); } void tst_QWebEnginePage::cleanup() @@ -278,7 +335,6 @@ void tst_QWebEnginePage::acceptNavigationRequest() m_view->setPage(0); } -#if defined(QWEBENGINEPAGE_SETFEATUREPERMISSION) class JSTestPage : public QWebEnginePage { Q_OBJECT @@ -291,12 +347,12 @@ public: return true; } public Q_SLOTS: - void requestPermission(QWebEngineFrame* frame, QWebEnginePage::Feature feature) + void requestPermission(const QUrl &origin, QWebEnginePage::Feature feature) { if (m_allowGeolocation) - setFeaturePermission(frame, feature, PermissionGrantedByUser); + setFeaturePermission(origin, feature, PermissionGrantedByUser); else - setFeaturePermission(frame, feature, PermissionDeniedByUser); + setFeaturePermission(origin, feature, PermissionDeniedByUser); } public: @@ -308,7 +364,6 @@ public: private: bool m_allowGeolocation; }; -#endif // [Qt] tst_QWebEnginePage::infiniteLoopJS() timeouts with DFG JIT // https://bugs.webkit.org/show_bug.cgi?id=79040 @@ -323,40 +378,38 @@ void tst_QWebEnginePage::infiniteLoopJS() } */ -void tst_QWebEnginePage::geolocationRequestJS() +void tst_QWebEnginePage::geolocationRequestJS_data() { -#if !defined(QWEBENGINEPAGE_SETFEATUREPERMISSION) - QSKIP("QWEBENGINEPAGE_SETFEATUREPERMISSION"); -#else - JSTestPage* newPage = new JSTestPage(m_view); + QTest::addColumn<bool>("allowed"); + QTest::addColumn<int>("errorCode"); + QTest::newRow("allowed") << true << 0; + QTest::newRow("not allowed") << false << 1; +} +void tst_QWebEnginePage::geolocationRequestJS() +{ + QFETCH(bool, allowed); + QFETCH(int, errorCode); + QWebEngineView *view = new QWebEngineView; + JSTestPage *newPage = new JSTestPage(view); + newPage->setView(view); + newPage->setGeolocationPermission(allowed); + + connect(newPage, SIGNAL(featurePermissionRequested(const QUrl&, QWebEnginePage::Feature)), + newPage, SLOT(requestPermission(const QUrl&, QWebEnginePage::Feature))); + + QSignalSpy spyLoadFinished(newPage, SIGNAL(loadFinished(bool))); + newPage->setHtml(QString("<html><body>test</body></html>"), QUrl()); + QTRY_COMPARE(spyLoadFinished.count(), 1); if (evaluateJavaScriptSync(newPage, QLatin1String("!navigator.geolocation")).toBool()) { - delete newPage; + delete view; W_QSKIP("Geolocation is not supported.", SkipSingle); } - connect(newPage, SIGNAL(featurePermissionRequested(QWebEngineFrame*, QWebEnginePage::Feature)), - newPage, SLOT(requestPermission(QWebEngineFrame*, QWebEnginePage::Feature))); - - newPage->setGeolocationPermission(false); - m_view->setPage(newPage); - m_view->setHtml(QString("<html><body>test</body></html>"), QUrl()); - evaluateJavaScriptSync(m_view->page(), "var errorCode = 0; function error(err) { errorCode = err.code; } function success(pos) { } navigator.geolocation.getCurrentPosition(success, error)"); - QTest::qWait(2000); - QVariant empty = evaluateJavaScriptSync(m_view->page(), "errorCode"); - - QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=102235", Continue); - QVERIFY(empty.type() == QVariant::Double && empty.toInt() != 0); + evaluateJavaScriptSync(newPage, "var errorCode = 0; function error(err) { errorCode = err.code; } function success(pos) { } navigator.geolocation.getCurrentPosition(success, error)"); - newPage->setGeolocationPermission(true); - evaluateJavaScriptSync(m_view->page(), "errorCode = 0; navigator.geolocation.getCurrentPosition(success, error);"); - empty = evaluateJavaScriptSync(m_view->page(), "errorCode"); - - //http://dev.w3.org/geo/api/spec-source.html#position - //PositionError: const unsigned short PERMISSION_DENIED = 1; - QVERIFY(empty.type() == QVariant::Double && empty.toInt() != 1); - delete newPage; -#endif + QTRY_COMPARE(evaluateJavaScriptSync(newPage, "errorCode").toInt(), errorCode); + delete view; } void tst_QWebEnginePage::loadFinished() @@ -1176,6 +1229,10 @@ public: int isSelectionCollapsed() { return evaluateJavaScriptSync(this, "window.getSelection().getRangeAt(0).collapsed").toBool(); } + bool hasSelection() + { + return !selectedText().isEmpty(); + } }; void tst_QWebEnginePage::cursorMovements() @@ -1380,17 +1437,19 @@ void tst_QWebEnginePage::cursorMovements() void tst_QWebEnginePage::textSelection() { -#if !defined(QWEBENGINEPAGE_EVALUATEJAVASCRIPT) - QSKIP("QWEBENGINEPAGE_EVALUATEJAVASCRIPT"); -#else - CursorTrackedPage* page = new CursorTrackedPage; + QWebEngineView *view = new QWebEngineView; + CursorTrackedPage *page = new CursorTrackedPage; QString content("<html><body><p id=one>The quick brown fox</p>" \ "<p id=two>jumps over the lazy dog</p>" \ "<p>May the source<br/>be with you!</p></body></html>"); + page->setView(view); + QSignalSpy loadSpy(view, SIGNAL(loadFinished(bool))); page->setHtml(content); + QTRY_COMPARE(loadSpy.count(), 1); // these actions must exist QVERIFY(page->action(QWebEnginePage::SelectAll) != 0); +#if defined(QWEBENGINEPAGE_SELECTACTIONS) QVERIFY(page->action(QWebEnginePage::SelectNextChar) != 0); QVERIFY(page->action(QWebEnginePage::SelectPreviousChar) != 0); QVERIFY(page->action(QWebEnginePage::SelectNextWord) != 0); @@ -1418,6 +1477,7 @@ void tst_QWebEnginePage::textSelection() QCOMPARE(page->action(QWebEnginePage::SelectEndOfBlock)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SelectStartOfDocument)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SelectEndOfDocument)->isEnabled(), false); +#endif // ..but SelectAll is awalys enabled QCOMPARE(page->action(QWebEnginePage::SelectAll)->isEnabled(), true); @@ -1432,13 +1492,15 @@ void tst_QWebEnginePage::textSelection() "getSelection().addRange(range);"; evaluateJavaScriptSync(page, selectScript); QCOMPARE(page->selectedText().trimmed(), QString::fromLatin1("The quick brown fox")); +#if defined(QWEBENGINEPAGE_SELECTEDHTML) QRegExp regExp(" style=\".*\""); regExp.setMinimal(true); QCOMPARE(page->selectedHtml().trimmed().replace(regExp, ""), QString::fromLatin1("<p id=\"one\">The quick brown fox</p>")); - +#endif // Make sure hasSelection returns true, since there is selected text now... QCOMPARE(page->hasSelection(), true); +#if defined(QWEBENGINEPAGE_SELECTACTIONS) // here the actions are enabled after a selection has been created QCOMPARE(page->action(QWebEnginePage::SelectNextChar)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectPreviousChar)->isEnabled(), true); @@ -1474,9 +1536,10 @@ void tst_QWebEnginePage::textSelection() QCOMPARE(page->action(QWebEnginePage::SelectEndOfBlock)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectStartOfDocument)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectEndOfDocument)->isEnabled(), true); +#endif delete page; -#endif + delete view; } void tst_QWebEnginePage::textEditing() @@ -1621,39 +1684,6 @@ void tst_QWebEnginePage::backActionUpdate() QVERIFY(action->isEnabled()); } -#if defined(QWEBENGINEFRAME) -void frameAtHelper(QWebEnginePage* webPage, QWebEngineFrame* webFrame, QPoint framePosition) -{ - if (!webFrame) - return; - - framePosition += QPoint(webFrame->pos()); - QList<QWebEngineFrame*> children = webFrame->childFrames(); - for (int i = 0; i < children.size(); ++i) { - if (children.at(i)->childFrames().size() > 0) - frameAtHelper(webPage, children.at(i), framePosition); - - QRect frameRect(children.at(i)->pos() + framePosition, children.at(i)->geometry().size()); - QVERIFY(children.at(i) == webPage->frameAt(frameRect.topLeft())); - } -} -#endif - -void tst_QWebEnginePage::frameAt() -{ -#if !defined(QWEBENGINEFRAME) - QSKIP("QWEBENGINEFRAME"); -#else - QWebEngineView webView; - QWebEnginePage* webPage = webView.page(); - QSignalSpy loadSpy(webPage, SIGNAL(loadFinished(bool))); - QUrl url = QUrl("qrc:///resources/iframe.html"); - webPage->load(url); - QTRY_COMPARE(loadSpy.count(), 1); - frameAtHelper(webPage, webPage->mainFrame(), webPage->mainFrame()->pos()); -#endif -} - void tst_QWebEnginePage::inputMethods_data() { QTest::addColumn<QString>("viewType"); @@ -2726,47 +2756,6 @@ void tst_QWebEnginePage::errorPageExtension() #endif } -void tst_QWebEnginePage::errorPageExtensionInIFrames() -{ -#if !defined(QWEBENGINEFRAME) - QSKIP("QWEBENGINEFRAME"); -#else - ErrorPage page; - m_view->setPage(&page); - - m_view->page()->load(QUrl( - "data:text/html," - "<h1>h1</h1>" - "<iframe src='data:text/html,<p/>p'></iframe>" - "<iframe src='http://non.existent/url'></iframe>")); - QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool))); - QTRY_COMPARE(spyLoadFinished.count(), 1); - - QCOMPARE(page.mainFrame()->childFrames()[1]->toPlainText(), QString("error")); - - m_view->setPage(0); -#endif -} - -void tst_QWebEnginePage::errorPageExtensionInFrameset() -{ -#if !defined(QWEBENGINEFRAME) - QSKIP("QWEBENGINEFRAME"); -#else - ErrorPage page; - m_view->setPage(&page); - - m_view->load(QUrl("qrc:///resources/index.html")); - - QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool))); - QTRY_COMPARE(spyLoadFinished.count(), 1); - QCOMPARE(page.mainFrame()->childFrames().count(), 2); - QCOMPARE(page.mainFrame()->childFrames()[1]->toPlainText(), QString("error")); - - m_view->setPage(0); -#endif -} - void tst_QWebEnginePage::errorPageExtensionLoadFinished() { #if !defined(QWEBENGINEPAGE_ERRORPAGEEXTENSION) @@ -2800,50 +2789,16 @@ void tst_QWebEnginePage::errorPageExtensionLoadFinished() #endif } -class FriendlyWebPage : public QWebEnginePage -{ -public: - friend class tst_QWebEnginePage; -}; - -void tst_QWebEnginePage::userAgentApplicationName() +void tst_QWebEnginePage::userAgentNewlineStripping() { -#if !defined(QWEBENGINEPAGE_USERAGENTFORURL) - QSKIP("QWEBENGINEPAGE_USERAGENTFORURL"); -#else - const QString oldApplicationName = QCoreApplication::applicationName(); - FriendlyWebPage page; + QWebEngineProfile profile; + QWebEnginePage page(&profile); - const QString applicationNameMarker = QString::fromUtf8("StrangeName\342\210\236"); - QCoreApplication::setApplicationName(applicationNameMarker); - QVERIFY(page.userAgentForUrl(QUrl()).contains(applicationNameMarker)); + profile.setHttpUserAgent(QStringLiteral("My User Agent\nX-New-Http-Header: Oh Noes!")); + // The user agent will be updated after a page load. + page.load(QUrl("about:blank")); - QCoreApplication::setApplicationName(oldApplicationName); -#endif -} - -class CustomUserAgentWebPage : public QWebEnginePage -{ -public: - static const QLatin1String filteredUserAgent; -protected: - virtual QString userAgentForUrl(const QUrl& url) const - { - Q_UNUSED(url); - return QString("My User Agent\nX-New-Http-Header: Oh Noes!"); - } -}; -const QLatin1String CustomUserAgentWebPage::filteredUserAgent("My User AgentX-New-Http-Header: Oh Noes!"); - -void tst_QWebEnginePage::userAgentNewlineStripping() -{ -#if !defined(QWEBENGINEPAGE_USERAGENTFORURL) - QSKIP("QWEBENGINEPAGE_USERAGENTFORURL"); -#else - CustomUserAgentWebPage page; - page.setHtml("<html><body></body></html>"); - QCOMPARE(evaluateJavaScriptSync(&page, "navigator.userAgent").toString(), CustomUserAgentWebPage::filteredUserAgent); -#endif + QCOMPARE(evaluateJavaScriptSync(&page, "navigator.userAgent").toString(), QStringLiteral("My User Agent X-New-Http-Header: Oh Noes!")); } void tst_QWebEnginePage::crashTests_LazyInitializationOfMainFrame() @@ -2951,65 +2906,6 @@ void tst_QWebEnginePage::unacceleratedWebGLScreenshotWithoutView() } #endif -void tst_QWebEnginePage::originatingObjectInNetworkRequests() -{ -#if !defined(QWEBENGINEFRAME) - QSKIP("QWEBENGINEFRAME"); -#else - TestNetworkManager* networkManager = new TestNetworkManager(m_page); - m_page->setNetworkAccessManager(networkManager); - networkManager->requests.clear(); - - m_view->setHtml(QString("<frameset cols=\"25%,75%\"><frame src=\"data:text/html," - "<head><meta http-equiv='refresh' content='1'></head>foo \">" - "<frame src=\"data:text/html,bar\"></frameset>"), QUrl()); - QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); - - QCOMPARE(networkManager->requests.count(), 2); - - QList<QWebEngineFrame*> childFrames = m_page->mainFrame()->childFrames(); - QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118660", Continue); - QCOMPARE(childFrames.count(), 2); - - for (int i = 0; i < 2; ++i) - QVERIFY(qobject_cast<QWebEngineFrame*>(networkManager->requests.at(i).originatingObject()) == childFrames.at(i)); -#endif -} - -void tst_QWebEnginePage::networkReplyParentDidntChange() -{ -#if !defined(QWEBENGINEPAGE_SETNETWORKACCESSMANAGER) - QSKIP("QWEBENGINEPAGE_SETNETWORKACCESSMANAGER"); -#else - TestNetworkManager* networkManager = new TestNetworkManager(m_page); - m_page->setNetworkAccessManager(networkManager); - networkManager->requests.clear(); - - // Trigger a load and check that pending QNetworkReplies haven't been reparented before returning to the event loop. - m_view->load(QUrl("qrc:///resources/content.html")); - - QVERIFY(networkManager->requests.count() > 0); - QVERIFY(networkManager->findChildren<QNetworkReply*>().size() > 0); -#endif -} - -void tst_QWebEnginePage::destroyQNAMBeforeAbortDoesntCrash() -{ -#if !defined(QWEBENGINEPAGE_SETNETWORKACCESSMANAGER) - QSKIP("QWEBENGINEPAGE_SETNETWORKACCESSMANAGER"); -#else - QNetworkAccessManager* networkManager = new QNetworkAccessManager; - m_page->setNetworkAccessManager(networkManager); - - m_view->load(QUrl("qrc:///resources/content.html")); - delete networkManager; - // This simulates what PingLoader does with its QNetworkReply when it times out. - // PingLoader isn't attached to a QWebEnginePage and can be kept alive - // for 60000 seconds (~16.7 hours) to then cancel its ResourceHandle. - m_view->stop(); -#endif -} - /** * Test fixups for https://bugs.webkit.org/show_bug.cgi?id=30914 * @@ -3109,13 +3005,16 @@ void tst_QWebEnginePage::testStopScheduledPageRefresh() void tst_QWebEnginePage::findText() { - m_view->setHtml(QString("<html><head></head><body><div>foo bar</div></body></html>")); -#if defined(QWEBENGINEPAGE_TRIGGERACTION_SELECTALL) + QSignalSpy loadSpy(m_page, SIGNAL(loadFinished(bool))); + m_page->setHtml(QString("<html><head></head><body><div>foo bar</div></body></html>")); + QTRY_COMPARE(loadSpy.count(), 1); m_page->triggerAction(QWebEnginePage::SelectAll); - QVERIFY(!m_page->selectedText().isEmpty()); + QTRY_COMPARE(m_page->hasSelection(), true); +#if defined(QWEBENGINEPAGE_SELECTEDHTML) QVERIFY(!m_page->selectedHtml().isEmpty()); #endif m_page->findText(""); + QEXPECT_FAIL("", "Unsupported: findText only highlights and doesn't update the selection.", Continue); QVERIFY(m_page->selectedText().isEmpty()); #if defined(QWEBENGINEPAGE_SELECTEDHTML) QVERIFY(m_page->selectedHtml().isEmpty()); @@ -3129,6 +3028,7 @@ void tst_QWebEnginePage::findText() QVERIFY(m_page->selectedHtml().contains(subString)); #endif m_page->findText(""); + QEXPECT_FAIL("", "Unsupported: findText only highlights and doesn't update the selection.", Continue); QVERIFY(m_page->selectedText().isEmpty()); #if defined(QWEBENGINEPAGE_SELECTEDHTML) QVERIFY(m_page->selectedHtml().isEmpty()); @@ -3674,41 +3574,115 @@ void tst_QWebEnginePage::cssMediaTypePageSetting() #endif } -class JavaScriptCallback +class JavaScriptCallbackBase +{ +public: + JavaScriptCallbackBase() + { + if (watcher) + QMetaObject::invokeMethod(watcher, "add"); + } + + void operator() (const QVariant &result) + { + check(result); + if (watcher) + QMetaObject::invokeMethod(watcher, "notify"); + } + +protected: + virtual void check(const QVariant &result) = 0; + +private: + friend class JavaScriptCallbackWatcher; + static QPointer<QObject> watcher; +}; + +QPointer<QObject> JavaScriptCallbackBase::watcher = 0; + +class JavaScriptCallback : public JavaScriptCallbackBase { public: JavaScriptCallback() { } JavaScriptCallback(const QVariant& _expected) : expected(_expected) { } - virtual void operator() (const QVariant& result) { + + void check(const QVariant& result) Q_DECL_OVERRIDE + { QVERIFY(result.isValid()); QCOMPARE(result, expected); } + private: QVariant expected; }; -class JavaScriptCallbackNull +class JavaScriptCallbackNull : public JavaScriptCallbackBase { public: - virtual void operator() (const QVariant& result) { + void check(const QVariant& result) Q_DECL_OVERRIDE + { QVERIFY(result.isNull()); // FIXME: Returned null values are currently invalid QVariants. // QVERIFY(result.isValid()); } }; -class JavaScriptCallbackUndefined +class JavaScriptCallbackUndefined : public JavaScriptCallbackBase { public: - virtual void operator() (const QVariant& result) { + void check(const QVariant& result) Q_DECL_OVERRIDE + { QVERIFY(result.isNull()); QVERIFY(!result.isValid()); } }; +class JavaScriptCallbackWatcher : public QObject +{ + Q_OBJECT +public: + JavaScriptCallbackWatcher() + { + Q_ASSERT(!JavaScriptCallbackBase::watcher); + JavaScriptCallbackBase::watcher = this; + } + + Q_INVOKABLE void add() + { + available++; + } + + Q_INVOKABLE void notify() + { + called++; + if (called == available) + emit allCalled(); + } + + bool wait(int maxSeconds = 30) + { + if (called == available) + return true; + + QTestEventLoop loop; + connect(this, SIGNAL(allCalled()), &loop, SLOT(exitLoop())); + loop.enterLoop(maxSeconds); + return !loop.timeout(); + } + +signals: + void allCalled(); + +private: + int available = 0; + int called = 0; +}; + + void tst_QWebEnginePage::runJavaScript() { TestPage page; + JavaScriptCallbackWatcher watcher; JavaScriptCallback callbackBool(QVariant(false)); page.runJavaScript("false", QWebEngineCallback<const QVariant&>(callbackBool)); @@ -3734,10 +3708,1363 @@ void tst_QWebEnginePage::runJavaScript() JavaScriptCallbackNull callbackNull; page.runJavaScript("null", QWebEngineCallback<const QVariant&>(callbackNull)); - JavaScriptCallbackNull callbackUndefined; + JavaScriptCallbackUndefined callbackUndefined; page.runJavaScript("undefined", QWebEngineCallback<const QVariant&>(callbackUndefined)); + QVERIFY(watcher.wait()); +} + +void tst_QWebEnginePage::fullScreenRequested() +{ + JavaScriptCallbackWatcher watcher; + QWebEnginePage* page = new QWebEnginePage; + QWebEngineView* view = new QWebEngineView; + view->setPage(page); + view->show(); + + page->settings()->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true); + + QSignalSpy loadSpy(view, SIGNAL(loadFinished(bool))); + page->load(QUrl("qrc:///resources/fullscreen.html")); + QTRY_COMPARE(loadSpy.count(), 1); + + page->runJavaScript("document.webkitFullscreenEnabled", JavaScriptCallback(true)); + page->runJavaScript("document.webkitIsFullScreen", JavaScriptCallback(false)); + QVERIFY(watcher.wait()); + + // FullscreenRequest must be a user gesture + bool acceptRequest = true; + connect(page, &QWebEnginePage::fullScreenRequested, + [&acceptRequest](const QWebEngineFullScreenRequest &request) { + if (acceptRequest) request.accept(); else request.reject(); + }); + + QTest::keyPress(qApp->focusWindow(), Qt::Key_Space); + QTest::qWait(100); + page->runJavaScript("document.webkitIsFullScreen", JavaScriptCallback(true)); + page->runJavaScript("document.webkitExitFullscreen()", JavaScriptCallbackUndefined()); + QVERIFY(watcher.wait()); + + acceptRequest = false; + + page->runJavaScript("document.webkitFullscreenEnabled", JavaScriptCallback(true)); + QTest::keyPress(qApp->focusWindow(), Qt::Key_Space); + QVERIFY(watcher.wait()); + page->runJavaScript("document.webkitIsFullScreen", JavaScriptCallback(false)); + QVERIFY(watcher.wait()); + + delete view; + delete page; +} + +void tst_QWebEnginePage::symmetricUrl() +{ + QWebEngineView view; + QSignalSpy loadFinishedSpy(view.page(), SIGNAL(loadFinished(bool))); + + QVERIFY(view.url().isEmpty()); + + QCOMPARE(view.history()->count(), 0); + + QUrl dataUrl("data:text/html,<h1>Test"); + + view.setUrl(dataUrl); + QCOMPARE(view.url(), dataUrl); + QCOMPARE(view.history()->count(), 0); + + // loading is _not_ immediate, so the text isn't set just yet. + QVERIFY(toPlainTextSync(view.page()).isEmpty()); + + QTRY_COMPARE(loadFinishedSpy.count(), 1); + + QCOMPARE(view.history()->count(), 1); + QCOMPARE(toPlainTextSync(view.page()), QString("Test")); + + QUrl dataUrl2("data:text/html,<h1>Test2"); + QUrl dataUrl3("data:text/html,<h1>Test3"); + + view.setUrl(dataUrl2); + view.setUrl(dataUrl3); + + QCOMPARE(view.url(), dataUrl3); + + QTRY_VERIFY(loadFinishedSpy.count() >= 2); + QTRY_COMPARE(loadFinishedSpy.count(), 3); + + QCOMPARE(view.history()->count(), 2); + + QCOMPARE(toPlainTextSync(view.page()), QString("Test3")); +} + +void tst_QWebEnginePage::progressSignal() +{ + QSignalSpy progressSpy(m_view, SIGNAL(loadProgress(int))); + + QUrl dataUrl("data:text/html,<h1>Test"); + m_view->setUrl(dataUrl); + + ::waitForSignal(m_view, SIGNAL(loadFinished(bool))); + + QVERIFY(progressSpy.size() >= 2); + int previousValue = -1; + for (QSignalSpy::ConstIterator it = progressSpy.begin(); it < progressSpy.end(); ++it) { + int current = (*it).first().toInt(); + QVERIFY(current >= previousValue); + previousValue = current; + } + + // But we always end at 100% + QCOMPARE(progressSpy.last().first().toInt(), 100); +} + +void tst_QWebEnginePage::urlChange() +{ + QSignalSpy urlSpy(m_page, SIGNAL(urlChanged(QUrl))); + + QUrl dataUrl("data:text/html,<h1>Test"); + m_view->setUrl(dataUrl); + + ::waitForSignal(m_page, SIGNAL(urlChanged(QUrl))); + + QCOMPARE(urlSpy.size(), 1); + + QUrl dataUrl2("data:text/html,<html><head><title>title</title></head><body><h1>Test</body></html>"); + m_view->setUrl(dataUrl2); + + ::waitForSignal(m_page, SIGNAL(urlChanged(QUrl))); + + QCOMPARE(urlSpy.size(), 2); +} + +class FakeReply : public QNetworkReply { + Q_OBJECT + +public: + static const QUrl urlFor404ErrorWithoutContents; + + FakeReply(const QNetworkRequest& request, QObject* parent = 0) + : QNetworkReply(parent) + { + setOperation(QNetworkAccessManager::GetOperation); + setRequest(request); + setUrl(request.url()); + if (request.url() == QUrl("qrc:/test1.html")) { + setHeader(QNetworkRequest::LocationHeader, QString("qrc:/test2.html")); + setAttribute(QNetworkRequest::RedirectionTargetAttribute, QUrl("qrc:/test2.html")); + QTimer::singleShot(0, this, SLOT(continueRedirect())); + } +#ifndef QT_NO_OPENSSL + else if (request.url() == QUrl("qrc:/fake-ssl-error.html")) { + setError(QNetworkReply::SslHandshakeFailedError, tr("Fake error!")); + QTimer::singleShot(0, this, SLOT(continueError())); + } +#endif + else if (request.url().host() == QLatin1String("abcdef.abcdef")) { + setError(QNetworkReply::HostNotFoundError, tr("Invalid URL")); + QTimer::singleShot(0, this, SLOT(continueError())); + } else if (request.url() == FakeReply::urlFor404ErrorWithoutContents) { + setError(QNetworkReply::ContentNotFoundError, "Not found"); + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 404); + QTimer::singleShot(0, this, SLOT(continueError())); + } + + open(QIODevice::ReadOnly); + } + ~FakeReply() + { + close(); + } + virtual void abort() {} + virtual void close() {} + +protected: + qint64 readData(char*, qint64) + { + return 0; + } + +private Q_SLOTS: + void continueRedirect() + { + emit metaDataChanged(); + emit finished(); + } + + void continueError() + { + emit error(this->error()); + emit finished(); + } +}; + +const QUrl FakeReply::urlFor404ErrorWithoutContents = QUrl("http://this.will/return-http-404-error-without-contents.html"); + +class FakeNetworkManager : public QNetworkAccessManager { + Q_OBJECT + +public: + FakeNetworkManager(QObject* parent) : QNetworkAccessManager(parent) { } + +protected: + virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, QIODevice* outgoingData) + { + QString url = request.url().toString(); + if (op == QNetworkAccessManager::GetOperation) { +#ifndef QT_NO_OPENSSL + if (url == "qrc:/fake-ssl-error.html") { + FakeReply* reply = new FakeReply(request, this); + QList<QSslError> errors; + emit sslErrors(reply, errors << QSslError(QSslError::UnspecifiedError)); + return reply; + } +#endif + if (url == "qrc:/test1.html" || url == "http://abcdef.abcdef/" || request.url() == FakeReply::urlFor404ErrorWithoutContents) + return new FakeReply(request, this); + } + + return QNetworkAccessManager::createRequest(op, request, outgoingData); + } +}; + +void tst_QWebEnginePage::requestedUrlAfterSetAndLoadFailures() +{ + QWebEnginePage page; + page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); + QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); + + const QUrl first("http://abcdef.abcdef/"); + page.setUrl(first); + ::waitForSignal(&page, SIGNAL(loadFinished(bool))); + QCOMPARE(spy.count(), 1); + QCOMPARE(page.url(), first); + QCOMPARE(page.requestedUrl(), first); + QVERIFY(!spy.at(0).first().toBool()); + + const QUrl second("http://abcdef.abcdef/another_page.html"); + QVERIFY(first != second); + + page.load(second); + ::waitForSignal(&page, SIGNAL(loadFinished(bool))); + QCOMPARE(spy.count(), 2); + QCOMPARE(page.url(), first); + QCOMPARE(page.requestedUrl(), second); + QVERIFY(!spy.at(1).first().toBool()); +} + +void tst_QWebEnginePage::javaScriptWindowObjectCleared_data() +{ + QTest::addColumn<QString>("html"); + QTest::addColumn<int>("signalCount"); + QTest::newRow("with <script>") << "<html><body><script>i=0</script><p>hello world</p></body></html>" << 1; + // NOTE: Empty scripts no longer cause this signal to be emitted. + QTest::newRow("with empty <script>") << "<html><body><script></script><p>hello world</p></body></html>" << 0; + QTest::newRow("without <script>") << "<html><body><p>hello world</p></body></html>" << 0; +} + +void tst_QWebEnginePage::javaScriptWindowObjectCleared() +{ +#if !defined(QWEBENGINEPAGE_JAVASCRIPTWINDOWOBJECTCLEARED) + QSKIP("QWEBENGINEPAGE_JAVASCRIPTWINDOWOBJECTCLEARED"); +#else + QWebEnginePage page; + QSignalSpy spy(&page, SIGNAL(javaScriptWindowObjectCleared())); + QFETCH(QString, html); + page.setHtml(html); + + QFETCH(int, signalCount); + QCOMPARE(spy.count(), signalCount); +#endif +} + +void tst_QWebEnginePage::javaScriptWindowObjectClearedOnEvaluate() +{ +#if !defined(QWEBENGINEPAGE_EVALUATEJAVASCRIPT) + QSKIP("QWEBENGINEPAGE_EVALUATEJAVASCRIPT"); +#else + QWebEnginePage page; + QSignalSpy spy(&page, SIGNAL(javaScriptWindowObjectCleared())); + page.setHtml("<html></html>"); + QCOMPARE(spy.count(), 0); + page.evaluateJavaScript("var a = 'a';"); + QCOMPARE(spy.count(), 1); + // no new clear for a new script: + page.evaluateJavaScript("var a = 1;"); + QCOMPARE(spy.count(), 1); +#endif +} + +void tst_QWebEnginePage::asyncAndDelete() +{ + QWebEnginePage *page = new QWebEnginePage; + CallbackSpy<QString> plainTextSpy; + CallbackSpy<QString> htmlSpy; + page->toPlainText(plainTextSpy.ref()); + page->toHtml(htmlSpy.ref()); + + delete page; + // Pending callbacks should be called with an empty value in the page's destructor. + QCOMPARE(plainTextSpy.waitForResult(), QString()); + QVERIFY(plainTextSpy.wasCalled()); + QCOMPARE(htmlSpy.waitForResult(), QString()); + QVERIFY(htmlSpy.wasCalled()); +} + +void tst_QWebEnginePage::earlyToHtml() +{ + QString html("<html><head></head><body></body></html>"); + QCOMPARE(toHtmlSync(m_view->page()), html); +} + +void tst_QWebEnginePage::setHtml() +{ + QString html("<html><head></head><body><p>hello world</p></body></html>"); + QSignalSpy spy(m_view->page(), SIGNAL(loadFinished(bool))); + m_view->page()->setHtml(html); + QVERIFY(spy.wait()); + QCOMPARE(toHtmlSync(m_view->page()), html); +} + +void tst_QWebEnginePage::setHtmlWithImageResource() +{ + // By default, only security origins of local files can load local resources. + // So we should specify baseUrl to be a local file in order to get a proper origin and load the local image. + + QLatin1String html("<html><body><p>hello world</p><img src='qrc:/resources/image.png'/></body></html>"); + QWebEnginePage page; + + page.setHtml(html, QUrl(QLatin1String("file:///path/to/file"))); + waitForSignal(&page, SIGNAL(loadFinished(bool)), 200); + + QCOMPARE(evaluateJavaScriptSync(&page, "document.images.length").toInt(), 1); + QCOMPARE(evaluateJavaScriptSync(&page, "document.images[0].width").toInt(), 128); + QCOMPARE(evaluateJavaScriptSync(&page, "document.images[0].height").toInt(), 128); + + // Now we test the opposite: without a baseUrl as a local file, we cannot request local resources. + + page.setHtml(html); + waitForSignal(&page, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(evaluateJavaScriptSync(&page, "document.images.length").toInt(), 1); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118659", Continue); + QCOMPARE(evaluateJavaScriptSync(&page, "document.images[0].width").toInt(), 0); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118659", Continue); + QCOMPARE(evaluateJavaScriptSync(&page, "document.images[0].height").toInt(), 0); +} + +void tst_QWebEnginePage::setHtmlWithStylesheetResource() +{ +#if !defined(QWEBENGINEELEMENT) + QSKIP("QWEBENGINEELEMENT"); +#else + // By default, only security origins of local files can load local resources. + // So we should specify baseUrl to be a local file in order to be able to download the local stylesheet. + + const char* htmlData = + "<html>" + "<head>" + "<link rel='stylesheet' href='qrc:/style.css' type='text/css' />" + "</head>" + "<body>" + "<p id='idP'>some text</p>" + "</body>" + "</html>"; + QLatin1String html(htmlData); + QWebEnginePage page; + QWebEngineElement webElement; + + page.setHtml(html, QUrl(QLatin1String("qrc:///file"))); + waitForSignal(&page, SIGNAL(loadFinished(bool)), 200); + webElement = page.documentElement().findFirst("p"); + QCOMPARE(webElement.styleProperty("color", QWebEngineElement::CascadedStyle), QLatin1String("red")); + + // Now we test the opposite: without a baseUrl as a local file, we cannot request local resources. + + page.setHtml(html, QUrl(QLatin1String("http://www.example.com/"))); + waitForSignal(&page, SIGNAL(loadFinished(bool)), 200); + webElement = page.documentElement().findFirst("p"); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118659", Continue); + QCOMPARE(webElement.styleProperty("color", QWebEngineElement::CascadedStyle), QString()); +#endif +} + +void tst_QWebEnginePage::setHtmlWithBaseURL() +{ + // This tests if baseUrl is indeed affecting the relative paths from resources. + // As we are using a local file as baseUrl, its security origin should be able to load local resources. + + if (!QDir(TESTS_SOURCE_DIR).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + + QDir::setCurrent(TESTS_SOURCE_DIR); + + QString html("<html><body><p>hello world</p><img src='resources/image2.png'/></body></html>"); + + QWebEnginePage page; + + // in few seconds, the image should be completey loaded + QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); + + page.setHtml(html, QUrl::fromLocalFile(TESTS_SOURCE_DIR)); + waitForSignal(&page, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy.count(), 1); + + QCOMPARE(evaluateJavaScriptSync(&page, "document.images.length").toInt(), 1); + QCOMPARE(evaluateJavaScriptSync(&page, "document.images[0].width").toInt(), 128); + QCOMPARE(evaluateJavaScriptSync(&page, "document.images[0].height").toInt(), 128); + + // no history item has to be added. + QCOMPARE(m_view->page()->history()->count(), 0); +} + +class MyPage : public QWebEnginePage +{ +public: + MyPage() : QWebEnginePage(), alerts(0) {} + int alerts; + +protected: + virtual void javaScriptAlert(const QUrl &securityOrigin, const QString &msg) + { + alerts++; + QCOMPARE(securityOrigin, QUrl(QStringLiteral("http://test.origin.com/"))); + QCOMPARE(msg, QString("foo")); + } +}; + +void tst_QWebEnginePage::setHtmlWithJSAlert() +{ + QString html("<html><head></head><body><script>alert('foo');</script><p>hello world</p></body></html>"); + MyPage page; + page.setHtml(html, QUrl(QStringLiteral("http://test.origin.com/path#fragment"))); + waitForSignal(&page, SIGNAL(loadFinished(bool))); + QCOMPARE(page.alerts, 1); + QCOMPARE(toHtmlSync(&page), html); +} + +void tst_QWebEnginePage::metaData() +{ +#if !defined(QWEBENGINEPAGE_METADATA) + QSKIP("QWEBENGINEPAGE_METADATA"); +#else + m_view->setHtml("<html>" + " <head>" + " <meta name=\"description\" content=\"Test description\">" + " <meta name=\"keywords\" content=\"HTML, JavaScript, Css\">" + " </head>" + "</html>"); + + QMultiMap<QString, QString> metaData = m_view->page()->metaData(); + + QCOMPARE(metaData.count(), 2); + + QCOMPARE(metaData.value("description"), QString("Test description")); + QCOMPARE(metaData.value("keywords"), QString("HTML, JavaScript, Css")); + QCOMPARE(metaData.value("nonexistent"), QString()); + + m_view->setHtml("<html>" + " <head>" + " <meta name=\"samekey\" content=\"FirstValue\">" + " <meta name=\"samekey\" content=\"SecondValue\">" + " </head>" + "</html>"); + + metaData = m_view->page()->metaData(); + + QCOMPARE(metaData.count(), 2); + + QStringList values = metaData.values("samekey"); + QCOMPARE(values.count(), 2); + + QVERIFY(values.contains("FirstValue")); + QVERIFY(values.contains("SecondValue")); + + QCOMPARE(metaData.value("nonexistent"), QString()); +#endif +} + +#if !defined(QT_NO_COMBOBOX) +void tst_QWebEnginePage::popupFocus() +{ +#if !defined(QWEBENGINEELEMENT) + QSKIP("QWEBENGINEELEMENT"); +#else + QWebEngineView view; + view.setHtml("<html>" + " <body>" + " <select name=\"select\">" + " <option>1</option>" + " <option>2</option>" + " </select>" + " <input type=\"text\"> </input>" + " <textarea name=\"text_area\" rows=\"3\" cols=\"40\">" + "This test checks whether showing and hiding a popup" + "takes the focus away from the webpage." + " </textarea>" + " </body>" + "</html>"); + view.resize(400, 100); + // Call setFocus before show to work around http://bugreports.qt.nokia.com/browse/QTBUG-14762 + view.setFocus(); + view.show(); + QTest::qWaitForWindowExposed(&view); + view.activateWindow(); + QTRY_VERIFY(view.hasFocus()); + + // open the popup by clicking. check if focus is on the popup + const QWebEngineElement webCombo = view.page()->documentElement().findFirst(QLatin1String("select[name=select]")); + QTest::mouseClick(&view, Qt::LeftButton, 0, webCombo.geometry().center()); + + QComboBox* combo = view.findChild<QComboBox*>(); + QVERIFY(combo != 0); + QTRY_VERIFY(!view.hasFocus() && combo->view()->hasFocus()); // Focus should be on the popup + + // hide the popup and check if focus is on the page + combo->hidePopup(); + QTRY_VERIFY(view.hasFocus()); // Focus should be back on the WebView +#endif +} +#endif + +void tst_QWebEnginePage::inputFieldFocus() +{ +#if !defined(QWEBENGINEELEMENT) + QSKIP("QWEBENGINEELEMENT"); +#else + QWebEngineView view; + view.setHtml("<html><body><input type=\"text\"></input></body></html>"); + view.resize(400, 100); + view.show(); + QTest::qWaitForWindowExposed(&view); + view.activateWindow(); + view.setFocus(); + QTRY_VERIFY(view.hasFocus()); + + // double the flashing time, should at least blink once already + int delay = qApp->cursorFlashTime() * 2; + + // focus the lineedit and check if it blinks + bool autoSipEnabled = qApp->autoSipEnabled(); + qApp->setAutoSipEnabled(false); + const QWebEngineElement inputElement = view.page()->documentElement().findFirst(QLatin1String("input[type=text]")); + QTest::mouseClick(&view, Qt::LeftButton, 0, inputElement.geometry().center()); + m_inputFieldsTestView = &view; + view.installEventFilter( this ); + QTest::qWait(delay); + QVERIFY2(m_inputFieldTestPaintCount >= 3, + "The input field should have a blinking caret"); + qApp->setAutoSipEnabled(autoSipEnabled); +#endif +} + +void tst_QWebEnginePage::hitTestContent() +{ +#if !defined(QWEBENGINEELEMENT) + QSKIP("QWEBENGINEELEMENT"); +#else + QString html("<html><body><p>A paragraph</p><br/><br/><br/><a href=\"about:blank\" target=\"_foo\" id=\"link\">link text</a></body></html>"); + + QWebEnginePage page; + page.setHtml(html); + page.setViewportSize(QSize(200, 0)); //no height so link is not visible + const QWebEngineElement linkElement = page.documentElement().findFirst(QLatin1String("a#link")); + QWebEngineHitTestResult result = page.hitTestContent(linkElement.geometry().center()); + QCOMPARE(result.linkText(), QString("link text")); + QWebEngineElement link = result.linkElement(); + QCOMPARE(link.attribute("target"), QString("_foo")); +#endif +} + +void tst_QWebEnginePage::baseUrl_data() +{ + QTest::addColumn<QString>("html"); + QTest::addColumn<QUrl>("loadUrl"); + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QUrl>("baseUrl"); + + QTest::newRow("null") << QString() << QUrl() + << QUrl("about:blank") << QUrl("about:blank"); + + QTest::newRow("foo") << QString() << QUrl("http://foobar.baz/") + << QUrl("http://foobar.baz/") << QUrl("http://foobar.baz/"); + + QString html = "<html>" + "<head>" + "<base href=\"http://foobaz.bar/\" />" + "</head>" + "</html>"; + QTest::newRow("customBaseUrl") << html << QUrl("http://foobar.baz/") + << QUrl("http://foobar.baz/") << QUrl("http://foobaz.bar/"); +} + +void tst_QWebEnginePage::baseUrl() +{ + QFETCH(QString, html); + QFETCH(QUrl, loadUrl); + QFETCH(QUrl, url); + QFETCH(QUrl, baseUrl); + + QSignalSpy loadSpy(m_page, SIGNAL(loadFinished(bool))); + m_page->setHtml(html, loadUrl); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(m_page->url(), url); + QEXPECT_FAIL("null", "Slight change: We now translate QUrl() to about:blank for the virtual url, but not for the baseUrl", Continue); + QCOMPARE(baseUrlSync(m_page), baseUrl); +} + +class DummyPaintEngine: public QPaintEngine { +public: + + DummyPaintEngine() + : QPaintEngine(QPaintEngine::AllFeatures) + , renderHints(0) + { + } + + bool begin(QPaintDevice*) + { + setActive(true); + return true; + } + + bool end() + { + setActive(false); + return false; + } + + void updateState(const QPaintEngineState& state) + { + renderHints = state.renderHints(); + } + + void drawPath(const QPainterPath&) { } + void drawPixmap(const QRectF&, const QPixmap&, const QRectF&) { } + + QPaintEngine::Type type() const + { + return static_cast<QPaintEngine::Type>(QPaintEngine::User + 2); + } + + QPainter::RenderHints renderHints; +}; + +class DummyPaintDevice: public QPaintDevice { +public: + DummyPaintDevice() + : QPaintDevice() + , m_engine(new DummyPaintEngine) + { + } + + ~DummyPaintDevice() + { + delete m_engine; + } + + QPaintEngine* paintEngine() const + { + return m_engine; + } + + QPainter::RenderHints renderHints() const + { + return m_engine->renderHints; + } + +protected: + int metric(PaintDeviceMetric metric) const; + +private: + DummyPaintEngine* m_engine; + friend class DummyPaintEngine; +}; + + +int DummyPaintDevice::metric(PaintDeviceMetric metric) const +{ + switch (metric) { + case PdmWidth: + return 400; + break; + + case PdmHeight: + return 200; + break; + + case PdmNumColors: + return INT_MAX; + break; + + case PdmDepth: + return 32; + break; + + default: + break; + } + return 0; +} + +void tst_QWebEnginePage::renderHints() +{ +#if !defined(QWEBENGINEPAGE_RENDER) + QSKIP("QWEBENGINEPAGE_RENDER"); +#else + QString html("<html><body><p>Hello, world!</p></body></html>"); + + QWebEnginePage page; + page.setHtml(html); + page.setViewportSize(page.contentsSize()); + + // We will call frame->render and trap the paint engine state changes + // to ensure that GraphicsContext does not clobber the render hints. + DummyPaintDevice buffer; + QPainter painter(&buffer); + + painter.setRenderHint(QPainter::TextAntialiasing, false); + page.render(&painter); + QVERIFY(!(buffer.renderHints() & QPainter::TextAntialiasing)); + QVERIFY(!(buffer.renderHints() & QPainter::SmoothPixmapTransform)); + QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing)); + + painter.setRenderHint(QPainter::TextAntialiasing, true); + page.render(&painter); + QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing); + QVERIFY(!(buffer.renderHints() & QPainter::SmoothPixmapTransform)); + QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing)); + + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + page.render(&painter); + QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing); + QVERIFY(buffer.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing)); + + painter.setRenderHint(QPainter::HighQualityAntialiasing, true); + page.render(&painter); + QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing); + QVERIFY(buffer.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(buffer.renderHints() & QPainter::HighQualityAntialiasing); +#endif +} + +void tst_QWebEnginePage::scrollPosition() +{ +#if !defined(QWEBENGINEPAGE_EVALUATEJAVASCRIPT) + QSKIP("QWEBENGINEPAGE_EVALUATEJAVASCRIPT"); +#else + // enlarged image in a small viewport, to provoke the scrollbars to appear + QString html("<html><body><img src='qrc:/image.png' height=500 width=500/></body></html>"); + + QWebEnginePage page; + page.setViewportSize(QSize(200, 200)); + + page.setHtml(html); + page.setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); + page.setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); + + // try to set the scroll offset programmatically + page.setScrollPosition(QPoint(23, 29)); + QCOMPARE(page.scrollPosition().x(), 23); + QCOMPARE(page.scrollPosition().y(), 29); + + int x = page.evaluateJavaScript("window.scrollX").toInt(); + int y = page.evaluateJavaScript("window.scrollY").toInt(); + QCOMPARE(x, 23); + QCOMPARE(y, 29); +#endif +} + +void tst_QWebEnginePage::scrollToAnchor() +{ +#if !defined(QWEBENGINEELEMENT) + QSKIP("QWEBENGINEELEMENT"); +#else + QWebEnginePage page; + page.setViewportSize(QSize(480, 800)); + + QString html("<html><body><p style=\"margin-bottom: 1500px;\">Hello.</p>" + "<p><a id=\"foo\">This</a> is an anchor</p>" + "<p style=\"margin-bottom: 1500px;\"><a id=\"bar\">This</a> is another anchor</p>" + "</body></html>"); + page.setHtml(html); + page.setScrollPosition(QPoint(0, 0)); + QCOMPARE(page.scrollPosition().x(), 0); + QCOMPARE(page.scrollPosition().y(), 0); + + QWebEngineElement fooAnchor = page.findFirstElement("a[id=foo]"); + + page.scrollToAnchor("foo"); + QCOMPARE(page.scrollPosition().y(), fooAnchor.geometry().top()); + + page.scrollToAnchor("bar"); + page.scrollToAnchor("foo"); + QCOMPARE(page.scrollPosition().y(), fooAnchor.geometry().top()); + + page.scrollToAnchor("top"); + QCOMPARE(page.scrollPosition().y(), 0); + + page.scrollToAnchor("bar"); + page.scrollToAnchor("notexist"); + QVERIFY(page.scrollPosition().y() != 0); +#endif +} + + +void tst_QWebEnginePage::scrollbarsOff() +{ +#if !defined(QWEBENGINEPAGE_EVALUATEJAVASCRIPT) + QSKIP("QWEBENGINEPAGE_EVALUATEJAVASCRIPT"); +#else + QWebEngineView view; + QWebEngineFrame* mainFrame = view.page(); + + mainFrame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); + mainFrame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); + + QString html("<script>" \ + " function checkScrollbar() {" \ + " if (innerWidth === document.documentElement.offsetWidth)" \ + " document.getElementById('span1').innerText = 'SUCCESS';" \ + " else" \ + " document.getElementById('span1').innerText = 'FAIL';" \ + " }" \ + "</script>" \ + "<body>" \ + " <div style='margin-top:1000px ; margin-left:1000px'>" \ + " <a id='offscreen' href='a'>End</a>" \ + " </div>" \ + "<span id='span1'></span>" \ + "</body>"); + + + QSignalSpy loadSpy(&view, SIGNAL(loadFinished(bool))); + view.setHtml(html); + ::waitForSignal(&view, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(loadSpy.count(), 1); + + mainFrame->evaluateJavaScript("checkScrollbar();"); + QCOMPARE(mainFrame->documentElement().findAll("span").at(0).toPlainText(), QString("SUCCESS")); +#endif +} + +void tst_QWebEnginePage::horizontalScrollAfterBack() +{ +#if !defined(QWEBENGINESETTINGS) + QSKIP("QWEBENGINESETTINGS"); +#else + QWebEngineView view; + QSignalSpy loadSpy(view.page(), SIGNAL(loadFinished(bool))); + + view.page()->settings()->setMaximumPagesInCache(2); + view.page()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAsNeeded); + view.page()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAsNeeded); + + view.load(QUrl("qrc:/resources/testiframe2.html")); + view.resize(200, 200); + QTRY_COMPARE(loadSpy.count(), 1); + QTRY_VERIFY((view.page()->scrollBarGeometry(Qt::Horizontal)).height()); + + view.load(QUrl("qrc:/resources/testiframe.html")); + QTRY_COMPARE(loadSpy.count(), 2); + + view.page()->triggerAction(QWebEnginePage::Back); + QTRY_COMPARE(loadSpy.count(), 3); + QTRY_VERIFY((view.page()->scrollBarGeometry(Qt::Horizontal)).height()); +#endif +} + +void tst_QWebEnginePage::evaluateWillCauseRepaint() +{ +#if !defined(QWEBENGINEPAGE_EVALUATEJAVASCRIPT) + QSKIP("QWEBENGINEPAGE_EVALUATEJAVASCRIPT"); +#else + QWebEngineView view; + QString html("<html><body>top<div id=\"junk\" style=\"display: block;\">" + "junk</div>bottom</body></html>"); + view.setHtml(html); + view.show(); + + QTest::qWaitForWindowExposed(&view); + view.page()->evaluateJavaScript( + "document.getElementById('junk').style.display = 'none';"); + + ::waitForSignal(view.page(), SIGNAL(repaintRequested(QRect))); +#endif +} + +void tst_QWebEnginePage::setContent_data() +{ + QTest::addColumn<QString>("mimeType"); + QTest::addColumn<QByteArray>("testContents"); + QTest::addColumn<QString>("expected"); + + QString str = QString::fromUtf8("ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει"); + QTest::newRow("UTF-8 plain text") << "text/plain; charset=utf-8" << str.toUtf8() << str; + + QTextCodec *utf16 = QTextCodec::codecForName("UTF-16"); + if (utf16) + QTest::newRow("UTF-16 plain text") << "text/plain; charset=utf-16" << utf16->fromUnicode(str) << str; + + str = QString::fromUtf8("Une chaîne de caractères à sa façon."); + QTest::newRow("latin-1 plain text") << "text/plain; charset=iso-8859-1" << str.toLatin1() << str; + + +} + +void tst_QWebEnginePage::setContent() +{ + QFETCH(QString, mimeType); + QFETCH(QByteArray, testContents); + QFETCH(QString, expected); + QSignalSpy loadSpy(m_page, SIGNAL(loadFinished(bool))); + m_view->setContent(testContents, mimeType); + QVERIFY(loadSpy.wait()); + QCOMPARE(toPlainTextSync(m_view->page()), expected); +} + +class CacheNetworkAccessManager : public QNetworkAccessManager { +public: + CacheNetworkAccessManager(QObject* parent = 0) + : QNetworkAccessManager(parent) + , m_lastCacheLoad(QNetworkRequest::PreferNetwork) + { + } + + virtual QNetworkReply* createRequest(Operation, const QNetworkRequest& request, QIODevice*) + { + QVariant cacheLoad = request.attribute(QNetworkRequest::CacheLoadControlAttribute); + if (cacheLoad.isValid()) + m_lastCacheLoad = static_cast<QNetworkRequest::CacheLoadControl>(cacheLoad.toUInt()); + else + m_lastCacheLoad = QNetworkRequest::PreferNetwork; // default value + return new FakeReply(request, this); + } + + QNetworkRequest::CacheLoadControl lastCacheLoad() const + { + return m_lastCacheLoad; + } + +private: + QNetworkRequest::CacheLoadControl m_lastCacheLoad; +}; + +void tst_QWebEnginePage::setCacheLoadControlAttribute() +{ +#if !defined(QWEBENGINEPAGE_SETNETWORKACCESSMANAGER) + QSKIP("QWEBENGINEPAGE_SETNETWORKACCESSMANAGER"); +#else + QWebEnginePage page; + CacheNetworkAccessManager* manager = new CacheNetworkAccessManager(&page); + page.setNetworkAccessManager(manager); + + QNetworkRequest request(QUrl("http://abcdef.abcdef/")); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache); + page.load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::AlwaysCache); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + page.load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::PreferCache); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); + page.load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::AlwaysNetwork); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork); + page.load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::PreferNetwork); +#endif +} + +void tst_QWebEnginePage::setUrlWithPendingLoads() +{ + QWebEnginePage page; + page.setHtml("<img src='dummy:'/>"); + page.setUrl(QUrl("about:blank")); +} + +void tst_QWebEnginePage::setUrlToEmpty() +{ + QSKIP("FIXME: [0908/090526:FATAL:navigation_controller_impl.cc(927)] Check failed: active_entry->site_instance() == rfh->GetSiteInstance()."); + + int expectedLoadFinishedCount = 0; + const QUrl aboutBlank("about:blank"); + const QUrl url("qrc:/resources/test2.html"); + + QWebEnginePage page; + QCOMPARE(page.url(), QUrl()); + QCOMPARE(page.requestedUrl(), QUrl()); + QCOMPARE(baseUrlSync(&page), QUrl()); + + QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); + + // Set existing url + page.setUrl(url); + expectedLoadFinishedCount++; + ::waitForSignal(&page, SIGNAL(loadFinished(bool))); + + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(page.url(), url); + QCOMPARE(page.requestedUrl(), url); + QCOMPARE(baseUrlSync(&page), url); + + // Set empty url + page.setUrl(QUrl()); + expectedLoadFinishedCount++; + + QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(page.url(), aboutBlank); + QCOMPARE(page.requestedUrl(), QUrl()); + QCOMPARE(baseUrlSync(&page), aboutBlank); + + // Set existing url + page.setUrl(url); + expectedLoadFinishedCount++; + + QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(page.url(), url); + QCOMPARE(page.requestedUrl(), url); + QCOMPARE(baseUrlSync(&page), url); + + // Load empty url + page.load(QUrl()); + expectedLoadFinishedCount++; + + QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(page.url(), aboutBlank); + QCOMPARE(page.requestedUrl(), QUrl()); + QCOMPARE(baseUrlSync(&page), aboutBlank); +} + +void tst_QWebEnginePage::setUrlToInvalid() +{ + QEXPECT_FAIL("", "Unsupported: QtWebEngine doesn't adjust invalid URLs.", Abort); + QVERIFY(false); + + QWebEnginePage page; + + const QUrl invalidUrl("http:/example.com"); + QVERIFY(!invalidUrl.isEmpty()); + QVERIFY(invalidUrl != QUrl()); + + // QWebEnginePage will do its best to accept the URL, possible converting it to a valid equivalent URL. + const QUrl validUrl("http://example.com/"); + page.setUrl(invalidUrl); + QCOMPARE(page.url(), validUrl); + QCOMPARE(page.requestedUrl(), validUrl); + QCOMPARE(baseUrlSync(&page), validUrl); + + // QUrls equivalent to QUrl() will be treated as such. + const QUrl aboutBlank("about:blank"); + const QUrl anotherInvalidUrl("1http://bugs.webkit.org"); + QVERIFY(!anotherInvalidUrl.isEmpty()); // and they are not necessarily empty. + QVERIFY(!anotherInvalidUrl.isValid()); + QCOMPARE(anotherInvalidUrl.toEncoded(), QUrl().toEncoded()); + + page.setUrl(anotherInvalidUrl); + QCOMPARE(page.url(), aboutBlank); + QCOMPARE(page.requestedUrl().toEncoded(), anotherInvalidUrl.toEncoded()); + QCOMPARE(baseUrlSync(&page), aboutBlank); +} + +static QStringList collectHistoryUrls(QWebEngineHistory *history) +{ + QStringList urls; + foreach (const QWebEngineHistoryItem &i, history->items()) + urls << i.url().toString(); + return urls; +} + +void tst_QWebEnginePage::setUrlHistory() +{ + QSKIP("FIXME: [0908/090526:FATAL:navigation_controller_impl.cc(927)] Check failed: active_entry->site_instance() == rfh->GetSiteInstance()."); + + const QUrl aboutBlank("about:blank"); + QUrl url; + int expectedLoadFinishedCount = 0; + QSignalSpy spy(m_page, SIGNAL(loadFinished(bool))); + + QCOMPARE(m_page->history()->count(), 0); + + m_page->setUrl(QUrl()); + expectedLoadFinishedCount++; + QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(m_page->url(), aboutBlank); + QCOMPARE(m_page->requestedUrl(), QUrl()); + // Chromium stores navigation entry for every successful loads. The load of the empty page is committed and stored as about:blank. + QCOMPARE(collectHistoryUrls(m_page->history()), QStringList() << aboutBlank.toString()); + + url = QUrl("http://non.existent/"); + m_page->setUrl(url); + expectedLoadFinishedCount++; + QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + // When error page is disabled in case of LoadFail the entry of the unavailable page is not stored. + // We expect the url of the previously loaded page here. + QCOMPARE(m_page->url(), aboutBlank); + QCOMPARE(m_page->requestedUrl(), QUrl()); + // Since the entry of the unavailable page is not stored it will not available in the history. + QCOMPARE(collectHistoryUrls(m_page->history()), QStringList() << aboutBlank.toString()); + + url = QUrl("qrc:/resources/test1.html"); + m_page->setUrl(url); + expectedLoadFinishedCount++; + QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(m_page->url(), url); + QCOMPARE(m_page->requestedUrl(), url); + QCOMPARE(collectHistoryUrls(m_page->history()), QStringList() << aboutBlank.toString() << QStringLiteral("qrc:/resources/test1.html")); + + m_page->setUrl(QUrl()); + expectedLoadFinishedCount++; + QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(m_page->url(), aboutBlank); + QCOMPARE(m_page->requestedUrl(), QUrl()); + // Chromium stores navigation entry for every successful loads. The load of the empty page is committed and stored as about:blank. + QCOMPARE(collectHistoryUrls(m_page->history()), QStringList() + << aboutBlank.toString() + << QStringLiteral("qrc:/resources/test1.html") + << aboutBlank.toString()); + + url = QUrl("qrc:/resources/test1.html"); + m_page->setUrl(url); + expectedLoadFinishedCount++; + QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(m_page->url(), url); + QCOMPARE(m_page->requestedUrl(), url); + // The history count DOES change since the about:blank is in the list. + QCOMPARE(collectHistoryUrls(m_page->history()), QStringList() + << aboutBlank.toString() + << QStringLiteral("qrc:/resources/test1.html") + << aboutBlank.toString() + << QStringLiteral("qrc:/resources/test1.html")); + + url = QUrl("qrc:/resources/test2.html"); + m_page->setUrl(url); + expectedLoadFinishedCount++; + QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(m_page->url(), url); + QCOMPARE(m_page->requestedUrl(), url); + QCOMPARE(collectHistoryUrls(m_page->history()), QStringList() + << aboutBlank.toString() + << QStringLiteral("qrc:/resources/test1.html") + << aboutBlank.toString() + << QStringLiteral("qrc:/resources/test1.html") + << QStringLiteral("qrc:/resources/test2.html")); +} + +void tst_QWebEnginePage::setUrlUsingStateObject() +{ + const QUrl aboutBlank("about:blank"); + QUrl url; + QSignalSpy urlChangedSpy(m_page, SIGNAL(urlChanged(QUrl))); + int expectedUrlChangeCount = 0; + + QCOMPARE(m_page->history()->count(), 0); + + url = QUrl("qrc:/resources/test1.html"); + m_page->setUrl(url); + waitForSignal(m_page, SIGNAL(loadFinished(bool))); + expectedUrlChangeCount++; + QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QCOMPARE(m_page->url(), url); + QCOMPARE(m_page->history()->count(), 1); + + evaluateJavaScriptSync(m_page, "window.history.pushState(null, 'push', 'navigate/to/here')"); + expectedUrlChangeCount++; + QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QCOMPARE(m_page->url(), QUrl("qrc:/resources/navigate/to/here")); + QCOMPARE(m_page->history()->count(), 2); + QVERIFY(m_page->history()->canGoBack()); + + evaluateJavaScriptSync(m_page, "window.history.replaceState(null, 'replace', 'another/location')"); + expectedUrlChangeCount++; + QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QCOMPARE(m_page->url(), QUrl("qrc:/resources/navigate/to/another/location")); + QCOMPARE(m_page->history()->count(), 2); + QVERIFY(!m_page->history()->canGoForward()); + QVERIFY(m_page->history()->canGoBack()); + + evaluateJavaScriptSync(m_page, "window.history.back()"); QTest::qWait(100); + expectedUrlChangeCount++; + QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QCOMPARE(m_page->url(), QUrl("qrc:/resources/test1.html")); + QVERIFY(m_page->history()->canGoForward()); + QVERIFY(!m_page->history()->canGoBack()); +} + +static inline QUrl extractBaseUrl(const QUrl& url) +{ + return url.resolved(QUrl()); +} + +void tst_QWebEnginePage::setUrlThenLoads_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QUrl>("baseUrl"); + + QTest::newRow("resource file") << QUrl("qrc:/resources/test1.html") << extractBaseUrl(QUrl("qrc:/resources/test1.html")); + QTest::newRow("base specified in HTML") << QUrl("data:text/html,<head><base href=\"http://different.base/\"></head>") << QUrl("http://different.base/"); +} + +void tst_QWebEnginePage::setUrlThenLoads() +{ + QFETCH(QUrl, url); + QFETCH(QUrl, baseUrl); + QSignalSpy urlChangedSpy(m_page, SIGNAL(urlChanged(QUrl))); + QSignalSpy startedSpy(m_page, SIGNAL(loadStarted())); + QSignalSpy finishedSpy(m_page, SIGNAL(loadFinished(bool))); + + m_page->setUrl(url); + QTRY_COMPARE(startedSpy.count(), 1); + QTRY_COMPARE(urlChangedSpy.count(), 1); + QTRY_COMPARE(finishedSpy.count(), 1); + QVERIFY(finishedSpy.at(0).first().toBool()); + QCOMPARE(m_page->url(), url); + QCOMPARE(m_page->requestedUrl(), url); + QCOMPARE(baseUrlSync(m_page), baseUrl); + + const QUrl urlToLoad1("qrc:/resources/test2.html"); + const QUrl urlToLoad2("qrc:/resources/test1.html"); + + // Just after first load. URL didn't changed yet. + m_page->load(urlToLoad1); + QCOMPARE(m_page->url(), url); + QCOMPARE(m_page->requestedUrl(), urlToLoad1); + // baseUrlSync spins an event loop and this sometimes return the next result. + // QCOMPARE(baseUrlSync(m_page), baseUrl); + QTRY_COMPARE(startedSpy.count(), 2); + + // After first URL changed. + QTRY_COMPARE(urlChangedSpy.count(), 2); + QTRY_COMPARE(finishedSpy.count(), 2); + QVERIFY(finishedSpy.at(1).first().toBool()); + QCOMPARE(m_page->url(), urlToLoad1); + QCOMPARE(m_page->requestedUrl(), urlToLoad1); + QCOMPARE(baseUrlSync(m_page), extractBaseUrl(urlToLoad1)); + + // Just after second load. URL didn't changed yet. + m_page->load(urlToLoad2); + QCOMPARE(m_page->url(), urlToLoad1); + QCOMPARE(m_page->requestedUrl(), urlToLoad2); + QCOMPARE(baseUrlSync(m_page), extractBaseUrl(urlToLoad1)); + QTRY_COMPARE(startedSpy.count(), 3); + + // After second URL changed. + QTRY_COMPARE(urlChangedSpy.count(), 3); + QTRY_COMPARE(finishedSpy.count(), 3); + QVERIFY(finishedSpy.at(2).first().toBool()); + QCOMPARE(m_page->url(), urlToLoad2); + QCOMPARE(m_page->requestedUrl(), urlToLoad2); + QCOMPARE(baseUrlSync(m_page), extractBaseUrl(urlToLoad2)); +} + +void tst_QWebEnginePage::loadFinishedAfterNotFoundError() +{ + QWebEnginePage page; + QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); + + page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); + page.setUrl(QUrl("http://non.existent/url")); + QTRY_COMPARE(spy.count(), 1); + + page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, true); + page.setUrl(QUrl("http://another.non.existent/url")); + QTRY_COMPARE(spy.count(), 2); +} + +class URLSetter : public QObject { + Q_OBJECT + +public: + enum Signal { + LoadStarted, + LoadFinished, + ProvisionalLoad + }; + + enum Type { + UseLoad, + UseSetUrl + }; + + URLSetter(QWebEnginePage*, Signal, Type, const QUrl&); + +public Q_SLOTS: + void execute(); + +Q_SIGNALS: + void finished(); + +private: + QWebEnginePage* m_page; + QUrl m_url; + Type m_type; +}; + +Q_DECLARE_METATYPE(URLSetter::Signal) +Q_DECLARE_METATYPE(URLSetter::Type) + +URLSetter::URLSetter(QWebEnginePage* page, Signal signal, URLSetter::Type type, const QUrl& url) + : m_page(page), m_url(url), m_type(type) +{ + if (signal == LoadStarted) + connect(m_page, SIGNAL(loadStarted()), SLOT(execute())); + else if (signal == LoadFinished) + connect(m_page, SIGNAL(loadFinished(bool)), SLOT(execute())); + else + connect(m_page, SIGNAL(provisionalLoad()), SLOT(execute())); +} + +void URLSetter::execute() +{ + // We track only the first emission. + m_page->disconnect(this); + if (m_type == URLSetter::UseLoad) + m_page->load(m_url); + else + m_page->setUrl(m_url); + connect(m_page, SIGNAL(loadFinished(bool)), SIGNAL(finished())); +} + +void tst_QWebEnginePage::loadInSignalHandlers_data() +{ + QSKIP("FIXME: This crashes in content::WebContentsImpl::NavigateToEntry because of reentrancy. Should we require QueuedConnections or do it ourselves to support this?"); + + QTest::addColumn<URLSetter::Type>("type"); + QTest::addColumn<URLSetter::Signal>("signal"); + QTest::addColumn<QUrl>("url"); + + const QUrl validUrl("qrc:/resources/test2.html"); + const QUrl invalidUrl("qrc:/invalid"); + + QTest::newRow("call load() in loadStarted() after valid url") << URLSetter::UseLoad << URLSetter::LoadStarted << validUrl; + QTest::newRow("call load() in loadStarted() after invalid url") << URLSetter::UseLoad << URLSetter::LoadStarted << invalidUrl; + QTest::newRow("call load() in loadFinished() after valid url") << URLSetter::UseLoad << URLSetter::LoadFinished << validUrl; + QTest::newRow("call load() in loadFinished() after invalid url") << URLSetter::UseLoad << URLSetter::LoadFinished << invalidUrl; + QTest::newRow("call load() in provisionalLoad() after valid url") << URLSetter::UseLoad << URLSetter::ProvisionalLoad << validUrl; + QTest::newRow("call load() in provisionalLoad() after invalid url") << URLSetter::UseLoad << URLSetter::ProvisionalLoad << invalidUrl; + + QTest::newRow("call setUrl() in loadStarted() after valid url") << URLSetter::UseSetUrl << URLSetter::LoadStarted << validUrl; + QTest::newRow("call setUrl() in loadStarted() after invalid url") << URLSetter::UseSetUrl << URLSetter::LoadStarted << invalidUrl; + QTest::newRow("call setUrl() in loadFinished() after valid url") << URLSetter::UseSetUrl << URLSetter::LoadFinished << validUrl; + QTest::newRow("call setUrl() in loadFinished() after invalid url") << URLSetter::UseSetUrl << URLSetter::LoadFinished << invalidUrl; + QTest::newRow("call setUrl() in provisionalLoad() after valid url") << URLSetter::UseSetUrl << URLSetter::ProvisionalLoad << validUrl; + QTest::newRow("call setUrl() in provisionalLoad() after invalid url") << URLSetter::UseSetUrl << URLSetter::ProvisionalLoad << invalidUrl; +} + +void tst_QWebEnginePage::loadInSignalHandlers() +{ + QFETCH(URLSetter::Type, type); + QFETCH(URLSetter::Signal, signal); + QFETCH(QUrl, url); + + const QUrl urlForSetter("qrc:/resources/test1.html"); + URLSetter setter(m_page, signal, type, urlForSetter); + + m_page->load(url); + waitForSignal(&setter, SIGNAL(finished()), 200); + QCOMPARE(m_page->url(), urlForSetter); } QTEST_MAIN(tst_QWebEnginePage) diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc index 994d71b43..c7bffd5bb 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc @@ -1,5 +1,6 @@ <!DOCTYPE RCC><RCC version="1.0"> <qresource> + <file>resources/content.html</file> <file>resources/index.html</file> <file>resources/frame_a.html</file> <file>resources/frame_c.html</file> @@ -7,8 +8,14 @@ <file>resources/iframe2.html</file> <file>resources/iframe3.html</file> <file>resources/framedindex.html</file> - <file>resources/content.html</file> + <file>resources/fullscreen.html</file> <file>resources/script.html</file> <file>resources/user.css</file> + <file>resources/image.png</file> + <file>resources/style.css</file> + <file>resources/test1.html</file> + <file>resources/test2.html</file> + <file>resources/testiframe.html</file> + <file>resources/testiframe2.html</file> </qresource> </RCC> |