diff options
Diffstat (limited to 'tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp')
-rw-r--r-- | tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp | 503 |
1 files changed, 275 insertions, 228 deletions
diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp index dbb15ba10..94ce92e40 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp @@ -158,6 +158,7 @@ private Q_SLOTS: void setHtmlWithStylesheetResource(); void setHtmlWithBaseURL(); void setHtmlWithJSAlert(); + void setHtmlWithModuleImport(); void baseUrl_data(); void baseUrl(); void scrollPosition(); @@ -224,9 +225,14 @@ private Q_SLOTS: void editActionsWithoutSelection(); void customUserAgentInNewTab(); + void renderProcessCrashed(); + void backgroundColor(); private: static QPoint elementCenter(QWebEnginePage *page, const QString &id); + static bool isFalseJavaScriptResult(QWebEnginePage *page, const QString &javaScript); + static bool isTrueJavaScriptResult(QWebEnginePage *page, const QString &javaScript); + static bool isEmptyListJavaScriptResult(QWebEnginePage *page, const QString &javaScript); QWebEngineView* m_view; QWebEnginePage* m_page; @@ -322,7 +328,7 @@ void tst_QWebEnginePage::acceptNavigationRequest() page.setHtml(QString("<html><body><form name='tstform' action='data:text/html,foo'method='get'>" "<input type='text'><input type='submit'></form></body></html>"), QUrl()); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 20000); evaluateJavaScriptSync(&page, "tstform.submit();"); QTRY_COMPARE(loadSpy.count(), 2); @@ -388,7 +394,7 @@ void tst_QWebEnginePage::geolocationRequestJS() QSignalSpy spyLoadFinished(newPage, SIGNAL(loadFinished(bool))); newPage->setHtml(QString("<html><body>test</body></html>"), QUrl("qrc://secure/origin")); - QTRY_COMPARE(spyLoadFinished.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(spyLoadFinished.count(), 1, 20000); // Geolocation is only enabled for visible WebContents. view.show(); @@ -415,7 +421,7 @@ void tst_QWebEnginePage::loadFinished() page.load(QUrl("data:text/html,<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>")); - QTRY_COMPARE(spyLoadFinished.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(spyLoadFinished.count(), 1, 20000); QEXPECT_FAIL("", "Behavior change: Load signals are emitted only for the main frame in QtWebEngine.", Continue); QTRY_VERIFY_WITH_TIMEOUT(spyLoadStarted.count() > 1, 100); @@ -475,9 +481,9 @@ void tst_QWebEnginePage::pasteImage() QClipboard *clipboard = QGuiApplication::clipboard(); clipboard->setImage(origImage); QWebEnginePage *page = m_view->page(); - page->load(QUrl("qrc:///resources/pasteimage.html")); QSignalSpy spyFinished(m_view, &QWebEngineView::loadFinished); - QVERIFY(spyFinished.wait()); + page->load(QUrl("qrc:///resources/pasteimage.html")); + QTRY_VERIFY_WITH_TIMEOUT(!spyFinished.isEmpty(), 20000); page->triggerAction(QWebEnginePage::Paste); QTRY_VERIFY(evaluateJavaScriptSync(page, "window.myImageDataURL ? window.myImageDataURL.length : 0").toInt() > 0); @@ -568,11 +574,11 @@ void tst_QWebEnginePage::acceptNavigationRequestNavigationType() QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); page.load(QUrl("qrc:///resources/script.html")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 20000); QTRY_COMPARE(page.navigations.count(), 1); page.load(QUrl("qrc:///resources/content.html")); - QTRY_COMPARE(loadSpy.count(), 2); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 2, 20000); QTRY_COMPARE(page.navigations.count(), 2); page.triggerAction(QWebEnginePage::Stop); @@ -587,7 +593,7 @@ void tst_QWebEnginePage::acceptNavigationRequestNavigationType() QTRY_COMPARE(page.navigations.count(), 4); page.load(QUrl("qrc:///resources/reload.html")); - QTRY_COMPARE(loadSpy.count(), 6); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 6, 20000); QTRY_COMPARE(page.navigations.count(), 6); QList<QWebEnginePage::NavigationType> expectedList; @@ -613,7 +619,7 @@ void tst_QWebEnginePage::popupFormSubmission() page.setHtml("<form name='form1' method=get action='' target='myNewWin'>" " <input type='hidden' name='foo' value='bar'>" "</form>"); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 20000); page.runJavaScript("window.open('', 'myNewWin', 'width=500,height=300,toolbar=0');"); evaluateJavaScriptSync(&page, "document.form1.submit();"); @@ -666,8 +672,8 @@ void tst_QWebEnginePage::multipleProfilesAndLocalStorage() page1.setHtml(QString("<html><body> </body></html>"), QUrl("http://wwww.example.com")); page2.setHtml(QString("<html><body> </body></html>"), QUrl("http://wwww.example.com")); - QTRY_COMPARE(loadSpy1.count(), 1); - QTRY_COMPARE(loadSpy2.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy1.count(), 1, 20000); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy2.count(), 1, 20000); evaluateJavaScriptSync(&page1, "localStorage.setItem('test', 'value1');"); evaluateJavaScriptSync(&page2, "localStorage.setItem('test', 'value2');"); @@ -695,7 +701,7 @@ public: CursorTrackedPage(QWidget *parent = 0): QWebEnginePage(parent) { } - QString selectedText() { + QString jsSelectedText() { return evaluateJavaScriptSync(this, "window.getSelection().toString()").toString(); } @@ -711,62 +717,91 @@ public: int isSelectionCollapsed() { return evaluateJavaScriptSync(this, "window.getSelection().getRangeAt(0).collapsed").toBool(); } - bool hasSelection() - { - return !selectedText().isEmpty(); - } }; void tst_QWebEnginePage::textSelection() { - QWebEngineView view; - CursorTrackedPage *page = new CursorTrackedPage(&view); - QString content("<html><body><p id=one>The quick brown fox</p>" \ + CursorTrackedPage page; + + QString textToSelect("The quick brown fox"); + QString content = QString("<html><body><p id=one>%1</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); + "<p>May the source<br/>be with you!</p></body></html>").arg(textToSelect); + + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + page.setHtml(content); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 20000); // these actions must exist - QVERIFY(page->action(QWebEnginePage::SelectAll) != 0); + QVERIFY(page.action(QWebEnginePage::SelectAll) != 0); // ..but SelectAll is disabled because the page has no focus due to disabled FocusOnNavigationEnabled. - QCOMPARE(page->action(QWebEnginePage::SelectAll)->isEnabled(), false); + QCOMPARE(page.action(QWebEnginePage::SelectAll)->isEnabled(), false); // Verify hasSelection returns false since there is no selection yet... - QCOMPARE(page->hasSelection(), false); + QVERIFY(!page.hasSelection()); + QVERIFY(page.jsSelectedText().isEmpty()); // this will select the first paragraph QString selectScript = "var range = document.createRange(); " \ "var node = document.getElementById(\"one\"); " \ "range.selectNode(node); " \ "getSelection().addRange(range);"; - evaluateJavaScriptSync(page, selectScript); - QCOMPARE(page->selectedText().trimmed(), QString::fromLatin1("The quick brown fox")); + evaluateJavaScriptSync(&page, selectScript); + // Make sure hasSelection returns true, since there is selected text now... - QCOMPARE(page->hasSelection(), true); + QTRY_VERIFY(page.hasSelection()); + QCOMPARE(page.selectedText().trimmed(), textToSelect); + + QCOMPARE(page.jsSelectedText().trimmed(), textToSelect); + + // navigate away and check that selection is cleared + page.load(QUrl("about:blank")); + QTRY_COMPARE(loadSpy.count(), 2); + + QVERIFY(!page.hasSelection()); + QVERIFY(page.selectedText().isEmpty()); + + QVERIFY(page.jsSelectedText().isEmpty()); } void tst_QWebEnginePage::backActionUpdate() { QWebEngineView view; + view.resize(640, 480); + view.show(); + QWebEnginePage *page = view.page(); + QSignalSpy loadSpy(page, &QWebEnginePage::loadFinished); QAction *action = page->action(QWebEnginePage::Back); QVERIFY(!action->isEnabled()); - QSignalSpy loadSpy(page, SIGNAL(loadFinished(bool))); - QUrl url = QUrl("qrc:///resources/framedindex.html"); - page->load(url); - QTRY_COMPARE(loadSpy.count(), 1); + + page->load(QUrl("qrc:///resources/framedindex.html")); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 20000); QVERIFY(!action->isEnabled()); - QTest::mouseClick(&view, Qt::LeftButton, 0, QPoint(10, 10)); - QEXPECT_FAIL("", "Behavior change: Load signals are emitted only for the main frame in QtWebEngine.", Continue); - QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 2, 100); - QEXPECT_FAIL("", "FIXME: Mouse events aren't passed from the QWebEngineView down to the RWHVQtDelegateWidget", Continue); - QVERIFY(action->isEnabled()); + auto firstAnchorCenterInFrame = [](QWebEnginePage *page, const QString &frameName) { + QVariantList rectList = evaluateJavaScriptSync(page, + "(function(){" + "var frame = document.getElementsByName('" + frameName + "')[0];" + "var anchor = frame.contentDocument.getElementsByTagName('a')[0];" + "var rect = anchor.getBoundingClientRect();" + "return [(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2];" + "})()").toList(); + + if (rectList.count() != 2) { + qWarning("firstAnchorCenterInFrame failed."); + return QPoint(); + } + + return QPoint(rectList.at(0).toInt(), rectList.at(1).toInt()); + }; + + QVERIFY(evaluateJavaScriptSync(page, "document.getElementsByName('frame_b')[0].contentDocument == undefined").toBool()); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, firstAnchorCenterInFrame(page, "frame_c")); + QTRY_VERIFY(evaluateJavaScriptSync(page, "document.getElementsByName('frame_b')[0].contentDocument != undefined").toBool()); + QTRY_VERIFY(action->isEnabled()); } void tst_QWebEnginePage::localStorageVisibility() @@ -782,18 +817,20 @@ void tst_QWebEnginePage::localStorageVisibility() QSignalSpy loadSpy2(&webPage2, &QWebEnginePage::loadFinished); webPage1.setHtml(QString("<html><body>test</body></html>"), QUrl("http://www.example.com/")); webPage2.setHtml(QString("<html><body>test</body></html>"), QUrl("http://www.example.com/")); - QTRY_COMPARE(loadSpy1.count(), 1); - QTRY_COMPARE(loadSpy2.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy1.count(), 1, 20000); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy2.count(), 1, 20000); // The attribute determines the visibility of the window.localStorage object. QVERIFY(evaluateJavaScriptSync(&webPage1, QString("(window.localStorage != undefined)")).toBool()); QVERIFY(!evaluateJavaScriptSync(&webPage2, QString("(window.localStorage != undefined)")).toBool()); - // Switching the feature off does not actively remove the object from webPage1. + // Toggle local setting for every page and... webPage1.settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, false); webPage2.settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, true); + // ...first check second page (for storage to appear) as applying settings is batched and done asynchronously + QTRY_VERIFY(evaluateJavaScriptSync(&webPage2, QString("(window.localStorage != undefined)")).toBool()); + // Switching the feature off does not actively remove the object from webPage1. QVERIFY(evaluateJavaScriptSync(&webPage1, QString("(window.localStorage != undefined)")).toBool()); - QVERIFY(evaluateJavaScriptSync(&webPage2, QString("(window.localStorage != undefined)")).toBool()); // The object disappears only after reloading. webPage1.triggerAction(QWebEnginePage::Reload); @@ -900,7 +937,7 @@ void tst_QWebEnginePage::testJSPrompt() bool res; QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); page.setHtml(QStringLiteral("<html><body></body></html>")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 20000); // OK + QString() res = evaluateJavaScriptSync(&page, @@ -990,6 +1027,19 @@ void tst_QWebEnginePage::findText() QTRY_COMPARE(signalSpy.count(), 1); QTRY_COMPARE(m_view->selectedText(), QString("foo")); } + + // Invoking startFinding operation for the same text twice. Without any wait, the second one + // should interrupt the first one. + { + QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); + m_view->findText("foo", 0); + m_view->findText("foo", 0); + QTRY_COMPARE(signalSpy.count(), 2); + QTRY_VERIFY(m_view->selectedText().isEmpty()); + + QCOMPARE(signalSpy.at(0).value(0).value<QWebEngineFindTextResult>().numberOfMatches(), 0); + QCOMPARE(signalSpy.at(1).value(0).value<QWebEngineFindTextResult>().numberOfMatches(), 1); + } } void tst_QWebEnginePage::findTextResult() @@ -999,7 +1049,7 @@ void tst_QWebEnginePage::findTextResult() if (findTextSpy.count() != 1) return QVector<int>({-1, -1}); auto r = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); - return QVector<int>({ r.numberOfMatches(), r.activeMatchOrdinal() }); + return QVector<int>({ r.numberOfMatches(), r.activeMatch() }); }; // findText will abort in blink if the view has an empty size. @@ -1037,7 +1087,7 @@ void tst_QWebEnginePage::findTextSuccessiveShouldCallAllCallbacks() CallbackSpy<bool> spy5; QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool))); m_view->setHtml(QString("<html><head></head><body><div>abcdefg abcdefg abcdefg abcdefg abcdefg</div></body></html>")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 20000); m_page->findText("abcde", 0, spy1.ref()); m_page->findText("abcd", 0, spy2.ref()); m_page->findText("abc", 0, spy3.ref()); @@ -1104,7 +1154,7 @@ void tst_QWebEnginePage::findTextActiveMatchOrdinal() QTRY_COMPARE(findTextSpy.count(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 3); - QCOMPARE(result.activeMatchOrdinal(), i); + QCOMPARE(result.activeMatch(), i); } // The last match is followed by the fist one. @@ -1112,28 +1162,28 @@ void tst_QWebEnginePage::findTextActiveMatchOrdinal() QTRY_COMPARE(findTextSpy.count(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 3); - QCOMPARE(result.activeMatchOrdinal(), 1); + QCOMPARE(result.activeMatch(), 1); // The first match is preceded by the last one. m_view->page()->findText("foo", QWebEnginePage::FindBackward); QTRY_COMPARE(findTextSpy.count(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 3); - QCOMPARE(result.activeMatchOrdinal(), 3); + QCOMPARE(result.activeMatch(), 3); - // Finding another word resets the activeMatchOrdinal. + // Finding another word resets the activeMatch. m_view->page()->findText("bar", 0); QTRY_COMPARE(findTextSpy.count(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 2); - QCOMPARE(result.activeMatchOrdinal(), 1); + QCOMPARE(result.activeMatch(), 1); - // If no match activeMatchOrdinal is 0. + // If no match activeMatch is 0. m_view->page()->findText("bla", 0); QTRY_COMPARE(findTextSpy.count(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 0); - QCOMPARE(result.activeMatchOrdinal(), 0); + QCOMPARE(result.activeMatch(), 0); } static QWindow *findNewTopLevelWindow(const QWindowList &oldTopLevelWindows) @@ -1177,6 +1227,12 @@ void tst_QWebEnginePage::comboBoxPopupPositionAfterMove() auto jsViewPosition = [&view]() { QLatin1String script("(function() { return [window.screenX, window.screenY]; })()"); QVariantList posList = evaluateJavaScriptSync(view.page(), script).toList(); + + if (posList.count() != 2) { + qWarning("jsViewPosition failed."); + return QPoint(); + } + return QPoint(posList.at(0).toInt(), posList.at(1).toInt()); }; @@ -1190,6 +1246,8 @@ void tst_QWebEnginePage::comboBoxPopupPositionAfterMove() QTRY_VERIFY(QGuiApplication::topLevelWindows().contains(popup)); QTRY_VERIFY(!popup->position().isNull()); QCOMPARE(popupPos + offset, popup->position()); + QTest::mouseClick(window, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(1, 1)); + QTRY_VERIFY(!QGuiApplication::topLevelWindows().contains(popup)); } void tst_QWebEnginePage::comboBoxPopupPositionAfterChildMove() @@ -1331,7 +1389,7 @@ void tst_QWebEnginePage::loadSignalsOrder() QSignalSpy spyLoadSpy(&loadSpy, &SpyForLoadSignalsOrder::started); QVERIFY(spyLoadSpy.wait(500)); page.load(url); - QTRY_VERIFY(loadSpy.isFinished()); + QTRY_VERIFY_WITH_TIMEOUT(loadSpy.isFinished(), 20000); } void tst_QWebEnginePage::renderWidgetHostViewNotShowTopLevel() @@ -1373,16 +1431,21 @@ public: load(QUrl("qrc:///resources/content.html")); } - void jsGetUserMedia(const QString & constraints) + void jsGetMedia(const QString &call) { evaluateJavaScriptSync(this, QStringLiteral( "var promiseFulfilled = false;" "var promiseRejected = false;" - "navigator.mediaDevices.getUserMedia(%1)" + "navigator.mediaDevices.%1" ".then(stream => { promiseFulfilled = true})" ".catch(err => { promiseRejected = true})") - .arg(constraints)); + .arg(call)); + } + + void jsGetUserMedia(const QString &constraints) + { + jsGetMedia(QStringLiteral("getUserMedia(%1)").arg(constraints)); } bool jsPromiseFulfilled() @@ -1439,43 +1502,45 @@ private: void tst_QWebEnginePage::getUserMediaRequest_data() { - QTest::addColumn<QString>("constraints"); + QTest::addColumn<QString>("call"); QTest::addColumn<QWebEnginePage::Feature>("feature"); QTest::addRow("device audio") - << "{audio: true}" << QWebEnginePage::MediaAudioCapture; + << "getUserMedia({audio: true})" << QWebEnginePage::MediaAudioCapture; QTest::addRow("device video") - << "{video: true}" << QWebEnginePage::MediaVideoCapture; + << "getUserMedia({video: true})" << QWebEnginePage::MediaVideoCapture; QTest::addRow("device audio+video") - << "{audio: true, video: true}" << QWebEnginePage::MediaAudioVideoCapture; + << "getUserMedia({audio: true, video: true})" << QWebEnginePage::MediaAudioVideoCapture; QTest::addRow("desktop video") - << "{video: { mandatory: { chromeMediaSource: 'desktop' }}}" + << "getUserMedia({video: { mandatory: { chromeMediaSource: 'desktop' }}})" << QWebEnginePage::DesktopVideoCapture; QTest::addRow("desktop audio+video") - << "{audio: { mandatory: { chromeMediaSource: 'desktop' }}, video: { mandatory: { chromeMediaSource: 'desktop' }}}" + << "getUserMedia({audio: { mandatory: { chromeMediaSource: 'desktop' }}, video: { mandatory: { chromeMediaSource: 'desktop' }}})" << QWebEnginePage::DesktopAudioVideoCapture; + QTest::addRow("display video") + << "getDisplayMedia()" << QWebEnginePage::DesktopVideoCapture; } void tst_QWebEnginePage::getUserMediaRequest() { - QFETCH(QString, constraints); + QFETCH(QString, call); QFETCH(QWebEnginePage::Feature, feature); GetUserMediaTestPage page; + QWebEngineView view; if (feature == QWebEnginePage::DesktopVideoCapture || feature == QWebEnginePage::DesktopAudioVideoCapture) { // Desktop capture needs to be on a desktop. - QWebEngineView view; view.setPage(&page); view.resize(640, 480); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); } - QTRY_VERIFY_WITH_TIMEOUT(page.loadSucceeded(), 20000); + QTRY_VERIFY_WITH_TIMEOUT(page.loadSucceeded(), 60000); page.settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true); // 1. Rejecting request on C++ side should reject promise on JS side. - page.jsGetUserMedia(constraints); + page.jsGetMedia(call); QTRY_VERIFY(page.gotFeatureRequest(feature)); page.rejectPendingRequest(); QTRY_VERIFY(!page.jsPromiseFulfilled() && page.jsPromiseRejected()); @@ -1485,13 +1550,13 @@ void tst_QWebEnginePage::getUserMediaRequest() // deeper in the content layer we cannot guarantee that the promise will // always be fulfilled, however in this case an error should be returned to // JS instead of leaving the Promise in limbo. - page.jsGetUserMedia(constraints); + page.jsGetMedia(call); QTRY_VERIFY(page.gotFeatureRequest(feature)); page.acceptPendingRequest(); QTRY_VERIFY(page.jsPromiseFulfilled() || page.jsPromiseRejected()); // 3. Media feature permissions are not remembered. - page.jsGetUserMedia(constraints); + page.jsGetMedia(call); QTRY_VERIFY(page.gotFeatureRequest(feature)); page.acceptPendingRequest(); QTRY_VERIFY(page.jsPromiseFulfilled() || page.jsPromiseRejected()); @@ -1652,156 +1717,51 @@ void tst_QWebEnginePage::openWindowDefaultSize() QCOMPARE(requestedGeometry.height(), 100); } -class JavaScriptCallbackBase +bool tst_QWebEnginePage::isFalseJavaScriptResult(QWebEnginePage *page, const QString &javaScript) { -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) { } - - void check(const QVariant& result) override - { - QVERIFY(result.isValid()); - QCOMPARE(result, expected); - } - -private: - QVariant expected; -}; - -class JavaScriptCallbackNull : public JavaScriptCallbackBase -{ -public: - void check(const QVariant& result) override - { - QVERIFY(result.isNull()); -// FIXME: Returned null values are currently invalid QVariants. -// QVERIFY(result.isValid()); - } -}; + QVariant result = evaluateJavaScriptSync(page, javaScript); + return !result.isNull() && result.isValid() && result == QVariant(false); +} -class JavaScriptCallbackUndefined : public JavaScriptCallbackBase +bool tst_QWebEnginePage::isTrueJavaScriptResult(QWebEnginePage *page, const QString &javaScript) { -public: - void check(const QVariant& result) override - { - QVERIFY(result.isNull()); - QVERIFY(!result.isValid()); - } -}; + QVariant result = evaluateJavaScriptSync(page, javaScript); + return !result.isNull() && result.isValid() && result == QVariant(true); +} -class JavaScriptCallbackWatcher : public QObject +bool tst_QWebEnginePage::isEmptyListJavaScriptResult(QWebEnginePage *page, const QString &javaScript) { - 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; -}; - + QVariant result = evaluateJavaScriptSync(page, javaScript); + return !result.isNull() && result.isValid() && result == QList<QVariant>(); +} void tst_QWebEnginePage::runJavaScript() { TestPage page; - JavaScriptCallbackWatcher watcher; - - JavaScriptCallback callbackBool(QVariant(false)); - page.runJavaScript("false", QWebEngineCallback<const QVariant&>(callbackBool)); - - JavaScriptCallback callbackInt(QVariant(2)); - page.runJavaScript("2", QWebEngineCallback<const QVariant&>(callbackInt)); - - JavaScriptCallback callbackDouble(QVariant(2.5)); - page.runJavaScript("2.5", QWebEngineCallback<const QVariant&>(callbackDouble)); - - JavaScriptCallback callbackString(QVariant(QStringLiteral("Test"))); - page.runJavaScript("\"Test\"", QWebEngineCallback<const QVariant&>(callbackString)); - - QVariantList list; - JavaScriptCallback callbackList(list); - page.runJavaScript("[]", QWebEngineCallback<const QVariant&>(callbackList)); - + QVariant result; QVariantMap map; - map.insert(QStringLiteral("test"), QVariant(2)); - JavaScriptCallback callbackMap(map); - page.runJavaScript("var el = {\"test\": 2}; el", QWebEngineCallback<const QVariant&>(callbackMap)); - JavaScriptCallbackNull callbackNull; - page.runJavaScript("null", QWebEngineCallback<const QVariant&>(callbackNull)); + QVERIFY(isFalseJavaScriptResult(&page, "false")); + QCOMPARE(evaluateJavaScriptSync(&page, "2").toInt(), 2); + QCOMPARE(evaluateJavaScriptSync(&page, "2.5").toDouble(), 2.5); + QCOMPARE(evaluateJavaScriptSync(&page, "\"Test\"").toString(), "Test"); + QVERIFY(isEmptyListJavaScriptResult(&page, "[]")); - JavaScriptCallbackUndefined callbackUndefined; - page.runJavaScript("undefined", QWebEngineCallback<const QVariant&>(callbackUndefined)); + map.insert(QStringLiteral("test"), QVariant(2)); + QCOMPARE(evaluateJavaScriptSync(&page, "var el = {\"test\": 2}; el").toMap(), map); - JavaScriptCallback callbackDate(QVariant(42.0)); - page.runJavaScript("new Date(42000)", QWebEngineCallback<const QVariant&>(callbackDate)); + QVERIFY(evaluateJavaScriptSync(&page, "null").isNull()); - JavaScriptCallback callbackBlob(QVariant(QByteArray(8, 0))); - page.runJavaScript("new ArrayBuffer(8)", QWebEngineCallback<const QVariant&>(callbackBlob)); + result = evaluateJavaScriptSync(&page, "undefined"); + QVERIFY(result.isNull() && !result.isValid()); - JavaScriptCallbackUndefined callbackFunction; - page.runJavaScript("(function(){})", QWebEngineCallback<const QVariant&>(callbackFunction)); + QCOMPARE(evaluateJavaScriptSync(&page, "new Date(42000)").toDate(), QVariant(42.0).toDate()); + QCOMPARE(evaluateJavaScriptSync(&page, "new ArrayBuffer(8)").toByteArray(), QByteArray(8, 0)); - JavaScriptCallback callbackPromise(QVariant(QVariantMap{})); - page.runJavaScript("new Promise(function(){})", QWebEngineCallback<const QVariant&>(callbackPromise)); + result = evaluateJavaScriptSync(&page, "(function(){})"); + QVERIFY(result.isNull() && !result.isValid()); - QVERIFY(watcher.wait()); + QCOMPARE(evaluateJavaScriptSync(&page, "new Promise(function(){})"), QVariant(QVariantMap{})); } void tst_QWebEnginePage::runJavaScriptDisabled() @@ -1812,7 +1772,7 @@ void tst_QWebEnginePage::runJavaScriptDisabled() // Settings changes take effect asynchronously. The load and wait ensure // that the settings are applied by the time we start to execute JavaScript. page.load(QStringLiteral("about:blank")); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 20000); QCOMPARE(evaluateJavaScriptSyncInWorld(&page, QStringLiteral("1+1"), QWebEngineScript::MainWorld), QVariant()); QCOMPARE(evaluateJavaScriptSyncInWorld(&page, QStringLiteral("1+1"), QWebEngineScript::ApplicationWorld), @@ -1824,29 +1784,26 @@ void tst_QWebEnginePage::runJavaScriptFromSlot() { QWebEngineProfile profile; QWebEnginePage page(&profile); - page.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); QSignalSpy loadFinishedSpy(&page, &QWebEnginePage::loadFinished); page.setHtml("<html><body>" " <input type='text' id='input1' value='QtWebEngine' size='50' />" "</body></html>"); QTRY_COMPARE(loadFinishedSpy.count(), 1); - // Workaround for QTBUG-74718 - QTRY_VERIFY(page.action(QWebEnginePage::SelectAll)->isEnabled()); - QVariant result(-1); + bool done = false; connect(&page, &QWebEnginePage::selectionChanged, [&]() { - result = evaluateJavaScriptSync(&page, QStringLiteral("2+2")); + QTRY_COMPARE(evaluateJavaScriptSync(&page, QStringLiteral("2+2")), QVariant(4)); + done = true; }); evaluateJavaScriptSync(&page, QStringLiteral("const input = document.getElementById('input1');" "input.focus();" "input.select();")); - QTRY_COMPARE(result, QVariant(4)); + QTRY_VERIFY(done); } void tst_QWebEnginePage::fullScreenRequested() { - JavaScriptCallbackWatcher watcher; QWebEngineView view; QWebEnginePage* page = view.page(); view.show(); @@ -1857,9 +1814,8 @@ void tst_QWebEnginePage::fullScreenRequested() 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()); + QTRY_VERIFY(isTrueJavaScriptResult(page, "document.webkitFullscreenEnabled")); + QVERIFY(isFalseJavaScriptResult(page, "document.webkitIsFullScreen")); // FullscreenRequest must be a user gesture bool acceptRequest = true; @@ -1869,17 +1825,15 @@ void tst_QWebEnginePage::fullScreenRequested() }); QTest::keyPress(view.focusProxy(), Qt::Key_Space); - QTRY_VERIFY(evaluateJavaScriptSync(page, "document.webkitIsFullScreen").toBool()); - page->runJavaScript("document.webkitExitFullscreen()", JavaScriptCallbackUndefined()); - QVERIFY(watcher.wait()); + QTRY_VERIFY(isTrueJavaScriptResult(page, "document.webkitIsFullScreen")); + page->runJavaScript("document.webkitExitFullscreen()"); + QTRY_VERIFY(isFalseJavaScriptResult(page, "document.webkitIsFullScreen")); acceptRequest = false; - page->runJavaScript("document.webkitFullscreenEnabled", JavaScriptCallback(true)); + QVERIFY(isTrueJavaScriptResult(page, "document.webkitFullscreenEnabled")); QTest::keyPress(view.focusProxy(), Qt::Key_Space); - QVERIFY(watcher.wait()); - page->runJavaScript("document.webkitIsFullScreen", JavaScriptCallback(false)); - QVERIFY(watcher.wait()); + QTRY_VERIFY(isFalseJavaScriptResult(page, "document.webkitIsFullScreen")); } void tst_QWebEnginePage::quotaRequested() @@ -1940,7 +1894,7 @@ void tst_QWebEnginePage::symmetricUrl() // loading is _not_ immediate, so the text isn't set just yet. QVERIFY(toPlainTextSync(view.page()).isEmpty()); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 20000); QCOMPARE(view.history()->count(), 1); QCOMPARE(toPlainTextSync(view.page()), QString("Test")); @@ -2012,7 +1966,8 @@ void tst_QWebEnginePage::urlChange() QUrl testUrl("http://test.qt.io/"); m_view->setHtml(QStringLiteral("<h1>Test</h1"), testUrl); - QTRY_COMPARE(urlSpy.size(), 1); + QTRY_COMPARE(urlSpy.size(), 2); + QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), QUrl("data:text/html;charset=UTF-8,%3Ch1%3ETest%3C%2Fh1")); QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), testUrl); } @@ -2270,6 +2225,41 @@ void tst_QWebEnginePage::setHtmlWithJSAlert() QCOMPARE(toHtmlSync(&page), html); } +void tst_QWebEnginePage::setHtmlWithModuleImport() +{ + HttpServer server; + connect(&server, &HttpServer::newRequest, [&](HttpReqRep *rr) { + if (rr->requestMethod() == "GET" && rr->requestPath() == "/fibonacci.mjs") { + rr->setResponseBody("export function fib(n) {\n" + " return n < 2 ? n : fib(n-1) + fib(n-2)\n" + "}\n"); + rr->setResponseHeader("Content-Type", "text/javascript"); + rr->sendResponse(); + } else { + rr->setResponseStatus(404); + rr->sendResponse(); + } + }); + QVERIFY(server.start()); + + QString html("<html>\n" + " <head>\n" + " <script type='module'>\n" + " import {fib} from './fibonacci.mjs'\n" + " window.fib7 = fib(7)\n" + " </script>\n" + " </head>\n" + " <body></body>\n" + "</html>\n"); + + QWebEnginePage page; + QSignalSpy spy(&page, &QWebEnginePage::loadFinished); + page.setHtml(html, server.url()); + QVERIFY(spy.count() || spy.wait()); + + QCOMPARE(evaluateJavaScriptSync(&page, "fib7"), QVariant(13)); +} + void tst_QWebEnginePage::baseUrl_data() { QTest::addColumn<QString>("html"); @@ -2718,14 +2708,14 @@ void tst_QWebEnginePage::setUrlUsingStateObject() evaluateJavaScriptSync(m_page, "window.history.pushState(null, 'push', 'navigate/to/here')"); expectedUrlChangeCount++; - QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QTRY_COMPARE(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); + QTRY_COMPARE(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()); @@ -2774,8 +2764,8 @@ void tst_QWebEnginePage::setUrlThenLoads() const QUrl urlToLoad2("qrc:/resources/test1.html"); m_page->load(urlToLoad1); - QCOMPARE(m_page->url(), urlToLoad1); - QCOMPARE(m_page->requestedUrl(), urlToLoad1); + QTRY_COMPARE(m_page->url(), urlToLoad1); + QTRY_COMPARE(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); @@ -2789,8 +2779,8 @@ void tst_QWebEnginePage::setUrlThenLoads() QCOMPARE(baseUrlSync(m_page), extractBaseUrl(urlToLoad1)); m_page->load(urlToLoad2); - QCOMPARE(m_page->url(), urlToLoad2); - QCOMPARE(m_page->requestedUrl(), urlToLoad2); + QTRY_COMPARE(m_page->url(), urlToLoad2); + QTRY_COMPARE(m_page->requestedUrl(), urlToLoad2); QCOMPARE(baseUrlSync(m_page), extractBaseUrl(urlToLoad1)); QTRY_COMPARE(startedSpy.count(), 3); @@ -4361,6 +4351,63 @@ void tst_QWebEnginePage::customUserAgentInNewTab() QCOMPARE(lastUserAgent, profile2.httpUserAgent().toUtf8()); } +void tst_QWebEnginePage::renderProcessCrashed() +{ + using Status = QWebEnginePage::RenderProcessTerminationStatus; + QWebEngineProfile profile; + QWebEnginePage page(&profile); + bool done = false; + Status status; + connect(&page, &QWebEnginePage::renderProcessTerminated, [&](Status newStatus) { + status = newStatus; + done = true; + }); + page.load(QUrl("chrome://crash")); + QTRY_VERIFY_WITH_TIMEOUT(done, 20000); + // The status depends on whether stack traces are enabled. With + // --disable-in-process-stack-traces we get an AbnormalTerminationStatus, + // otherwise a CrashedTerminationStatus. + QVERIFY(status == QWebEnginePage::CrashedTerminationStatus || + status == QWebEnginePage::AbnormalTerminationStatus); +} + +void tst_QWebEnginePage::backgroundColor() +{ + QWebEngineProfile profile; + QWebEngineView view; + QWebEnginePage *page = new QWebEnginePage(&profile, &view); + + view.resize(640, 480); + view.show(); + QPoint center(view.size().width() / 2, view.size().height() / 2); + + QCOMPARE(page->backgroundColor(), Qt::white); + QTRY_COMPARE(view.grab().toImage().pixelColor(center), Qt::white); + + page->setBackgroundColor(Qt::red); + view.setPage(page); + + QCOMPARE(page->backgroundColor(), Qt::red); + QTRY_COMPARE(view.grab().toImage().pixelColor(center), Qt::red); + + page->setHtml(QString("<html>" + "<head><style>html, body { margin:0; padding:0; }</style></head>" + "<body><div style=\"width:100%; height:10px; background-color:black\"/></body>" + "</html>")); + QSignalSpy spyFinished(page, &QWebEnginePage::loadFinished); + QVERIFY(spyFinished.wait()); + // Make sure the page is rendered and the test is not grabbing the color of the RenderWidgetHostViewQtDelegateWidget. + QTRY_COMPARE(view.grab().toImage().pixelColor(QPoint(5, 5)), Qt::black); + + QCOMPARE(page->backgroundColor(), Qt::red); + QCOMPARE(view.grab().toImage().pixelColor(center), Qt::red); + + page->setBackgroundColor(Qt::green); + + QCOMPARE(page->backgroundColor(), Qt::green); + QTRY_COMPARE(view.grab().toImage().pixelColor(center), Qt::green); +} + static QByteArrayList params = {QByteArrayLiteral("--use-fake-device-for-media-stream")}; W_QTEST_MAIN(tst_QWebEnginePage, params) |