diff options
Diffstat (limited to 'tests/auto/widgets')
28 files changed, 1279 insertions, 376 deletions
diff --git a/tests/auto/widgets/qwebengineaccessibility/tst_qwebengineaccessibility.cpp b/tests/auto/widgets/qwebengineaccessibility/tst_qwebengineaccessibility.cpp index 3d2c04486..759158a50 100644 --- a/tests/auto/widgets/qwebengineaccessibility/tst_qwebengineaccessibility.cpp +++ b/tests/auto/widgets/qwebengineaccessibility/tst_qwebengineaccessibility.cpp @@ -40,6 +40,8 @@ private Q_SLOTS: void hierarchy(); void text(); void value(); + void roles_data(); + void roles(); }; // This will be called before the first test function is executed. @@ -248,6 +250,175 @@ void tst_QWebEngineAccessibility::value() QCOMPARE(progressBarValueInterface->maximumValue().toInt(), 99); } +void tst_QWebEngineAccessibility::roles_data() +{ + QTest::addColumn<QString>("html"); + QTest::addColumn<bool>("isSection"); + QTest::addColumn<QAccessible::Role>("role"); + + QTest::newRow("AX_ROLE_ABBR") << QString("<abbr>a</abbr>") << false << QAccessible::StaticText; + QTest::newRow("AX_ROLE_ALERT") << QString("<div role='alert'>alert</div>") << true << QAccessible::AlertMessage; + QTest::newRow("AX_ROLE_ALERT_DIALOG") << QString("<div role='alertdialog'>alert</div>") << true << QAccessible::AlertMessage; + //QTest::newRow("AX_ROLE_ANCHOR") << QString("<a>target</a>") << false << QAccessible::Link; // FIXME: The test case might be wrong (see https://codereview.chromium.org/2713193003) + QTest::newRow("AX_ROLE_ANNOTATION") << QString("<rt>a</rt>") << false << QAccessible::StaticText; + QTest::newRow("AX_ROLE_APPLICATION") << QString("<div role='application'>landmark</div>") << true << QAccessible::Document; + QTest::newRow("AX_ROLE_ARTICLE") << QString("<article>a</article>") << true << QAccessible::Section; + QTest::newRow("AX_ROLE_AUDIO") << QString("<audio controls><source src='test.mp3' type='audio/mpeg'></audio>") << false << QAccessible::Sound; + QTest::newRow("AX_ROLE_BANNER") << QString("<header>a</header>") << true << QAccessible::Section; + QTest::newRow("AX_ROLE_BLOCKQUOTE") << QString("<blockquote>a</blockquote>") << true << QAccessible::Section; + //QTest::newRow("AX_ROLE_BUSY_INDICATOR"); // Deprecated + QTest::newRow("AX_ROLE_BUTTON") << QString("<button>a</button>") << false << QAccessible::Button; + //QTest::newRow("AX_ROLE_BUTTON_DROP_DOWN"); // Not a blink accessibility role + //QTest::newRow("AX_ROLE_CANVAS") << QString("<canvas width='10' height='10'></canvas>") << true << QAccessible::Canvas; // FIXME: The test case might be wrong (see AXLayoutObject.cpp) + QTest::newRow("AX_ROLE_CAPTION") << QString("<table><caption>a</caption></table>") << false << QAccessible::Heading; + //QTest::newRow("AX_ROLE_CARET"); // Not a blink accessibility role + //QTest::newRow("AX_ROLE_CELL") << QString("<td role='cell'>a</td>") << true << QAccessible::Cell; // FIXME: Aria role 'cell' should work for <td> + QTest::newRow("AX_ROLE_CHECK_BOX") << QString("<input type='checkbox'>a</input>") << false << QAccessible::CheckBox; + QTest::newRow("AX_ROLE_CLIENT") << QString("") << true << QAccessible::Client; + QTest::newRow("AX_ROLE_COLOR_WELL") << QString("<input type='color'>a</input>") << false << QAccessible::ColorChooser; + //QTest::newRow("AX_ROLE_COLUMN") << QString("<table><tr><td>a</td></tr>") << true << QAccessible::Column; // FIXME: The test case might be wrong (see AXTableColumn.h) + QTest::newRow("AX_ROLE_COLUMN_HEADER") << QString("<div role='columnheader'>a</div>") << true << QAccessible::ColumnHeader; + QTest::newRow("AX_ROLE_COMBO_BOX") << QString("<select><option>a</option></select>") << false << QAccessible::ComboBox; + QTest::newRow("AX_ROLE_COMPLEMENTARY") << QString("<aside>a</aside>") << true << QAccessible::ComplementaryContent; + QTest::newRow("AX_ROLE_CONTENT_INFO") << QString("<address>a</address>") << true << QAccessible::Section; + QTest::newRow("AX_ROLE_DATE") << QString("<input type='date'></input>") << false << QAccessible::Clock; + QTest::newRow("AX_ROLE_DATE_TIME") << QString("<input type='datetime-local'></input>") << false << QAccessible::Clock; + QTest::newRow("AX_ROLE_DEFINITION") << QString("<div role='definition'>landmark</div>") << true << QAccessible::Paragraph; + QTest::newRow("AX_ROLE_DESCRIPTION_LIST") << QString("<dl>a</dl>") << true << QAccessible::List; + QTest::newRow("AX_ROLE_DESCRIPTION_LIST_DETAIL") << QString("<dd>a</dd>") << true << QAccessible::Paragraph; + QTest::newRow("AX_ROLE_DESCRIPTION_LIST_TERM") << QString("<dt>a</dt>") << true << QAccessible::ListItem; + QTest::newRow("AX_ROLE_DETAILS") << QString("<details>a</details>") << true << QAccessible::Grouping; + //QTest::newRow("AX_ROLE_DESKTOP"); // Not a blink accessibility role + QTest::newRow("AX_ROLE_DIALOG") << QString("<div role='dialog'></div>") << true << QAccessible::Dialog; + //QTest::newRow("AX_ROLE_DIRECTORY") << QString("<div role='directory'></div>") << true << QAccessible::NoRole; // FIXME: Aria role 'directory' should work + QTest::newRow("AX_ROLE_DISCLOSURE_TRIANGLE") << QString("<details><summary>a</summary></details>") << false << QAccessible::NoRole; + QTest::newRow("AX_ROLE_GENERIC_CONTAINER") << QString("<div>a</div>") << true << QAccessible::Section; + QTest::newRow("AX_ROLE_DOCUMENT") << QString("<div role='document'>a</div>") << true << QAccessible::Document; + QTest::newRow("AX_ROLE_EMBEDDED_OBJECT") << QString("<object width='10' height='10'></object>") << false << QAccessible::Grouping; + QTest::newRow("AX_ROLE_FEED") << QString("<div role='feed'>a</div>") << true << QAccessible::Section; + QTest::newRow("AX_ROLE_FIGCAPTION") << QString("<figcaption>a</figcaption>") << true << QAccessible::Heading; + QTest::newRow("AX_ROLE_FIGURE") << QString("<figure>a</figure>") << true << QAccessible::Section; + QTest::newRow("AX_ROLE_FOOTER") << QString("<footer>a</footer>") << true << QAccessible::Footer; + QTest::newRow("AX_ROLE_FORM") << QString("<form></form>") << true << QAccessible::Form; + QTest::newRow("AX_ROLE_GRID") << QString("<div role='grid'></div>") << true << QAccessible::Table; + QTest::newRow("AX_ROLE_GROUP") << QString("<fieldset></fieldset>") << true << QAccessible::Grouping; + QTest::newRow("AX_ROLE_HEADING") << QString("<h1>a</h1>") << true << QAccessible::Heading; + QTest::newRow("AX_ROLE_IFRAME") << QString("<iframe>a</iframe>") << true << QAccessible::Section; + QTest::newRow("AX_ROLE_IFRAME_PRESENTATIONAL") << QString("<iframe role='presentation'>a</iframe>") << false << QAccessible::NoRole; + //QTest::newRow("AX_ROLE_IGNORED") << QString("<tag>a</tag>") << true << QAccessible::NoRole; // FIXME: The HTML element should not be exposed as an element (see AXNodeObject.cpp) + QTest::newRow("AX_ROLE_IMAGE") << QString("<img>") << false << QAccessible::Graphic; + //QTest::newRow("AX_ROLE_IMAGE_MAP") << QString("<map>a</map>") << true << QAccessible::Graphic; // FIXME: The test case might be wrong (see AXLayoutObject.cpp) + //QTest::newRow("AX_ROLE_IMAGE_MAP_LINK") << QString("<map>a</map>") << true << QAccessible::Graphic; // FIXME: The test case might be wrong (see AXLayoutObject.cpp) + QTest::newRow("AX_ROLE_INLINE_TEXT_BOX") << QString("<textarea rows='4' cols='50'></textarea>") << false << QAccessible::EditableText; + QTest::newRow("AX_ROLE_INPUT_TIME") << QString("<input type='time'></input>") << false << QAccessible::SpinBox; + QTest::newRow("AX_ROLE_LABEL_TEXT") << QString("<label>a</label>") << false << QAccessible::StaticText; + QTest::newRow("AX_ROLE_LEGEND") << QString("<legend>a</legend>") << true << QAccessible::StaticText; + QTest::newRow("AX_ROLE_LINE_BREAK") << QString("<br>") << false << QAccessible::Separator; + QTest::newRow("AX_ROLE_LINK") << QString("<a href=''>link</a>") << false << QAccessible::Link; + QTest::newRow("AX_ROLE_LIST") << QString("<ul></ul>") << true << QAccessible::List; + QTest::newRow("AX_ROLE_LIST_BOX") << QString("<select role='listbox'></select>") << false << QAccessible::ComboBox; + QTest::newRow("AX_ROLE_LIST_BOX_OPTION") << QString("<option>a</option>") << true << QAccessible::ListItem; + QTest::newRow("AX_ROLE_LIST_ITEM") << QString("<li>a</li>") << true << QAccessible::ListItem; + QTest::newRow("AX_ROLE_LIST_MARKER") << QString("<li><ul></ul></li>") << false << QAccessible::StaticText; + //QTest::newRow("AX_ROLE_LOCATION_BAR"); // Not a blink accessibility role + QTest::newRow("AX_ROLE_LOG") << QString("<div role='log'>a</div>") << true << QAccessible::Section; + QTest::newRow("AX_ROLE_MAIN") << QString("<main>a</main>") << true << QAccessible::Grouping; + QTest::newRow("AX_ROLE_MARK") << QString("<mark>a</mark>") << false << QAccessible::StaticText; + QTest::newRow("AX_ROLE_MARQUEE") << QString("<div role='marquee'>a</div>") << true << QAccessible::Section; + QTest::newRow("AX_ROLE_MATH") << QString("<math>a</math>") << false << QAccessible::Equation; + QTest::newRow("AX_ROLE_MENU") << QString("<div role='menu'>a</div>") << true << QAccessible::PopupMenu; + QTest::newRow("AX_ROLE_MENU_BAR") << QString("<div role='menubar'>a</div>") << true << QAccessible::MenuBar; + QTest::newRow("AX_ROLE_MENU_ITEM") << QString("<menu role='menu'><input type='button' /></menu>") << false << QAccessible::MenuItem; + QTest::newRow("AX_ROLE_MENU_ITEM_CHECK_BOX") << QString("<menu role='menu'><input type='checkbox'></input></menu>") << false << QAccessible::CheckBox; + QTest::newRow("AX_ROLE_MENU_ITEM_RADIO") << QString("<menu role='menu'><input type='radio'></input></menu>") << false << QAccessible::RadioButton; + QTest::newRow("AX_ROLE_MENU_BUTTON") << QString("<menu role='group'><div role='menuitem'>a</div></menu>") << false << QAccessible::MenuItem; + //QTest::newRow("AX_ROLE_MENU_LIST_OPTION") << QString("<select role='menu'><option>a</option></select>") << false << QAccessible::MenuItem; // FIXME: <select> should be a menu list (see AXMenuListOption.cpp) + //QTest::newRow("AX_ROLE_MENU_LIST_POPUP") << QString("<menu type='context'>a</menu>") << true << QAccessible::PopupMenu; // FIXME: <menu> is not fully supported by Chromium (see AXMenuListPopup.h) + QTest::newRow("AX_ROLE_METER") << QString("<meter>a</meter>") << false << QAccessible::Chart; + QTest::newRow("AX_ROLE_NAVIGATION") << QString("<nav>a</nav>") << true << QAccessible::Section; + QTest::newRow("AX_ROLE_NOTE") << QString("<div role='note'>a</div>") << true << QAccessible::Note; + //QTest::newRow("AX_ROLE_OUTLINE"); // No mapping to ARIA role + //QTest::newRow("AX_ROLE_PANE"); // Not a blink accessibility role + QTest::newRow("AX_ROLE_PARAGRAH") << QString("<p>a</p>") << true << QAccessible::Paragraph; + QTest::newRow("AX_ROLE_POP_UP_BUTTON") << QString("<select multiple></select>") << false << QAccessible::ComboBox; + QTest::newRow("AX_ROLE_PRE") << QString("<pre>a</pre>") << true << QAccessible::Section; + //QTest::newRow("AX_ROLE_PRESENTATIONAL") << QString("<div role='presentation'>a</div>") << true << QAccessible::NoRole; // FIXME: Aria role 'presentation' should work + QTest::newRow("AX_ROLE_PROGRESS_INDICATOR") << QString("<div role='progressbar' aria-valuenow='77' aria-valuemin='22' aria-valuemax='99'></div>") << true << QAccessible::ProgressBar; + QTest::newRow("AX_ROLE_RADIO_BUTTON") << QString("<input type='radio'></input>") << false << QAccessible::RadioButton; + QTest::newRow("AX_ROLE_RADIO_GROUP") << QString("<fieldset role='radiogroup'></fieldset>") << true << QAccessible::Grouping; + QTest::newRow("AX_ROLE_REGION") << QString("<section>a</section>") << true << QAccessible::Section; + //QTest::newRow("AX_ROLE_ROW") << QString("<tr role='row'>a</tr>") << true << QAccessible::Row; // FIXME: Aria role 'row' should work for <tr> + //QTest::newRow("AX_ROLE_ROW_HEADER") << QString("<td role='rowheader'>a</td>") << true << QAccessible::RowHeader; // FIXME: Aria role 'rowheader' should work for <td> + QTest::newRow("AX_ROLE_RUBY") << QString("<ruby>a</ruby>") << false << QAccessible::StaticText; + //QTest::newRow("AX_ROLE_RULER"); // No mapping to ARIA role + //QTest::newRow("AX_ROLE_SCROLL_AREA"); // No mapping to ARIA role + QTest::newRow("AX_ROLE_SCROLL_BAR") << QString("<div role='scrollbar'>a</a>") << true << QAccessible::ScrollBar; + //QTest::newRow("AX_ROLE_SEAMLESS_WEB_AREA"); // No mapping to ARIA role + QTest::newRow("AX_ROLE_SEARCH") << QString("<div role='search'>landmark</div>") << true << QAccessible::Section; + QTest::newRow("AX_ROLE_SEARCH_BOX") << QString("<input type='search'></input>") << false << QAccessible::EditableText; + QTest::newRow("AX_ROLE_SLIDER") << QString("<input type='range'></input>") << false << QAccessible::Slider; + //QTest::newRow("AX_ROLE_SLIDER_THUMB"); // No mapping to ARIA role + QTest::newRow("AX_ROLE_SPIN_BUTTON") << QString("<input type='number'></input>") << false << QAccessible::SpinBox; + //QTest::newRow("AX_ROLE_SPIN_BUTTON_PART"); // No mapping to ARIA role + QTest::newRow("AX_ROLE_SPLITER") << QString("<hr>") << true << QAccessible::Splitter; + QTest::newRow("AX_ROLE_STATIC_TEXT") << QString("a") << false << QAccessible::StaticText; + QTest::newRow("AX_ROLE_STATUS") << QString("<output>a</output>") << false << QAccessible::Indicator; + QTest::newRow("AX_ROLE_SVG_ROOT") << QString("<svg width='10' height='10'></svg>") << false << QAccessible::Graphic; + QTest::newRow("AX_ROLE_SWITCH") << QString("<button aria-checked='false'>a</button>") << false << QAccessible::Button; + //QTest::newRow("AX_ROLE_TABLE") << QString("<table>a</table>") << true << QAccessible::Table; // FIXME: The test case might be wrong (see AXTable.cpp) + //QTest::newRow("AX_ROLE_TABLE_HEADER_CONTAINER"); // No mapping to ARIA role + QTest::newRow("AX_ROLE_TAB") << QString("<div role='tab'>a</div>") << true << QAccessible::PageTab; + //QTest::newRow("AX_ROLE_TAB_GROUP"); // No mapping to ARIA role + QTest::newRow("AX_ROLE_TAB_LIST") << QString("<div role='tablist'>a</div>") << true << QAccessible::PageTabList; + QTest::newRow("AX_ROLE_TAB_PANEL") << QString("<div role='tab'>a</div>") << true << QAccessible::PageTab; + QTest::newRow("AX_ROLE_TERM") << QString("<div role='term'>a</div>") << true << QAccessible::StaticText; + QTest::newRow("AX_ROLE_TEXT_FIELD") << QString("<input type='text'></input>") << false << QAccessible::EditableText; + QTest::newRow("AX_ROLE_TIME") << QString("<time>a</time>") << false << QAccessible::Clock; + QTest::newRow("AX_ROLE_TIMER") << QString("<div role='timer'>a</div>") << true << QAccessible::Clock; + //QTest::newRow("AX_ROLE_TITLE_BAR"); // Not a blink accessibility role + QTest::newRow("AX_ROLE_TOGGLE_BUTTON") << QString("<button aria-pressed='false'>a</button>") << false << QAccessible::Button; + QTest::newRow("AX_ROLE_TOOLBAR") << QString("<div role='toolbar'>a</div>") << true << QAccessible::ToolBar; + QTest::newRow("AX_ROLE_TOOLTIP") << QString("<div role='tooltip'>a</div>") << true << QAccessible::ToolTip; + QTest::newRow("AX_ROLE_TREE") << QString("<div role='tree'>a</div>") << true << QAccessible::Tree; + QTest::newRow("AX_ROLE_TREE_GRID") << QString("<div role='treegrid'>a</div>") << true << QAccessible::Tree; + QTest::newRow("AX_ROLE_TREE_ITEM") << QString("<div role='treeitem'>a</div>") << true << QAccessible::TreeItem; + QTest::newRow("AX_ROLE_VIDEO") << QString("<video><source src='test.mp4' type='video/mp4'></video>") << false << QAccessible::Animation; + //QTest::newRow("AX_ROLE_WINDOW"); // No mapping to ARIA role +} + +void tst_QWebEngineAccessibility::roles() +{ + QFETCH(QString, html); + QFETCH(bool, isSection); + QFETCH(QAccessible::Role, role); + + QWebEngineView webView; + webView.setHtml("<html><body>" + html + "</body></html>"); + QSignalSpy spyFinished(&webView, &QWebEngineView::loadFinished); + QVERIFY(spyFinished.wait()); + + QAccessibleInterface *view = QAccessible::queryAccessibleInterface(&webView); + + // Corner case for Client role + if (html.isEmpty()) { + QCOMPARE(view->role(), QAccessible::Client); + return; + } + + QTRY_COMPARE(view->child(0)->childCount(), 1); + QAccessibleInterface *document = view->child(0); + QAccessibleInterface *section = document->child(0); + + if (isSection) { + QCOMPARE(section->role(), role); + return; + } + + QVERIFY(section->childCount() > 0); + QAccessibleInterface *element = section->child(0); + QCOMPARE(element->role(), role); +} + static QByteArrayList params = QByteArrayList() << "--force-renderer-accessibility"; diff --git a/tests/auto/widgets/qwebenginedefaultsurfaceformat/tst_qwebenginedefaultsurfaceformat.cpp b/tests/auto/widgets/qwebenginedefaultsurfaceformat/tst_qwebenginedefaultsurfaceformat.cpp index 79fc57ac0..3ac8943a5 100644 --- a/tests/auto/widgets/qwebenginedefaultsurfaceformat/tst_qwebenginedefaultsurfaceformat.cpp +++ b/tests/auto/widgets/qwebenginedefaultsurfaceformat/tst_qwebenginedefaultsurfaceformat.cpp @@ -43,7 +43,7 @@ private Q_SLOTS: void tst_QWebEngineDefaultSurfaceFormat::customDefaultSurfaceFormat() { -#if !defined(Q_OS_MACOSX) +#if !defined(Q_OS_MACOS) QSKIP("OpenGL Core Profile is currently only supported on macOS."); #endif // Setting a new default QSurfaceFormat with a core OpenGL profile before diff --git a/tests/auto/widgets/qwebenginedownloads/tst_qwebenginedownloads.cpp b/tests/auto/widgets/qwebenginedownloads/tst_qwebenginedownloads.cpp index c25e82d41..83e8d1101 100644 --- a/tests/auto/widgets/qwebenginedownloads/tst_qwebenginedownloads.cpp +++ b/tests/auto/widgets/qwebenginedownloads/tst_qwebenginedownloads.cpp @@ -38,6 +38,18 @@ #include <httpserver.h> #include <waitforsignal.h> +static std::unique_ptr<HttpReqRep> waitForFaviconRequest(HttpServer *server) +{ + auto rr = waitForRequest(server); + if (!rr || + rr->requestMethod() != QByteArrayLiteral("GET") || + rr->requestPath() != QByteArrayLiteral("/favicon.ico")) + return nullptr; + rr->setResponseStatus(404); + rr->sendResponse(); + return std::move(rr); +} + class tst_QWebEngineDownloads : public QObject { Q_OBJECT @@ -47,6 +59,7 @@ private Q_SLOTS: void downloadTwoLinks(); void downloadPage_data(); void downloadPage(); + void downloadViaSetUrl(); }; enum DownloadTestUserAction { @@ -318,17 +331,14 @@ void tst_QWebEngineDownloads::downloadLink() QVERIFY(loadOk); // 1.1. Ignore favicon request - auto favIconRR = waitForRequest(&server); + auto favIconRR = waitForFaviconRequest(&server); QVERIFY(favIconRR); - QCOMPARE(favIconRR->requestMethod(), QByteArrayLiteral("GET")); - QCOMPARE(favIconRR->requestPath(), QByteArrayLiteral("/favicon.ico")); - favIconRR->setResponseStatus(404); - favIconRR->sendResponse(); // 2. Simulate user action // // - Navigate: user left-clicks on link // - SaveLink: user right-clicks on link and chooses "save link as" from menu + QTRY_VERIFY(view.focusWidget()); QWidget *renderWidget = view.focusWidget(); QPoint linkPos(10, 10); if (userAction == SaveLink) { @@ -450,11 +460,10 @@ void tst_QWebEngineDownloads::downloadTwoLinks() QTRY_COMPARE(requestSpy.count(), 2); std::unique_ptr<HttpReqRep> favIconRR(results.takeFirst()); QVERIFY(favIconRR); - QCOMPARE(favIconRR->requestMethod(), QByteArrayLiteral("GET")); - QCOMPARE(favIconRR->requestPath(), QByteArrayLiteral("/favicon.ico")); favIconRR->setResponseStatus(404); favIconRR->sendResponse(); + QTRY_VERIFY(view.focusWidget()); QWidget *renderWidget = view.focusWidget(); QTest::mouseClick(renderWidget, Qt::LeftButton, {}, QPoint(10, 10)); QTest::mouseClick(renderWidget, Qt::LeftButton, {}, QPoint(10, 30)); @@ -551,12 +560,8 @@ void tst_QWebEngineDownloads::downloadPage() QVERIFY(waitForSignal(&page, &QWebEnginePage::loadFinished, [&](bool ok){ loadOk = ok; })); QVERIFY(loadOk); - auto favIconRR = waitForRequest(&server); + auto favIconRR = waitForFaviconRequest(&server); QVERIFY(favIconRR); - QCOMPARE(favIconRR->requestMethod(), QByteArrayLiteral("GET")); - QCOMPARE(favIconRR->requestPath(), QByteArrayLiteral("/favicon.ico")); - favIconRR->setResponseStatus(404); - favIconRR->sendResponse(); QTemporaryDir tmpDir; QVERIFY(tmpDir.isValid()); @@ -603,5 +608,70 @@ void tst_QWebEngineDownloads::downloadPage() QVERIFY(file.exists()); } +void tst_QWebEngineDownloads::downloadViaSetUrl() +{ + // Reproduce the scenario described in QTBUG-63388 by triggering downloads + // of the same file multiple times via QWebEnginePage::setUrl + + HttpServer server; + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished); + QSignalSpy urlSpy(&page, &QWebEnginePage::urlChanged); + const QUrl indexUrl = server.url(); + const QUrl fileUrl = server.url(QByteArrayLiteral("/file")); + + // Set up the test scenario by trying to load some unrelated HTML. + + page.setUrl(indexUrl); + + auto indexRR = waitForRequest(&server); + QVERIFY(indexRR); + QCOMPARE(indexRR->requestMethod(), QByteArrayLiteral("GET")); + QCOMPARE(indexRR->requestPath(), QByteArrayLiteral("/")); + indexRR->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html")); + indexRR->setResponseBody(QByteArrayLiteral("<html><body>Hello</body></html>")); + indexRR->sendResponse(); + + auto indexFavRR = waitForFaviconRequest(&server); + QVERIFY(indexFavRR); + + QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(urlSpy.count(), 1); + QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true); + QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), indexUrl); + + // Download files via setUrl. With QTBUG-63388 after the first iteration the + // downloads would be triggered for indexUrl and not fileUrl. + + QVector<QUrl> downloadUrls; + QObject::connect(&profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { + downloadUrls.append(item->url()); + }); + + for (int i = 0; i != 3; ++i) { + page.setUrl(fileUrl); + QCOMPARE(page.url(), fileUrl); + + auto fileRR = waitForRequest(&server); + QVERIFY(fileRR); + fileRR->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); + fileRR->setResponseBody(QByteArrayLiteral("redacted")); + fileRR->sendResponse(); + + auto fileFavRR = waitForFaviconRequest(&server); + QVERIFY(fileFavRR); + + QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(urlSpy.count(), 2); + QTRY_COMPARE(downloadUrls.count(), 1); + QCOMPARE(loadSpy.takeFirst().value(0).toBool(), false); + QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), fileUrl); + QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), indexUrl); + QCOMPARE(downloadUrls.takeFirst(), fileUrl); + QCOMPARE(page.url(), indexUrl); + } +} + QTEST_MAIN(tst_QWebEngineDownloads) #include "tst_qwebenginedownloads.moc" diff --git a/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp b/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp index d9bbce173..9fceffd7e 100644 --- a/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp +++ b/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp @@ -40,6 +40,7 @@ protected : loadFinishedSpy->clear(); page->load(QUrl("qrc:/resources/page" + QString::number(nr) + ".html")); QTRY_COMPARE(loadFinishedSpy->count(), 1); + loadFinishedSpy->clear(); } public Q_SLOTS: @@ -160,7 +161,7 @@ void tst_QWebEngineHistory::forward() while (hist->canGoBack()) { hist->back(); histBackCount++; - QTRY_COMPARE(loadFinishedSpy->count(), histBackCount+1); + QTRY_COMPARE(loadFinishedSpy->count(), histBackCount); } QSignalSpy titleChangedSpy(page, SIGNAL(titleChanged(const QString&))); @@ -197,15 +198,15 @@ void tst_QWebEngineHistory::goToItem() QWebEngineHistoryItem current = hist->currentItem(); hist->back(); - QTRY_COMPARE(loadFinishedSpy->count(), 2); + QTRY_COMPARE(loadFinishedSpy->count(), 1); hist->back(); - QTRY_COMPARE(loadFinishedSpy->count(), 3); + QTRY_COMPARE(loadFinishedSpy->count(), 2); QVERIFY(hist->currentItem().title() != current.title()); hist->goToItem(current); - QTRY_COMPARE(loadFinishedSpy->count(), 3); + QTRY_COMPARE(loadFinishedSpy->count(), 2); QTRY_COMPARE(hist->currentItem().title(), current.title()); } @@ -228,10 +229,10 @@ void tst_QWebEngineHistory::items() void tst_QWebEngineHistory::backForwardItems() { hist->back(); - QTRY_COMPARE(loadFinishedSpy->count(), 2); + QTRY_COMPARE(loadFinishedSpy->count(), 1); hist->back(); - QTRY_COMPARE(loadFinishedSpy->count(), 3); + QTRY_COMPARE(loadFinishedSpy->count(), 2); QTRY_COMPARE(hist->items().size(), 5); QTRY_COMPARE(hist->backItems(100).size(), 2); @@ -281,16 +282,17 @@ void tst_QWebEngineHistory::serialize_2() // Force a "same document" navigation. page->load(page->url().toString() + QLatin1String("#dummyAnchor")); - QTRY_COMPARE(loadFinishedSpy->count(), 1); + // "same document" navigation doesn't trigger loadFinished signal. + QTRY_COMPARE(evaluateJavaScriptSync(page, "location.hash").toString(), QStringLiteral("#dummyAnchor")); int initialCurrentIndex = hist->currentItemIndex(); hist->back(); - QTRY_COMPARE(loadFinishedSpy->count(), 2); + QTRY_VERIFY(evaluateJavaScriptSync(page, "location.hash").toString().isEmpty()); hist->back(); - QTRY_COMPARE(loadFinishedSpy->count(), 3); + QTRY_COMPARE(loadFinishedSpy->count(), 1); hist->back(); - QTRY_COMPARE(loadFinishedSpy->count(), 4); + QTRY_COMPARE(loadFinishedSpy->count(), 2); //check if current index was changed (make sure that it is not last item) QVERIFY(hist->currentItemIndex() != initialCurrentIndex); //save current index @@ -301,17 +303,17 @@ void tst_QWebEngineHistory::serialize_2() load >> *hist; QVERIFY(load.status() == QDataStream::Ok); // Restoring the history will trigger a load. - QTRY_COMPARE(loadFinishedSpy->count(), 5); + QTRY_COMPARE(loadFinishedSpy->count(), 3); //check current index QTRY_COMPARE(hist->currentItemIndex(), oldCurrentIndex); hist->forward(); - QTRY_COMPARE(loadFinishedSpy->count(), 6); + QTRY_COMPARE(loadFinishedSpy->count(), 4); hist->forward(); - QTRY_COMPARE(loadFinishedSpy->count(), 7); + QTRY_COMPARE(loadFinishedSpy->count(), 5); hist->forward(); - QTRY_COMPARE(loadFinishedSpy->count(), 8); + QTRY_COMPARE(loadFinishedSpy->count(), 6); QTRY_COMPARE(hist->currentItemIndex(), initialCurrentIndex); } diff --git a/tests/auto/widgets/qwebenginehistoryinterface/tst_qwebenginehistoryinterface.cpp b/tests/auto/widgets/qwebenginehistoryinterface/tst_qwebenginehistoryinterface.cpp deleted file mode 100644 index 4666d1ba4..000000000 --- a/tests/auto/widgets/qwebenginehistoryinterface/tst_qwebenginehistoryinterface.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - Copyright (C) 2008 Holger Hans Peter Freyther - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - - -#include <QtTest/QtTest> - -#include <qwebenginepage.h> -#include <qwebengineview.h> -#if defined(QWEBENGINEHISTORYINTERFACE) -#include <qwebenginehistoryinterface.h> -#endif -#include <QDebug> - -class tst_QWebEngineHistoryInterface : public QObject -{ - Q_OBJECT - -public: - tst_QWebEngineHistoryInterface(); - virtual ~tst_QWebEngineHistoryInterface(); - -public Q_SLOTS: - void initTestCase(); - void init(); - void cleanup(); - -private Q_SLOTS: - void visitedLinks(); - -private: - - -private: - QWebEngineView* m_view; - QWebEnginePage* m_page; -}; - -tst_QWebEngineHistoryInterface::tst_QWebEngineHistoryInterface() -{ -} - -tst_QWebEngineHistoryInterface::~tst_QWebEngineHistoryInterface() -{ -} - -void tst_QWebEngineHistoryInterface::initTestCase() -{ -} - -void tst_QWebEngineHistoryInterface::init() -{ - m_view = new QWebEngineView(); - m_page = m_view->page(); -} - -void tst_QWebEngineHistoryInterface::cleanup() -{ - delete m_view; -} - -#if defined(QWEBENGINEHISTORYINTERFACE) -class FakeHistoryImplementation : public QWebEngineHistoryInterface { -public: - void addHistoryEntry(const QString&) {} - bool historyContains(const QString& url) const { - return url == QLatin1String("http://www.trolltech.com/"); - } -}; -#endif - -/* - * Test that visited links are properly colored. http://www.trolltech.com is marked - * as visited, so the below website should have exactly one element in the a:visited - * state. - */ -void tst_QWebEngineHistoryInterface::visitedLinks() -{ -#if !defined(QWEBENGINEELEMENT) - QSKIP("QWEBENGINEELEMENT"); -#else - QWebEngineHistoryInterface::setDefaultInterface(new FakeHistoryImplementation); - m_view->setHtml("<html><style>:link{color:green}:visited{color:red}</style><body><a href='http://www.trolltech.com' id='vlink'>Trolltech</a></body></html>"); - QWebEngineElement anchor = m_view->page()->mainFrame()->findFirstElement("a[id=vlink]"); - QString linkColor = anchor.styleProperty("color", QWebEngineElement::ComputedStyle); - QCOMPARE(linkColor, QString::fromLatin1("rgb(255, 0, 0)")); -#endif -} - -QTEST_MAIN(tst_QWebEngineHistoryInterface) -#include "tst_qwebenginehistoryinterface.moc" diff --git a/tests/auto/widgets/qwebenginepage/BLACKLIST b/tests/auto/widgets/qwebenginepage/BLACKLIST index b376cd375..7470a1523 100644 --- a/tests/auto/widgets/qwebenginepage/BLACKLIST +++ b/tests/auto/widgets/qwebenginepage/BLACKLIST @@ -7,8 +7,6 @@ [macCopyUnicodeToClipboard] osx -[getUserMediaRequest] -* [mouseMovementProperties] osx-10.11 osx-10.12 diff --git a/tests/auto/widgets/qwebenginepage/qwebenginepage.pro b/tests/auto/widgets/qwebenginepage/qwebenginepage.pro index e0765736e..47c09e1ce 100644 --- a/tests/auto/widgets/qwebenginepage/qwebenginepage.pro +++ b/tests/auto/widgets/qwebenginepage/qwebenginepage.pro @@ -1,4 +1,4 @@ include(../tests.pri) QT *= core-private -contains(WEBENGINE_CONFIG, use_pdf): DEFINES+=QWEBENGINEPAGE_PDFPRINTINGENABLED +qtConfig(webengine-printing-and-pdf): DEFINES+=QWEBENGINEPAGE_PDFPRINTINGENABLED diff --git a/tests/auto/widgets/qwebengineview/resources/basic_printing_page.html b/tests/auto/widgets/qwebenginepage/resources/basic_printing_page.html index 0c6ff379f..0c6ff379f 100644 --- a/tests/auto/widgets/qwebengineview/resources/basic_printing_page.html +++ b/tests/auto/widgets/qwebenginepage/resources/basic_printing_page.html diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp index 182094a11..54cf27066 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp @@ -106,6 +106,7 @@ private Q_SLOTS: void updatePositionDependentActionsCrash(); void createPluginWithPluginsEnabled(); void createPluginWithPluginsDisabled(); + void callbackSpyDeleted(); void destroyPlugin_data(); void destroyPlugin(); void createViewlessPlugin_data(); @@ -126,7 +127,10 @@ private Q_SLOTS: void userAgentNewlineStripping(); void undoActionHaveCustomText(); void renderWidgetHostViewNotShowTopLevel(); + void getUserMediaRequest_data(); void getUserMediaRequest(); + void getUserMediaRequestDesktopAudio(); + void getUserMediaRequestSettingDisabled(); void savePage(); void crashTests_LazyInitializationOfMainFrame(); @@ -182,7 +186,6 @@ private Q_SLOTS: void baseUrl_data(); void baseUrl(); void scrollPosition(); - void scrollToAnchor(); void scrollbarsOff(); void evaluateWillCauseRepaint(); void setContent_data(); @@ -191,6 +194,8 @@ private Q_SLOTS: void setUrlWithPendingLoads(); void setUrlToEmpty(); void setUrlToInvalid(); + void setUrlToBadDomain(); + void setUrlToBadPort(); void setUrlHistory(); void setUrlUsingStateObject(); void setUrlThenLoads_data(); @@ -251,6 +256,7 @@ void tst_QWebEnginePage::init() m_view = new QWebEngineView(); m_page = m_view->page(); m_page->settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); + m_view->settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); } void tst_QWebEnginePage::cleanup() @@ -308,6 +314,10 @@ void tst_QWebEnginePage::acceptNavigationRequest() NavigationRequestOverride* newPage = new NavigationRequestOverride(&view, false); view.setPage(newPage); + // acceptNavigationRequest and QWebEngineUrlRequestInterceptor::interceptRequest are not called + // for data: urls, which means the test is broken, aka setting + // newPage->m_acceptNavigationRequest to false does nothing to stop the page from loading. + // See QTBUG-50922 comments. view.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); @@ -442,6 +452,22 @@ static QImage imageWithoutAlpha(const QImage &image) return result; } +void tst_QWebEnginePage::callbackSpyDeleted() +{ + QWebEnginePage *page = m_view->page(); + CallbackSpy<QVariant> spy; + QString poorManSleep("function wait(ms){" + " var start = new Date().getTime();" + " var end = start;" + " while (start + ms > end) {" + "end = new Date().getTime();" + " }" + "}" + "wait(1000);"); + page->runJavaScript(poorManSleep, spy.ref()); + //spy deleted before callback +} + void tst_QWebEnginePage::pasteImage() { // Pixels with an alpha value of 0 will have different RGB values after the @@ -459,7 +485,9 @@ void tst_QWebEnginePage::pasteImage() "window.myImageDataURL ? window.myImageDataURL.length : 0").toInt() > 0); QByteArray data = evaluateJavaScriptSync(page, "window.myImageDataURL").toByteArray(); data.remove(0, data.indexOf(";base64,") + 8); - const QImage image = QImage::fromData(QByteArray::fromBase64(data), "PNG"); + QImage image = QImage::fromData(QByteArray::fromBase64(data), "PNG"); + if (image.format() == QImage::Format_RGB32) + image.reinterpretAsFormat(QImage::Format_ARGB32); QCOMPARE(image, origImage); } @@ -2114,29 +2142,55 @@ void tst_QWebEnginePage::testStopScheduledPageRefresh() void tst_QWebEnginePage::findText() { - QSignalSpy loadSpy(m_page, SIGNAL(loadFinished(bool))); - m_page->setHtml(QString("<html><head></head><body><div>foo bar</div></body></html>")); + QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool))); + m_view->setHtml(QString("<html><head></head><body><div>foo bar</div></body></html>")); + + // Showing is required, otherwise all find operations fail. + m_view->show(); QTRY_COMPARE(loadSpy.count(), 1); // Select whole page contents. - m_page->triggerAction(QWebEnginePage::SelectAll); - QTRY_COMPARE(m_page->hasSelection(), true); + m_view->page()->triggerAction(QWebEnginePage::SelectAll); + QTRY_COMPARE(m_view->hasSelection(), true); - // Invoke a stopFinding() operation, which should clear the currently selected text. - m_page->findText(""); - QTRY_VERIFY(m_page->selectedText().isEmpty()); + // Invoking a stopFinding operation will not change or clear the currently selected text, + // if nothing was found beforehand. + { + CallbackSpy<bool> spy; + m_view->findText("", 0, spy.ref()); + QVERIFY(spy.wasCalled()); + QTRY_COMPARE(m_view->selectedText(), QString("foo bar")); + } - QStringList words = (QStringList() << "foo" << "bar"); - foreach (QString subString, words) { - // Invoke a find operation, which should clear the currently selected text, should - // highlight all the found ocurrences, but should not update the selected text to the - // searched for string. - m_page->findText(subString); - QTRY_VERIFY(m_page->selectedText().isEmpty()); - - // Search highlights should be cleared, selected text should still be empty. - m_page->findText(""); - QTRY_VERIFY(m_page->selectedText().isEmpty()); + // Invoking a startFinding operation with text that won't be found, will clear the current + // selection. + { + CallbackSpy<bool> spy; + m_view->findText("Will not be found", 0, spy.ref()); + QCOMPARE(spy.waitForResult(), false); + QTRY_VERIFY(m_view->selectedText().isEmpty()); + } + + // Select whole page contents again. + m_view->page()->triggerAction(QWebEnginePage::SelectAll); + QTRY_COMPARE(m_view->hasSelection(), true); + + // Invoking a startFinding operation with text that will be found, will clear the current + // selection as well. + { + CallbackSpy<bool> spy; + m_view->findText("foo", 0, spy.ref()); + QVERIFY(spy.waitForResult()); + QTRY_VERIFY(m_view->selectedText().isEmpty()); + } + + // Invoking a stopFinding operation after text was found, will set the selected text to the + // found text. + { + CallbackSpy<bool> spy; + m_view->findText("", 0, spy.ref()); + QTRY_VERIFY(spy.wasCalled()); + QTRY_COMPARE(m_view->selectedText(), QString("foo")); } } @@ -2606,8 +2660,36 @@ Q_OBJECT public: GetUserMediaTestPage() : m_gotRequest(false) + , m_loadSucceeded(false) { connect(this, &QWebEnginePage::featurePermissionRequested, this, &GetUserMediaTestPage::onFeaturePermissionRequested); + connect(this, &QWebEnginePage::loadFinished, [this](bool success){ + m_loadSucceeded = success; + }); + // We need to load content from a resource in order for the securityOrigin to be valid. + load(QUrl("qrc:///resources/content.html")); + } + + void jsGetUserMedia(const QString & constraints) + { + evaluateJavaScriptSync(this, + QStringLiteral( + "var promiseFulfilled = false;" + "var promiseRejected = false;" + "navigator.mediaDevices.getUserMedia(%1)" + ".then(stream => { promiseFulfilled = true})" + ".catch(err => { promiseRejected = true})") + .arg(constraints)); + } + + bool jsPromiseFulfilled() + { + return evaluateJavaScriptSync(this, QStringLiteral("promiseFulfilled")).toBool(); + } + + bool jsPromiseRejected() + { + return evaluateJavaScriptSync(this, QStringLiteral("promiseRejected")).toBool(); } void rejectPendingRequest() @@ -2626,6 +2708,16 @@ public: return m_gotRequest && m_requestedFeature == feature; } + bool gotFeatureRequest() const + { + return m_gotRequest; + } + + bool loadSucceeded() const + { + return m_loadSucceeded; + } + private Q_SLOTS: void onFeaturePermissionRequested(const QUrl &securityOrigin, QWebEnginePage::Feature feature) { @@ -2636,33 +2728,92 @@ private Q_SLOTS: private: bool m_gotRequest; + bool m_loadSucceeded; QWebEnginePage::Feature m_requestedFeature; QUrl m_requestSecurityOrigin; }; +void tst_QWebEnginePage::getUserMediaRequest_data() +{ + QTest::addColumn<QString>("constraints"); + QTest::addColumn<QWebEnginePage::Feature>("feature"); + + QTest::addRow("device audio") + << "{audio: true}" << QWebEnginePage::MediaAudioCapture; + QTest::addRow("device video") + << "{video: true}" << QWebEnginePage::MediaVideoCapture; + QTest::addRow("device audio+video") + << "{audio: true, video: true}" << QWebEnginePage::MediaAudioVideoCapture; + QTest::addRow("desktop video") + << "{video: { mandatory: { chromeMediaSource: 'desktop' }}}" + << QWebEnginePage::DesktopVideoCapture; + QTest::addRow("desktop audio+video") + << "{audio: { mandatory: { chromeMediaSource: 'desktop' }}, video: { mandatory: { chromeMediaSource: 'desktop' }}}" + << QWebEnginePage::DesktopAudioVideoCapture; +} void tst_QWebEnginePage::getUserMediaRequest() { - GetUserMediaTestPage page; + QFETCH(QString, constraints); + QFETCH(QWebEnginePage::Feature, feature); - // We need to load content from a resource in order for the securityOrigin to be valid. - QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); - page.load(QUrl("qrc:///resources/content.html")); - QTRY_COMPARE(loadSpy.count(), 1); + GetUserMediaTestPage page; + QTRY_VERIFY_WITH_TIMEOUT(page.loadSucceeded(), 20000); + page.settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true); + + // 1. Rejecting request on C++ side should reject promise on JS side. + page.jsGetUserMedia(constraints); + QTRY_VERIFY(page.gotFeatureRequest(feature)); + page.rejectPendingRequest(); + QTRY_VERIFY(!page.jsPromiseFulfilled() && page.jsPromiseRejected()); + + // 2. Accepting request on C++ side should either fulfill or reject the + // Promise on JS side. Due to the potential lack of physical media devices + // 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); + QTRY_VERIFY(page.gotFeatureRequest(feature)); + page.acceptPendingRequest(); + QTRY_VERIFY(page.jsPromiseFulfilled() || page.jsPromiseRejected()); - QVERIFY(evaluateJavaScriptSync(&page, QStringLiteral("!!navigator.webkitGetUserMedia")).toBool()); - evaluateJavaScriptSync(&page, QStringLiteral("navigator.webkitGetUserMedia({audio: true}, function() {}, function(){})")); - QTRY_VERIFY(page.gotFeatureRequest(QWebEnginePage::MediaAudioCapture)); - // Might end up failing due to the lack of physical media devices deeper in the content layer, so the JS callback is not guaranteed to be called, - // but at least we go through that code path, potentially uncovering failing assertions. + // 3. Media feature permissions are not remembered. + page.jsGetUserMedia(constraints); + QTRY_VERIFY(page.gotFeatureRequest(feature)); page.acceptPendingRequest(); + QTRY_VERIFY(page.jsPromiseFulfilled() || page.jsPromiseRejected()); +} - page.runJavaScript(QStringLiteral("errorCallbackCalled = false;")); - evaluateJavaScriptSync(&page, QStringLiteral("navigator.webkitGetUserMedia({audio: true, video: true}, function() {}, function(){errorCallbackCalled = true;})")); - QTRY_VERIFY(page.gotFeatureRequest(QWebEnginePage::MediaAudioVideoCapture)); - page.rejectPendingRequest(); // Should always end up calling the error callback in JS. - QTRY_VERIFY(evaluateJavaScriptSync(&page, QStringLiteral("errorCallbackCalled;")).toBool()); +void tst_QWebEnginePage::getUserMediaRequestDesktopAudio() +{ + GetUserMediaTestPage page; + QTRY_VERIFY_WITH_TIMEOUT(page.loadSucceeded(), 20000); + page.settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true); + + // Audio-only desktop capture is not supported. JS Promise should be + // rejected immediately. + + page.jsGetUserMedia( + QStringLiteral("{audio: { mandatory: { chromeMediaSource: 'desktop' }}}")); + QTRY_VERIFY(!page.jsPromiseFulfilled() && page.jsPromiseRejected()); + + page.jsGetUserMedia( + QStringLiteral("{audio: { mandatory: { chromeMediaSource: 'desktop' }}, video: true}")); + QTRY_VERIFY(!page.jsPromiseFulfilled() && page.jsPromiseRejected()); +} + +void tst_QWebEnginePage::getUserMediaRequestSettingDisabled() +{ + GetUserMediaTestPage page; + QTRY_VERIFY_WITH_TIMEOUT(page.loadSucceeded(), 20000); + page.settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, false); + + // With the setting disabled, the JS Promise should be rejected without + // asking for permission first. + + page.jsGetUserMedia(QStringLiteral("{video: { mandatory: { chromeMediaSource: 'desktop' }}}")); + QTRY_VERIFY(!page.jsPromiseFulfilled() && page.jsPromiseRejected()); } void tst_QWebEnginePage::savePage() @@ -3044,21 +3195,19 @@ void tst_QWebEnginePage::progressSignal() void tst_QWebEnginePage::urlChange() { - QSignalSpy urlSpy(m_page, SIGNAL(urlChanged(QUrl))); + QSignalSpy urlSpy(m_page, &QWebEnginePage::urlChanged); QUrl dataUrl("data:text/html,<h1>Test"); m_view->setUrl(dataUrl); - QVERIFY(urlSpy.wait()); - - QCOMPARE(urlSpy.size(), 1); + QTRY_COMPARE(urlSpy.size(), 1); + QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), dataUrl); QUrl dataUrl2("data:text/html,<html><head><title>title</title></head><body><h1>Test</body></html>"); m_view->setUrl(dataUrl2); - QVERIFY(urlSpy.wait()); - - QCOMPARE(urlSpy.size(), 2); + QTRY_COMPARE(urlSpy.size(), 1); + QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), dataUrl2); } class FakeReply : public QNetworkReply { @@ -3169,7 +3318,7 @@ void tst_QWebEnginePage::requestedUrlAfterSetAndLoadFailures() page.load(second); QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 2, 12000); - QCOMPARE(page.url(), first); + QCOMPARE(page.url(), second); QCOMPARE(page.requestedUrl(), second); QVERIFY(!spy.at(1).first().toBool()); } @@ -3410,7 +3559,7 @@ void tst_QWebEnginePage::scrollPosition() view.setFixedSize(200,200); view.show(); - QTest::qWaitForWindowExposed(&view); + QVERIFY(QTest::qWaitForWindowExposed(&view)); QSignalSpy loadSpy(view.page(), SIGNAL(loadFinished(bool))); view.setHtml(html); @@ -3427,77 +3576,21 @@ void tst_QWebEnginePage::scrollPosition() QCOMPARE(y, 29); } -void tst_QWebEnginePage::scrollToAnchor() +void tst_QWebEnginePage::scrollbarsOff() { -#if !defined(QWEBENGINEELEMENT) - QSKIP("QWEBENGINEELEMENT"); -#else - QWebEnginePage page; - page.setViewportSize(QSize(480, 800)); + QWebEngineView view; + view.page()->settings()->setAttribute(QWebEngineSettings::ShowScrollBars, false); - 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>" + QString html("<html><body>" + " <div style='margin-top:1000px ; margin-left:1000px'>" + " <a id='offscreen' href='a'>End</a>" + " </div>" "</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, &QWebEngineView::loadFinished); + QSignalSpy loadSpy(&view, SIGNAL(loadFinished(bool))); view.setHtml(html); - QVERIFY(loadSpy.wait(200); - QCOMPARE(loadSpy.count(), 1); - - mainFrame->evaluateJavaScript("checkScrollbar();"); - QCOMPARE(mainFrame->documentElement().findAll("span").at(0).toPlainText(), QString("SUCCESS")); -#endif + QTRY_COMPARE(loadSpy.count(), 1); + QVERIFY(evaluateJavaScriptSync(view.page(), "innerWidth == document.documentElement.offsetWidth").toBool()); } void tst_QWebEnginePage::horizontalScrollAfterBack() @@ -3545,7 +3638,7 @@ void tst_QWebEnginePage::evaluateWillCauseRepaint() { WebView view; view.show(); - QTest::qWaitForWindowExposed(&view); + QVERIFY(QTest::qWaitForWindowExposed(&view)); QString html("<html><body>" " top" @@ -3739,6 +3832,90 @@ void tst_QWebEnginePage::setUrlToInvalid() QCOMPARE(baseUrlSync(&page), aboutBlank); } +void tst_QWebEnginePage::setUrlToBadDomain() +{ + // Failing to load a URL should still emit a urlChanged signal. + // + // This test is based on the scenario in QTBUG-48995 where the second setUrl + // call first triggers an unexpected additional urlChanged signal with the + // original url before the expected signal with the new url. + + // RFC 2606 says the .invalid TLD should be invalid. + const QUrl url1 = QStringLiteral("http://this.is.definitely.invalid/"); + const QUrl url2 = QStringLiteral("http://this.is.also.invalid/"); + QWebEnginePage page; + QSignalSpy urlSpy(&page, &QWebEnginePage::urlChanged); + QSignalSpy titleSpy(&page, &QWebEnginePage::titleChanged); + QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished); + + page.setUrl(url1); + + QTRY_COMPARE(urlSpy.count(), 1); + QTRY_COMPARE(titleSpy.count(), 1); + QTRY_COMPARE(loadSpy.count(), 1); + + QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), url1); + QCOMPARE(titleSpy.takeFirst().value(0).toString(), url1.host()); + QCOMPARE(loadSpy.takeFirst().value(0).toBool(), false); + + QCOMPARE(page.url(), url1); + QCOMPARE(page.title(), url1.host()); + + page.setUrl(url2); + + QTRY_COMPARE(urlSpy.count(), 1); + QTRY_COMPARE(titleSpy.count(), 1); + QTRY_COMPARE(loadSpy.count(), 1); + + QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), url2); + QCOMPARE(titleSpy.takeFirst().value(0).toString(), url2.host()); + QCOMPARE(loadSpy.takeFirst().value(0).toBool(), false); + + QCOMPARE(page.url(), url2); + QCOMPARE(page.title(), url2.host()); +} + +void tst_QWebEnginePage::setUrlToBadPort() +{ + // Failing to load a URL should still emit a urlChanged signal. + + // Ports 244-245 are hopefully unbound (marked unassigned in RFC1700). + const QUrl url1 = QStringLiteral("http://127.0.0.1:244/"); + const QUrl url2 = QStringLiteral("http://127.0.0.1:245/"); + QWebEnginePage page; + QSignalSpy urlSpy(&page, &QWebEnginePage::urlChanged); + QSignalSpy titleSpy(&page, &QWebEnginePage::titleChanged); + QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished); + + page.setUrl(url1); + + QTRY_COMPARE(urlSpy.count(), 1); + QTRY_COMPARE(titleSpy.count(), 2); + QTRY_COMPARE(loadSpy.count(), 1); + + QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), url1); + QCOMPARE(titleSpy.takeFirst().value(0).toString(), url1.authority()); + QCOMPARE(titleSpy.takeFirst().value(0).toString(), url1.host()); + QCOMPARE(loadSpy.takeFirst().value(0).toBool(), false); + + QCOMPARE(page.url(), url1); + QCOMPARE(page.title(), url1.host()); + + page.setUrl(url2); + + QTRY_COMPARE(urlSpy.count(), 1); + QTRY_COMPARE(titleSpy.count(), 2); + QTRY_COMPARE(loadSpy.count(), 1); + + QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), url2); + QCOMPARE(titleSpy.takeFirst().value(0).toString(), url2.authority()); + QCOMPARE(titleSpy.takeFirst().value(0).toString(), url2.host()); + QCOMPARE(loadSpy.takeFirst().value(0).toBool(), false); + + QCOMPARE(page.url(), url2); + QCOMPARE(page.title(), url2.host()); +} + static QStringList collectHistoryUrls(QWebEngineHistory *history) { QStringList urls; @@ -3899,9 +4076,8 @@ void tst_QWebEnginePage::setUrlThenLoads() 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->url(), urlToLoad1); QCOMPARE(m_page->requestedUrl(), urlToLoad1); // baseUrlSync spins an event loop and this sometimes return the next result. // QCOMPARE(baseUrlSync(m_page), baseUrl); @@ -3915,9 +4091,8 @@ void tst_QWebEnginePage::setUrlThenLoads() 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->url(), urlToLoad2); QCOMPARE(m_page->requestedUrl(), urlToLoad2); QCOMPARE(baseUrlSync(m_page), extractBaseUrl(urlToLoad1)); QTRY_COMPARE(startedSpy.count(), 3); @@ -4128,10 +4303,10 @@ void tst_QWebEnginePage::toPlainTextLoadFinishedRace() QVERIFY(s.contains("foobarbaz") == !enableErrorPage); page->load(QUrl("data:text/plain,lalala")); - QTRY_VERIFY(spy.count() == 3); - QCOMPARE(toPlainTextSync(page.data()), QString("lalala")); + QTRY_COMPARE(spy.count(), 3); + QTRY_COMPARE(toPlainTextSync(page.data()), QString("lalala")); page.reset(); - QVERIFY(spy.count() == 3); + QCOMPARE(spy.count(), 3); } void tst_QWebEnginePage::setZoomFactor() @@ -4216,7 +4391,7 @@ void tst_QWebEnginePage::mouseButtonTranslation() </div>\ </body></html>")); view.show(); - QTest::qWaitForWindowExposed(&view); + QVERIFY(QTest::qWaitForWindowExposed(&view)); QTRY_VERIFY(spy.count() == 1); QVERIFY(view.focusProxy() != nullptr); @@ -4240,7 +4415,7 @@ void tst_QWebEnginePage::mouseMovementProperties() ConsolePage page; view.setPage(&page); view.show(); - QTest::qWaitForWindowExposed(&view); + QVERIFY(QTest::qWaitForWindowExposed(&view)); QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); page.setHtml(QStringLiteral( @@ -4372,5 +4547,7 @@ void tst_QWebEnginePage::proxyConfigWithUnexpectedHostPortPair() QTRY_COMPARE(loadFinishedSpy.count(), 1); } -QTEST_MAIN(tst_QWebEnginePage) +static QByteArrayList params = {QByteArrayLiteral("--use-fake-device-for-media-stream")}; +W_QTEST_MAIN(tst_QWebEnginePage, params) + #include "tst_qwebenginepage.moc" diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc index 1226e367d..fc83aefa5 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/basic_printing_page.html</file> <file>resources/content.html</file> <file>resources/index.html</file> <file>resources/frame_a.html</file> diff --git a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp index 639a8456f..6961f3b6d 100644 --- a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp +++ b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp @@ -49,6 +49,7 @@ private Q_SLOTS: void urlSchemeHandlers(); void urlSchemeHandlerFailRequest(); void urlSchemeHandlerFailOnRead(); + void urlSchemeHandlerStreaming(); void customUserAgent(); void httpAcceptLanguage(); void downloadItem(); @@ -183,6 +184,80 @@ public: QList<QPointer<QBuffer>> m_buffers; }; +class StreamingIODevice : public QIODevice { + Q_OBJECT +public: + StreamingIODevice(QObject *parent) : QIODevice(parent), m_bytesRead(0), m_bytesAvailable(0) + { + setOpenMode(QIODevice::ReadOnly); + m_timer.start(100, this); + } + void close() override + { + QMutexLocker lock(&m_mutex); + QIODevice::close(); + deleteLater(); + } + bool isSequential() const override { return true; } + qint64 bytesAvailable() const override + { return m_bytesAvailable; } + bool atEnd() const override + { + return (m_data.size() >= 1000 && m_bytesRead >= 1000); + } +protected: + void timerEvent(QTimerEvent *) override + { + QMutexLocker lock(&m_mutex); + m_bytesAvailable += 200; + m_data.append(200, 'c'); + emit readyRead(); + if (m_data.size() >= 1000) { + m_timer.stop(); + emit readChannelFinished(); + } + } + + qint64 readData(char *data, qint64 maxlen) override + { + QMutexLocker lock(&m_mutex); + qint64 len = qMin(qint64(m_bytesAvailable), maxlen); + if (len) { + memcpy(data, m_data.constData() + m_bytesRead, len); + m_bytesAvailable -= len; + m_bytesRead += len; + } else if (m_data.size() > 0) + return -1; + + return len; + } + qint64 writeData(const char *, qint64) override + { + return 0; + } + +private: + QMutex m_mutex; + QByteArray m_data; + QBasicTimer m_timer; + int m_bytesRead; + int m_bytesAvailable; +}; + +class StreamingUrlSchemeHandler : public QWebEngineUrlSchemeHandler +{ +public: + StreamingUrlSchemeHandler(QObject *parent = nullptr) + : QWebEngineUrlSchemeHandler(parent) + { + } + + void requestStarted(QWebEngineUrlRequestJob *job) + { + job->reply("text/plain;charset=utf-8", new StreamingIODevice(this)); + } +}; + static bool loadSync(QWebEngineView *view, const QUrl &url, int timeout = 5000) { // Ripped off QTRY_VERIFY. @@ -320,6 +395,22 @@ void tst_QWebEngineProfile::urlSchemeHandlerFailOnRead() QCOMPARE(toPlainTextSync(view.page()), QString()); } +void tst_QWebEngineProfile::urlSchemeHandlerStreaming() +{ + StreamingUrlSchemeHandler handler; + QWebEngineProfile profile; + profile.installUrlSchemeHandler("stream", &handler); + QWebEngineView view; + QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); + view.setPage(new QWebEnginePage(&profile, &view)); + view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); + view.load(QUrl(QStringLiteral("stream://whatever"))); + QVERIFY(loadFinishedSpy.wait()); + QByteArray result; + result.append(1000, 'c'); + QCOMPARE(toPlainTextSync(view.page()), QString::fromLatin1(result)); +} + void tst_QWebEngineProfile::customUserAgent() { QString defaultUserAgent = QWebEngineProfile::defaultProfile()->httpUserAgent(); diff --git a/tests/auto/widgets/qwebenginescript/resources/test_iframe_inner.html b/tests/auto/widgets/qwebenginescript/resources/test_iframe_inner.html new file mode 100644 index 000000000..3539c9620 --- /dev/null +++ b/tests/auto/widgets/qwebenginescript/resources/test_iframe_inner.html @@ -0,0 +1,8 @@ +<html> +<head> +<title></title> +</head> +<body> +<div>Inner text</div> +</body> +</html> diff --git a/tests/auto/widgets/qwebenginescript/resources/test_iframe_main.html b/tests/auto/widgets/qwebenginescript/resources/test_iframe_main.html new file mode 100644 index 000000000..47b991c2c --- /dev/null +++ b/tests/auto/widgets/qwebenginescript/resources/test_iframe_main.html @@ -0,0 +1,9 @@ +<html> +<head> +<title></title> +</head> +<body> +<div>Main text</div> +<iframe id="outer" src="qrc:/resources/test_iframe_outer.html"></iframe> +</body> +</html> diff --git a/tests/auto/widgets/qwebenginescript/resources/test_iframe_outer.html b/tests/auto/widgets/qwebenginescript/resources/test_iframe_outer.html new file mode 100644 index 000000000..8854809f8 --- /dev/null +++ b/tests/auto/widgets/qwebenginescript/resources/test_iframe_outer.html @@ -0,0 +1,9 @@ +<html> +<head> +<title></title> +</head> +<body> +<div>Outer text</div> +<iframe id="inner" src="qrc:/resources/test_iframe_inner.html"></iframe> +</body> +</html> diff --git a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp index c10ae2886..d852ca902 100644 --- a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp +++ b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp @@ -38,6 +38,7 @@ private Q_SLOTS: void webChannel_data(); void webChannel(); void noTransportWithoutWebChannel(); + void scriptsInNestedIframes(); }; void tst_QWebEngineScript::domEditing() @@ -66,7 +67,7 @@ void tst_QWebEngineScript::domEditing() QVERIFY(spyFinished.wait()); QCOMPARE(evaluateJavaScriptSync(&page, "document.getElementById(\"banner\").innerText"), QVariant(QStringLiteral("Injected banner"))); // elementFromPoint only works for exposed elements - QTest::qWaitForWindowExposed(&view); + QVERIFY(QTest::qWaitForWindowExposed(&view)); QCOMPARE(evaluateJavaScriptSync(&page, "document.elementFromPoint(2, 2).id"), QVariant::fromValue(QStringLiteral("banner"))); } @@ -80,15 +81,12 @@ void tst_QWebEngineScript::injectionPoint() s.setInjectionPoint(static_cast<QWebEngineScript::InjectionPoint>(injectionPoint)); s.setWorldId(QWebEngineScript::MainWorld); QWebEnginePage page; - page.scripts().insert(s); - page.setHtml(QStringLiteral("<html><head><script> var contents;") + testScript - + QStringLiteral("document.addEventListener(\"load\", setTimeout(function(event) {\ - document.body.innerText = contents;\ - }, 550));\ - </script></head><body></body></html>")); QSignalSpy spyFinished(&page, &QWebEnginePage::loadFinished); + page.scripts().insert(s); + page.setHtml(QStringLiteral("<html><head><script>") + testScript + QStringLiteral("</script></head><body></body></html>")); QVERIFY(spyFinished.wait()); - QTRY_COMPARE(evaluateJavaScriptSync(&page, "document.body.innerText"), QVariant::fromValue(QStringLiteral("SUCCESS"))); + const QVariant expected(QVariant::fromValue(QStringLiteral("SUCCESS"))); + QTRY_COMPARE(evaluateJavaScriptSync(&page, "document.myContents"), expected); } void tst_QWebEngineScript::injectionPoint_data() @@ -96,17 +94,21 @@ void tst_QWebEngineScript::injectionPoint_data() QTest::addColumn<int>("injectionPoint"); QTest::addColumn<QString>("testScript"); QTest::newRow("DocumentCreation") << static_cast<int>(QWebEngineScript::DocumentCreation) - << QStringLiteral("var contents = (typeof(foo) == \"undefined\")? \"FAILURE\" : \"SUCCESS\";"); + << QStringLiteral("document.myContents = (typeof(foo) == \"undefined\")? \"FAILURE\" : \"SUCCESS\";"); QTest::newRow("DocumentReady") << static_cast<int>(QWebEngineScript::DocumentReady) // use a zero timeout to make sure the user script got a chance to run as the order is undefined. << QStringLiteral("document.addEventListener(\"DOMContentLoaded\", function() {\ - setTimeout(function() {\ - contents = (typeof(foo) == \"undefined\")? \"FAILURE\" : \"SUCCESS\";\ - }, 0)});"); - QTest::newRow("Deferred") << static_cast<int>(QWebEngineScript::Deferred) - << QStringLiteral("document.addEventListener(\"load\", setTimeout(function(event) {\ - contents = (typeof(foo) == \"undefined\")? \"FAILURE\" : \"SUCCESS\";\ - }, 500));"); + setTimeout(function() {\ + document.myContents = (typeof(foo) == \"undefined\")? \"FAILURE\" : \"SUCCESS\";\ + }, 0)});"); + QTest::newRow("Deferred") << static_cast<int>(QWebEngineScript::DocumentReady) + << QStringLiteral("document.onreadystatechange = function() { \ + if (document.readyState == \"complete\") { \ + setTimeout(function() {\ + document.myContents = (typeof(foo) == \"undefined\")? \"FAILURE\" : \"SUCCESS\";\ + }, 0);\ + } \ + };"); } void tst_QWebEngineScript::scriptWorld() @@ -246,6 +248,58 @@ void tst_QWebEngineScript::noTransportWithoutWebChannel() QCOMPARE(evaluateJavaScriptSync(&page, "qt.webChannelTransport"), QVariant(QVariant::Invalid)); } +void tst_QWebEngineScript::scriptsInNestedIframes() +{ + QWebEnginePage page; + QWebEngineView view; + view.setPage(&page); + QWebEngineScript s; + s.setInjectionPoint(QWebEngineScript::DocumentReady); + s.setWorldId(QWebEngineScript::ApplicationWorld); + + // Prepend a "Modified prefix" to every frame's div content. + s.setSourceCode("var elements = document.getElementsByTagName(\"div\");\ + var i;\ + for (i = 0; i < elements.length; i++) {\ + var content = elements[i].innerHTML;\ + elements[i].innerHTML = \"Modified \" + content;\ + }\ + "); + + // Make sure the script runs on all frames. + s.setRunsOnSubFrames(true); + page.scripts().insert(s); + + QSignalSpy spyFinished(&page, &QWebEnginePage::loadFinished); + page.load(QUrl("qrc:/resources/test_iframe_main.html")); + view.show(); + QVERIFY(spyFinished.wait()); + + // Check that main frame has modified content. + QCOMPARE( + evaluateJavaScriptSyncInWorld(&page, "document.getElementsByTagName(\"div\")[0].innerHTML", + QWebEngineScript::ApplicationWorld), + QVariant::fromValue(QStringLiteral("Modified Main text"))); + + // Check that outer frame has modified content. + QCOMPARE( + evaluateJavaScriptSyncInWorld(&page, + "var i = document.getElementById(\"outer\").contentDocument;\ + i.getElementsByTagName(\"div\")[0].innerHTML", + QWebEngineScript::ApplicationWorld), + QVariant::fromValue(QStringLiteral("Modified Outer text"))); + + + // Check that inner frame has modified content. + QCOMPARE( + evaluateJavaScriptSyncInWorld(&page, + "var i = document.getElementById(\"outer\").contentDocument;\ + var i2 = i.getElementById(\"inner\").contentDocument;\ + i2.getElementsByTagName(\"div\")[0].innerHTML", + QWebEngineScript::ApplicationWorld), + QVariant::fromValue(QStringLiteral("Modified Inner text"))); +} + QTEST_MAIN(tst_QWebEngineScript) #include "tst_qwebenginescript.moc" diff --git a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.qrc b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.qrc new file mode 100644 index 000000000..8b7a11cf2 --- /dev/null +++ b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.qrc @@ -0,0 +1,7 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file>resources/test_iframe_main.html</file> + <file>resources/test_iframe_outer.html</file> + <file>resources/test_iframe_inner.html</file> +</qresource> +</RCC> diff --git a/tests/auto/widgets/qwebenginesettings/tst_qwebenginesettings.cpp b/tests/auto/widgets/qwebenginesettings/tst_qwebenginesettings.cpp index d8c18e509..5cbcf4ec0 100644 --- a/tests/auto/widgets/qwebenginesettings/tst_qwebenginesettings.cpp +++ b/tests/auto/widgets/qwebenginesettings/tst_qwebenginesettings.cpp @@ -33,6 +33,8 @@ private Q_SLOTS: void tst_QWebEngineSettings::resetAttributes() { + // QT_TODO_FIXME_ADAPT + QSKIP("The application deadlocks and hangs without exiting."); QWebEngineProfile profile; QWebEngineSettings *settings = profile.settings(); @@ -74,6 +76,8 @@ void tst_QWebEngineSettings::defaultFontFamily_data() void tst_QWebEngineSettings::defaultFontFamily() { + // QT_TODO_FIXME_ADAPT + QSKIP("The application deadlocks and hangs without exiting."); QWebEngineProfile profile; QWebEngineSettings *settings = profile.settings(); diff --git a/tests/auto/widgets/qwebenginehistoryinterface/qwebenginehistoryinterface.pro b/tests/auto/widgets/qwebengineshutdown/qwebengineshutdown.pro index e99c7f493..e99c7f493 100644 --- a/tests/auto/widgets/qwebenginehistoryinterface/qwebenginehistoryinterface.pro +++ b/tests/auto/widgets/qwebengineshutdown/qwebengineshutdown.pro diff --git a/tests/auto/widgets/qwebengineshutdown/tst_qwebengineshutdown.cpp b/tests/auto/widgets/qwebengineshutdown/tst_qwebengineshutdown.cpp new file mode 100644 index 000000000..d757e0d60 --- /dev/null +++ b/tests/auto/widgets/qwebengineshutdown/tst_qwebengineshutdown.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#include <qwebenginepage.h> +#include <qwebengineview.h> +#include <QDebug> + +class tst_QWebEngineShutdown : public QObject +{ + Q_OBJECT + +public: + tst_QWebEngineShutdown(); + virtual ~tst_QWebEngineShutdown(); + +public Q_SLOTS: + void init(); + void cleanup(); + +private Q_SLOTS: + void dummyTest(); + +private: + + +private: + QWebEngineView* m_view; + QWebEnginePage* m_page; +}; + +tst_QWebEngineShutdown::tst_QWebEngineShutdown() +{ +} + +tst_QWebEngineShutdown::~tst_QWebEngineShutdown() +{ +} + +void tst_QWebEngineShutdown::init() +{ + m_view = new QWebEngineView(); + m_page = m_view->page(); +} + +void tst_QWebEngineShutdown::cleanup() +{ + delete m_view; +} + +void tst_QWebEngineShutdown::dummyTest() +{ + QVERIFY(m_view); +} + +QTEST_MAIN(tst_QWebEngineShutdown) +#include "tst_qwebengineshutdown.moc" diff --git a/tests/auto/widgets/qwebenginespellcheck/dict/de-DE.dic b/tests/auto/widgets/qwebenginespellcheck/dict/de-DE.dic index d10ae2600..a57ab15b4 100644 --- a/tests/auto/widgets/qwebenginespellcheck/dict/de-DE.dic +++ b/tests/auto/widgets/qwebenginespellcheck/dict/de-DE.dic @@ -8,6 +8,7 @@ liebe/Q lieben/Q liebst/Q liebt/Q +löwe/Q qt/Q sie/Q Sie/Q diff --git a/tests/auto/widgets/qwebenginespellcheck/dict/en-US.dic b/tests/auto/widgets/qwebenginespellcheck/dict/en-US.dic index 3d4ecdfa4..63e9164cc 100644 --- a/tests/auto/widgets/qwebenginespellcheck/dict/en-US.dic +++ b/tests/auto/widgets/qwebenginespellcheck/dict/en-US.dic @@ -4,6 +4,7 @@ I/Q it/Q love/Q loves/Q +low/Q qt/Q she/Q they/Q diff --git a/tests/auto/widgets/qwebenginespellcheck/tst_qwebenginespellcheck.cpp b/tests/auto/widgets/qwebenginespellcheck/tst_qwebenginespellcheck.cpp index c7b083660..4f14f29f9 100644 --- a/tests/auto/widgets/qwebenginespellcheck/tst_qwebenginespellcheck.cpp +++ b/tests/auto/widgets/qwebenginespellcheck/tst_qwebenginespellcheck.cpp @@ -32,6 +32,7 @@ #include <QtWebEngineWidgets/qwebengineprofile.h> #include <QtWebEngineWidgets/qwebenginepage.h> #include <QtWebEngineWidgets/qwebengineview.h> +#include <qwebenginesettings.h> class WebView : public QWebEngineView { @@ -143,24 +144,27 @@ void tst_QWebEngineSpellcheck::spellcheck() QFETCH(QStringList, languages); QFETCH(QStringList, suggestions); + m_view->settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + QWebEngineProfile *profile = QWebEngineProfile::defaultProfile(); QVERIFY(profile); profile->setSpellCheckLanguages(languages); profile->setSpellCheckEnabled(true); load(); + QCOMPARE(profile->spellCheckLanguages(), languages); // make textarea editable evaluateJavaScriptSync(m_view->page(), "makeEditable();"); // calcuate position of misspelled word - QVariantList list = evaluateJavaScriptSync(m_view->page(), "findWordPosition('I lovee Qt ....','lovee');").toList(); + QVariantList list = evaluateJavaScriptSync(m_view->page(), "findWordPosition('I lowe Qt ....','lowe');").toList(); QRect rect(list[0].value<int>(),list[1].value<int>(),list[2].value<int>(),list[3].value<int>()); //type text, spellchecker needs time QTest::mouseMove(m_view->focusWidget(), QPoint(20,20)); QTest::mousePress(m_view->focusWidget(), Qt::LeftButton, 0, QPoint(20,20)); QTest::mouseRelease(m_view->focusWidget(), Qt::LeftButton, 0, QPoint(20,20)); - QString text("I lovee Qt ...."); + QString text("I lowe Qt ...."); for (int i = 0; i < text.length(); i++) { QTest::keyClicks(m_view->focusWidget(), text.at(i)); QTest::qWait(60); @@ -180,10 +184,10 @@ void tst_QWebEngineSpellcheck::spellcheck() QVERIFY(m_view->data().isContentEditable()); // check misspelled word - QVERIFY(m_view->data().misspelledWord() == "lovee"); + QCOMPARE(m_view->data().misspelledWord(), QStringLiteral("lowe")); // check suggestions - QVERIFY(m_view->data().spellCheckerSuggestions() == suggestions); + QCOMPARE(m_view->data().spellCheckerSuggestions(), suggestions); // check replace word m_view->page()->replaceMisspelledWord("love"); @@ -195,8 +199,8 @@ void tst_QWebEngineSpellcheck::spellcheck_data() { QTest::addColumn<QStringList>("languages"); QTest::addColumn<QStringList>("suggestions"); - QTest::newRow("en-US") << QStringList({"en-US"}) << QStringList({"love", "loves"}); - QTest::newRow("en-US,de-DE") << QStringList({"en-US","de-DE"}) << QStringList({"love", "liebe", "loves"}); + QTest::newRow("en-US") << QStringList({"en-US"}) << QStringList({"low", "love"}); + QTest::newRow("en-US,de-DE") << QStringList({"en-US", "de-DE"}) << QStringList({"low", "löwe", "love"}); } QTEST_MAIN(tst_QWebEngineSpellcheck) diff --git a/tests/auto/widgets/qwebengineview/BLACKLIST b/tests/auto/widgets/qwebengineview/BLACKLIST index b3f393af4..bf7292c86 100644 --- a/tests/auto/widgets/qwebengineview/BLACKLIST +++ b/tests/auto/widgets/qwebengineview/BLACKLIST @@ -1,8 +1,8 @@ [doNotSendMouseKeyboardEventsWhenDisabled] windows -[imeComposition] -osx +[mouseLeave] +* -[inputFieldOverridesShortcuts] -osx +[textSelectionOutOfInputField] +* diff --git a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp index 04de4d951..34a6f245b 100644 --- a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp +++ b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp @@ -33,10 +33,12 @@ #include <qdiriterator.h> #include <qstackedlayout.h> #include <qtemporarydir.h> +#include <QClipboard> #include <QCompleter> #include <QLabel> #include <QLineEdit> #include <QHBoxLayout> +#include <QMenu> #include <QQuickItem> #include <QQuickWidget> #include <QtWebEngineCore/qwebenginehttprequest.h> @@ -92,6 +94,30 @@ static QRect elementGeometry(QWebEnginePage *page, const QString &id) return QRect(coords[0].toInt(), coords[1].toInt(), coords[2].toInt(), coords[3].toInt()); } +QT_BEGIN_NAMESPACE +namespace QTest { + int Q_TESTLIB_EXPORT defaultMouseDelay(); + + static void mouseEvent(QEvent::Type type, QWidget *widget, const QPoint &pos) + { + QTest::qWait(QTest::defaultMouseDelay()); + lastMouseTimestamp += QTest::defaultMouseDelay(); + QMouseEvent me(type, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + me.setTimestamp(++lastMouseTimestamp); + QSpontaneKeyEvent::setSpontaneous(&me); + qApp->sendEvent(widget, &me); + } + + static void mouseMultiClick(QWidget *widget, const QPoint pos, int clickCount) + { + for (int i = 0; i < clickCount; ++i) { + mouseEvent(QMouseEvent::MouseButtonPress, widget, pos); + mouseEvent(QMouseEvent::MouseButtonRelease, widget, pos); + } + lastMouseTimestamp += mouseDoubleClickInterval; + } +} +QT_END_NAMESPACE class tst_QWebEngineView : public QObject { @@ -133,6 +159,7 @@ private Q_SLOTS: void inputMethodsTextFormat(); void keyboardEvents(); void keyboardFocusAfterPopup(); + void mouseClick(); void postData(); void inputFieldOverridesShortcuts(); @@ -148,6 +175,15 @@ private Q_SLOTS: void newlineInTextarea(); void mouseLeave(); + +#ifndef QT_NO_CLIPBOARD + void globalMouseSelection(); +#endif + void noContextMenu(); + void contextMenu_data(); + void contextMenu(); + void webUIURLs_data(); + void webUIURLs(); }; // This will be called before the first test function is executed. @@ -247,14 +283,14 @@ void tst_QWebEngineView::reusePage() } view1->show(); - QTest::qWaitForWindowExposed(view1); + QVERIFY(QTest::qWaitForWindowExposed(view1)); delete view1; QVERIFY(page != 0); // deleting view must not have deleted the page, since it's not a child of view QWebEngineView *view2 = new QWebEngineView; view2->setPage(page.data()); view2->show(); // in Windowless mode, you should still be able to see the plugin here - QTest::qWaitForWindowExposed(view2); + QVERIFY(QTest::qWaitForWindowExposed(view2)); delete view2; delete page.data(); // must not crash @@ -267,12 +303,12 @@ class WebViewCrashTest : public QObject { Q_OBJECT QWebEngineView* m_view; public: - bool m_executed; + bool m_invokedStop; WebViewCrashTest(QWebEngineView* view) : m_view(view) - , m_executed(false) + , m_invokedStop(false) { view->connect(view, SIGNAL(loadProgress(int)), this, SLOT(loading(int))); } @@ -280,10 +316,11 @@ public: private Q_SLOTS: void loading(int progress) { - if (progress > 1 && progress < 100) { - QVERIFY(!m_executed); + qDebug() << "progress: " << progress; + if (progress > 0 && progress < 100) { + QVERIFY(!m_invokedStop); m_view->stop(); - m_executed = true; + m_invokedStop = true; } } }; @@ -298,14 +335,17 @@ void tst_QWebEngineView::crashTests() WebViewCrashTest tester(&view); QUrl url("qrc:///resources/index.html"); view.load(url); - QTRY_VERIFY(tester.m_executed); // If fail it means that the test wasn't executed. + + // If the verification fails, it means that either stopping doesn't work, or the hardware is + // too slow to load the page and thus to slow to issue the first loadProgress > 0 signal. + QTRY_VERIFY_WITH_TIMEOUT(tester.m_invokedStop, 10000); } void tst_QWebEngineView::microFocusCoordinates() { QWebEngineView webView; webView.show(); - QTest::qWaitForWindowExposed(&webView); + QVERIFY(QTest::qWaitForWindowExposed(&webView)); QSignalSpy scrollSpy(webView.page(), SIGNAL(scrollPositionChanged(QPointF))); QSignalSpy loadFinishedSpy(&webView, SIGNAL(loadFinished(bool))); @@ -334,27 +374,38 @@ void tst_QWebEngineView::microFocusCoordinates() void tst_QWebEngineView::focusInputTypes() { + const QPlatformInputContext *context = QGuiApplicationPrivate::platformIntegration()->inputContext(); + bool imeHasHiddenTextCapability = context && context->hasCapability(QPlatformInputContext::HiddenTextCapability); + QWebEngineView webView; webView.show(); - QTest::qWaitForWindowExposed(&webView); + QVERIFY(QTest::qWaitForWindowExposed(&webView)); QSignalSpy loadFinishedSpy(&webView, SIGNAL(loadFinished(bool))); webView.load(QUrl("qrc:///resources/input_types.html")); QVERIFY(loadFinishedSpy.wait()); + auto inputMethodQuery = [&webView](Qt::InputMethodQuery query) { + QInputMethodQueryEvent event(query); + QApplication::sendEvent(webView.focusProxy(), &event); + return event.value(query); + }; + // 'text' field QPoint textInputCenter = elementCenter(webView.page(), "textInput"); QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, textInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("textInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), Qt::ImhPreferLowercase); QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); + QTRY_VERIFY(inputMethodQuery(Qt::ImEnabled).toBool()); // 'password' field QPoint passwordInputCenter = elementCenter(webView.page(), "passwordInput"); QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, passwordInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("passwordInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), (Qt::ImhSensitiveData | Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase | Qt::ImhHiddenText)); - QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); + QVERIFY(!webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); + QTRY_COMPARE(inputMethodQuery(Qt::ImEnabled).toBool(), imeHasHiddenTextCapability); // 'tel' field QPoint telInputCenter = elementCenter(webView.page(), "telInput"); @@ -362,6 +413,7 @@ void tst_QWebEngineView::focusInputTypes() QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("telInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), Qt::ImhDialableCharactersOnly); QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); + QTRY_VERIFY(inputMethodQuery(Qt::ImEnabled).toBool()); // 'number' field QPoint numberInputCenter = elementCenter(webView.page(), "numberInput"); @@ -369,6 +421,7 @@ void tst_QWebEngineView::focusInputTypes() QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("numberInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), Qt::ImhFormattedNumbersOnly); QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); + QTRY_VERIFY(inputMethodQuery(Qt::ImEnabled).toBool()); // 'email' field QPoint emailInputCenter = elementCenter(webView.page(), "emailInput"); @@ -376,6 +429,7 @@ void tst_QWebEngineView::focusInputTypes() QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("emailInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), Qt::ImhEmailCharactersOnly); QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); + QTRY_VERIFY(inputMethodQuery(Qt::ImEnabled).toBool()); // 'url' field QPoint urlInputCenter = elementCenter(webView.page(), "urlInput"); @@ -383,24 +437,28 @@ void tst_QWebEngineView::focusInputTypes() QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("urlInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), (Qt::ImhUrlCharactersOnly | Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase)); QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); + QTRY_VERIFY(inputMethodQuery(Qt::ImEnabled).toBool()); // 'password' field QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, passwordInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("passwordInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), (Qt::ImhSensitiveData | Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase | Qt::ImhHiddenText)); - QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); + QVERIFY(!webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); + QTRY_COMPARE(inputMethodQuery(Qt::ImEnabled).toBool(), imeHasHiddenTextCapability); // 'text' type QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, textInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("textInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), Qt::ImhPreferLowercase); QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); + QTRY_VERIFY(inputMethodQuery(Qt::ImEnabled).toBool()); // 'password' field QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, passwordInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("passwordInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), (Qt::ImhSensitiveData | Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase | Qt::ImhHiddenText)); - QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); + QVERIFY(!webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); + QTRY_COMPARE(inputMethodQuery(Qt::ImEnabled).toBool(), imeHasHiddenTextCapability); // 'text area' field QPoint textAreaCenter = elementCenter(webView.page(), "textArea"); @@ -408,6 +466,7 @@ void tst_QWebEngineView::focusInputTypes() QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("textArea")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), (Qt::ImhMultiLine | Qt::ImhPreferLowercase)); QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); + QTRY_VERIFY(inputMethodQuery(Qt::ImEnabled).toBool()); } class KeyEventRecordingWidget : public QWidget { @@ -423,7 +482,7 @@ void tst_QWebEngineView::unhandledKeyEventPropagation() KeyEventRecordingWidget parentWidget; QWebEngineView webView(&parentWidget); parentWidget.show(); - QTest::qWaitForWindowExposed(&webView); + QVERIFY(QTest::qWaitForWindowExposed(&webView)); QSignalSpy loadFinishedSpy(&webView, SIGNAL(loadFinished(bool))); webView.load(QUrl("qrc:///resources/keyboardEvents.html")); @@ -481,7 +540,7 @@ void tst_QWebEngineView::horizontalScrollbarTest() view.setFixedSize(600, 600); view.show(); - QTest::qWaitForWindowExposed(&view); + QVERIFY(QTest::qWaitForWindowExposed(&view)); QSignalSpy loadSpy(view.page(), SIGNAL(loadFinished(bool))); view.setHtml(html); @@ -771,7 +830,7 @@ void tst_QWebEngineView::doNotSendMouseKeyboardEventsWhenDisabled() parentWidget.layout()->addWidget(&webView); webView.resize(640, 480); parentWidget.show(); - QTest::qWaitForWindowExposed(&webView); + QVERIFY(QTest::qWaitForWindowExposed(&webView)); QSignalSpy loadSpy(&webView, SIGNAL(loadFinished(bool))); webView.setHtml("<html><head><title>Title</title></head><body>Hello" @@ -817,7 +876,7 @@ void tst_QWebEngineView::stopSettingFocusWhenDisabled() webView.resize(640, 480); webView.show(); webView.setEnabled(viewEnabled); - QTest::qWaitForWindowExposed(&webView); + QVERIFY(QTest::qWaitForWindowExposed(&webView)); QSignalSpy loadSpy(&webView, SIGNAL(loadFinished(bool))); webView.setHtml("<html><head><title>Title</title></head><body>Hello" @@ -877,7 +936,7 @@ void tst_QWebEngineView::focusOnNavigation() containerWidget->setLayout(layout); containerWidget->show(); - QTest::qWaitForWindowExposed(containerWidget.data()); + QVERIFY(QTest::qWaitForWindowExposed(containerWidget.data())); // Load the content, invoke javascript focus on the view, and check which widget has focus. QSignalSpy loadSpy(webView, SIGNAL(loadFinished(bool))); @@ -943,7 +1002,7 @@ void tst_QWebEngineView::focusInternalRenderWidgetHostViewQuickItem() containerWidget->setLayout(layout); containerWidget->show(); - QTest::qWaitForWindowExposed(containerWidget.data()); + QVERIFY(QTest::qWaitForWindowExposed(containerWidget.data())); // Load the content, and check that focus is not set. QSignalSpy loadSpy(webView, SIGNAL(loadFinished(bool))); @@ -1041,6 +1100,7 @@ void tst_QWebEngineView::inputMethodsTextFormat_data() void tst_QWebEngineView::inputMethodsTextFormat() { QWebEngineView view; + view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); view.setHtml("<html><body>" @@ -1179,6 +1239,7 @@ void tst_QWebEngineView::keyboardFocusAfterPopup() urlLine->setFocus(); QWebEngineView *webView = new QWebEngineView(containerWidget.data()); + webView->settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); QSignalSpy loadFinishedSpy(webView, SIGNAL(loadFinished(bool))); connect(urlLine, &QLineEdit::editingFinished, [=] { @@ -1201,7 +1262,7 @@ void tst_QWebEngineView::keyboardFocusAfterPopup() containerWidget->setLayout(layout); containerWidget->show(); - QTest::qWaitForWindowExposed(containerWidget.data()); + QVERIFY(QTest::qWaitForWindowExposed(containerWidget.data())); // Trigger completer's popup and select the first suggestion QTest::keyClick(urlLine, Qt::Key_T); @@ -1221,6 +1282,76 @@ void tst_QWebEngineView::keyboardFocusAfterPopup() QTRY_COMPARE(evaluateJavaScriptSync(webView->page(), "document.getElementById('input1').value").toString(), QStringLiteral("x")); } +void tst_QWebEngineView::mouseClick() +{ + QWebEngineView view; + view.show(); + view.resize(200, 200); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); + QSignalSpy selectionChangedSpy(&view, SIGNAL(selectionChanged())); + QPoint textInputCenter; + + // Single Click + view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false); + selectionChangedSpy.clear(); + + view.setHtml("<html><body>" + "<form><input id='input' width='150' type='text' value='The Qt Company' /></form>" + "</body></html>"); + QVERIFY(loadFinishedSpy.wait()); + + QVERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty()); + textInputCenter = elementCenter(view.page(), "input"); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input")); + QCOMPARE(selectionChangedSpy.count(), 0); + QVERIFY(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString().isEmpty()); + + // Double click + view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + selectionChangedSpy.clear(); + + view.setHtml("<html><body onload='document.getElementById(\"input\").focus()'>" + "<form><input id='input' width='150' type='text' value='The Qt Company' /></form>" + "</body></html>"); + QVERIFY(loadFinishedSpy.wait()); + + textInputCenter = elementCenter(view.page(), "input"); + QTest::mouseMultiClick(view.focusProxy(), textInputCenter, 2); + QVERIFY(selectionChangedSpy.wait()); + QCOMPARE(selectionChangedSpy.count(), 1); + QCOMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input")); + QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QStringLiteral("Company")); + + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QVERIFY(selectionChangedSpy.wait()); + QCOMPARE(selectionChangedSpy.count(), 2); + QVERIFY(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString().isEmpty()); + + // Triple click + view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + selectionChangedSpy.clear(); + + view.setHtml("<html><body onload='document.getElementById(\"input\").focus()'>" + "<form><input id='input' width='150' type='text' value='The Qt Company' /></form>" + "</body></html>"); + QVERIFY(loadFinishedSpy.wait()); + + textInputCenter = elementCenter(view.page(), "input"); + QTest::mouseMultiClick(view.focusProxy(), textInputCenter, 3); + QVERIFY(selectionChangedSpy.wait()); + QTRY_COMPARE(selectionChangedSpy.count(), 2); + QCOMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input")); + QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QStringLiteral("The Qt Company")); + + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QVERIFY(selectionChangedSpy.wait()); + QCOMPARE(selectionChangedSpy.count(), 3); + QVERIFY(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString().isEmpty()); +} + void tst_QWebEngineView::postData() { QMap<QString, QString> postData; @@ -1362,18 +1493,18 @@ void tst_QWebEngineView::inputFieldOverridesShortcuts() view.addAction(action); QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); - view.setHtml(QString("<html><body onload=\"input1=document.getElementById('input1')\">" + view.setHtml(QString("<html><body>" "<button id=\"btn1\" type=\"button\">push it real good</button>" "<input id=\"input1\" type=\"text\" value=\"x\">" "</body></html>")); QVERIFY(loadFinishedSpy.wait()); view.show(); - QTest::qWaitForWindowActive(&view); + QVERIFY(QTest::qWaitForWindowActive(&view)); auto inputFieldValue = [&view] () -> QString { return evaluateJavaScriptSync(view.page(), - "input1.value").toString(); + "document.getElementById('input1').value").toString(); }; // The input form is not focused. The action is triggered on pressing Shift+Delete. @@ -1390,10 +1521,13 @@ void tst_QWebEngineView::inputFieldOverridesShortcuts() QCOMPARE(inputFieldValue(), QString("x")); // The input form is focused. The action is not triggered, and the form's text changed. - evaluateJavaScriptSync(view.page(), "input1.focus();"); + evaluateJavaScriptSync(view.page(), "document.getElementById('input1').focus();"); + QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); actionTriggered = false; QTest::keyClick(view.windowHandle(), Qt::Key_Y); QTRY_COMPARE(inputFieldValue(), QString("yx")); + QTest::keyClick(view.windowHandle(), Qt::Key_X); + QTRY_COMPARE(inputFieldValue(), QString("yxx")); QVERIFY(!actionTriggered); // The input form is focused. Make sure we don't override all short cuts. @@ -1401,10 +1535,20 @@ void tst_QWebEngineView::inputFieldOverridesShortcuts() action->setShortcut(Qt::CTRL + Qt::Key_1); QTest::keyClick(view.windowHandle(), Qt::Key_1, Qt::ControlModifier); QTRY_VERIFY(actionTriggered); - QCOMPARE(inputFieldValue(), QString("yx")); + QCOMPARE(inputFieldValue(), QString("yxx")); + + // The input form is focused. The following shortcuts are not overridden + // thus handled by Qt WebEngine. Make sure the subsequent shortcuts with text + // character don't cause assert due to an unconsumed editor command. + QTest::keyClick(view.windowHandle(), Qt::Key_A, Qt::ControlModifier); + QTest::keyClick(view.windowHandle(), Qt::Key_C, Qt::ControlModifier); + QTest::keyClick(view.windowHandle(), Qt::Key_V, Qt::ControlModifier); + QTest::keyClick(view.windowHandle(), Qt::Key_V, Qt::ControlModifier); + QTRY_COMPARE(inputFieldValue(), QString("yxxyxx")); // Remove focus from the input field. A QKeySequence::Copy action must be triggerable. evaluateJavaScriptSync(view.page(), "document.getElementById('btn1').focus();"); + QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("btn1")); action->setShortcut(QKeySequence::Copy); actionTriggered = false; QTest::keyClick(view.windowHandle(), Qt::Key_C, Qt::ControlModifier); @@ -1511,7 +1655,7 @@ void tst_QWebEngineView::inputMethods() view.setHtml("<html><body>" " <input type='text' id='input1' style='font-family: serif' value='' maxlength='20' size='50'/>" "</body></html>"); - QVERIFY(loadFinishedSpy.wait()); + QTRY_COMPARE(loadFinishedSpy.size(), 1); QPoint textInputCenter = elementCenter(view.page(), "input1"); QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); @@ -1557,8 +1701,7 @@ void tst_QWebEngineView::inputMethods() QInputMethodEvent eventSelection1("", inputAttributes); QApplication::sendEvent(view.focusProxy(), &eventSelection1); - QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 1); + QTRY_COMPARE(selectionChangedSpy.size(), 1); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 3); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 5); @@ -1569,8 +1712,7 @@ void tst_QWebEngineView::inputMethods() inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 6, -5, QVariant()); QInputMethodEvent eventSelection2("", inputAttributes); QApplication::sendEvent(view.focusProxy(), &eventSelection2); - QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 2); + QTRY_COMPARE(selectionChangedSpy.size(), 2); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 1); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 6); @@ -1583,8 +1725,7 @@ void tst_QWebEngineView::inputMethods() attributes.append(newSelection); QInputMethodEvent eventComposition("composition", attributes); QApplication::sendEvent(view.focusProxy(), &eventComposition); - QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 3); + QTRY_COMPARE(selectionChangedSpy.size(), 3); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("")); // An ongoing composition should not change the surrounding text before it is committed. @@ -1628,14 +1769,17 @@ void tst_QWebEngineView::textSelectionInInputField() event.setCommitString("XXX", 0, 0); QApplication::sendEvent(view.focusProxy(), &event); QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngineXXX")); + QCOMPARE(selectionChangedSpy.count(), 0); event.setCommitString(QString(), -2, 2); // Erase two characters. QApplication::sendEvent(view.focusProxy(), &event); QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngineX")); + QCOMPARE(selectionChangedSpy.count(), 0); event.setCommitString(QString(), -1, 1); // Erase one character. QApplication::sendEvent(view.focusProxy(), &event); QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngine")); + QCOMPARE(selectionChangedSpy.count(), 0); // Move to the start of the line QTest::keyClick(view.focusProxy(), Qt::Key_Home); @@ -1699,13 +1843,8 @@ void tst_QWebEngineView::textSelectionOutOfInputField() QVERIFY(!view.hasSelection()); QVERIFY(view.page()->selectedText().isEmpty()); - // Workaround for macOS: press ctrl+a without key text - QKeyEvent keyPressCtrlA(QEvent::KeyPress, Qt::Key_A, Qt::ControlModifier); - QKeyEvent keyReleaseCtrlA(QEvent::KeyRelease, Qt::Key_A, Qt::ControlModifier); - // Select text by ctrl+a - QApplication::sendEvent(view.focusProxy(), &keyPressCtrlA); - QApplication::sendEvent(view.focusProxy(), &keyReleaseCtrlA); + QTest::keyClick(view.windowHandle(), Qt::Key_A, Qt::ControlModifier); QVERIFY(selectionChangedSpy.wait()); QCOMPARE(selectionChangedSpy.count(), 1); QVERIFY(view.hasSelection()); @@ -1735,8 +1874,7 @@ void tst_QWebEngineView::textSelectionOutOfInputField() QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty()); // Select the whole page by ctrl+a - QApplication::sendEvent(view.focusProxy(), &keyPressCtrlA); - QApplication::sendEvent(view.focusProxy(), &keyReleaseCtrlA); + QTest::keyClick(view.windowHandle(), Qt::Key_A, Qt::ControlModifier); QVERIFY(selectionChangedSpy.wait()); QCOMPARE(selectionChangedSpy.count(), 1); QVERIFY(view.hasSelection()); @@ -1752,8 +1890,7 @@ void tst_QWebEngineView::textSelectionOutOfInputField() QVERIFY(view.page()->selectedText().isEmpty()); // Select the content of the input field by ctrl+a - QApplication::sendEvent(view.focusProxy(), &keyPressCtrlA); - QApplication::sendEvent(view.focusProxy(), &keyReleaseCtrlA); + QTest::keyClick(view.windowHandle(), Qt::Key_A, Qt::ControlModifier); QVERIFY(selectionChangedSpy.wait()); QCOMPARE(selectionChangedSpy.count(), 3); QVERIFY(view.hasSelection()); @@ -1783,7 +1920,7 @@ void tst_QWebEngineView::hiddenText() QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, passwordInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("password1")); - QVERIFY(view.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); + QVERIFY(!view.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); QVERIFY(view.focusProxy()->inputMethodHints() & Qt::ImhHiddenText); QPoint textInputCenter = elementCenter(view.page(), "input1"); @@ -1805,19 +1942,13 @@ void tst_QWebEngineView::emptyInputMethodEvent() QVERIFY(loadFinishedSpy.wait()); evaluateJavaScriptSync(view.page(), "var inputEle = document.getElementById('input1'); inputEle.focus(); inputEle.select();"); - QTRY_VERIFY(!evaluateJavaScriptSync(view.page(), "window.getSelection().toString()").toString().isEmpty()); - - QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue); - QVERIFY(selectionChangedSpy.wait(100)); - QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue); - QCOMPARE(selectionChangedSpy.count(), 1); + QTRY_COMPARE(selectionChangedSpy.count(), 1); // 1. Empty input method event does not clear text QInputMethodEvent emptyEvent; - QApplication::sendEvent(view.focusProxy(), &emptyEvent); - - QString inputValue = evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString(); - QTRY_COMPARE(inputValue, QStringLiteral("QtWebEngine")); + QVERIFY(QApplication::sendEvent(view.focusProxy(), &emptyEvent)); + qApp->processEvents(); + QCOMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString(), QStringLiteral("QtWebEngine")); QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QStringLiteral("QtWebEngine")); // Reset: clear input field @@ -1829,12 +1960,12 @@ void tst_QWebEngineView::emptyInputMethodEvent() // Start IME composition QList<QInputMethodEvent::Attribute> attributes; QInputMethodEvent eventComposition("a", attributes); - QApplication::sendEvent(view.focusProxy(), &eventComposition); + QVERIFY(QApplication::sendEvent(view.focusProxy(), &eventComposition)); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString(), QStringLiteral("a")); QTRY_VERIFY(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString().isEmpty()); // Cancel IME composition - QApplication::sendEvent(view.focusProxy(), &emptyEvent); + QVERIFY(QApplication::sendEvent(view.focusProxy(), &emptyEvent)); QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString().isEmpty()); QTRY_VERIFY(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString().isEmpty()); @@ -1857,12 +1988,7 @@ void tst_QWebEngineView::imeComposition() QVERIFY(loadFinishedSpy.wait()); evaluateJavaScriptSync(view.page(), "var inputEle = document.getElementById('input1'); inputEle.focus(); inputEle.select();"); - QTRY_VERIFY(!evaluateJavaScriptSync(view.page(), "window.getSelection().toString()").toString().isEmpty()); - - QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue); - QVERIFY(selectionChangedSpy.wait(100)); - QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue); - QCOMPARE(selectionChangedSpy.count(), 1); + QTRY_COMPARE(selectionChangedSpy.count(), 1); // Clear the selection, also cancel the ongoing composition if there is one. { @@ -1871,17 +1997,13 @@ void tst_QWebEngineView::imeComposition() attributes.append(newSelection); QInputMethodEvent event("", attributes); QApplication::sendEvent(view.focusProxy(), &event); - QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "window.getSelection().toString()").toString().isEmpty()); - - QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue); - QVERIFY(selectionChangedSpy.wait(100)); - QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue); + selectionChangedSpy.wait(); QCOMPARE(selectionChangedSpy.count(), 2); } - QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngine inputMethod")); - QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 0); - QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 0); - QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("")); + QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngine inputMethod")); + QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 0); + QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 0); + QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("")); selectionChangedSpy.clear(); @@ -2002,19 +2124,14 @@ void tst_QWebEngineView::imeComposition() QList<QInputMethodEvent::Attribute> attributes; QInputMethodEvent event("w", attributes); QApplication::sendEvent(view.focusProxy(), &event); - QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "window.getSelection().toString()").toString().isEmpty()); - QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString(), QString("oeQtWebEngine w")); - - QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue); - QVERIFY(selectionChangedSpy.wait(100)); - QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue); + // The new composition should clear the previous selection + QVERIFY(selectionChangedSpy.wait()); QCOMPARE(selectionChangedSpy.count(), 2); } - QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("oeQtWebEngine ")); - QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 14); - QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue); - QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 14); - QEXPECT_FAIL("", "https://bugreports.qt.io/browse/QTBUG-53134", Continue); + QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("oeQtWebEngine ")); + // The cursor should be positioned at the end of the composition text + QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 15); + QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 15); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("")); // Send commit text, which makes the editor conforms composition. @@ -2029,6 +2146,7 @@ void tst_QWebEngineView::imeComposition() QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 15); QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 15); QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("")); + QCOMPARE(selectionChangedSpy.count(), 2); } void tst_QWebEngineView::newlineInTextarea() @@ -2141,6 +2259,8 @@ void tst_QWebEngineView::imeCompositionQueryEvent_data() void tst_QWebEngineView::imeCompositionQueryEvent() { QWebEngineView view; + view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + view.show(); QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); @@ -2210,6 +2330,104 @@ void tst_QWebEngineView::imeCompositionQueryEvent() QTRY_COMPARE(anchorPosQuery.value(Qt::ImAnchorPosition).toInt(), 11); } +#ifndef QT_NO_CLIPBOARD +void tst_QWebEngineView::globalMouseSelection() +{ + if (!QApplication::clipboard()->supportsSelection()) { + QSKIP("Test only relevant for systems with selection"); + return; + } + + QApplication::clipboard()->clear(QClipboard::Selection); + QWebEngineView view; + view.show(); + + QSignalSpy selectionChangedSpy(&view, SIGNAL(selectionChanged())); + QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); + view.setHtml("<html><body>" + " <input type='text' id='input1' value='QtWebEngine' size='50' />" + "</body></html>"); + QVERIFY(loadFinishedSpy.wait()); + + // Select text via JavaScript + evaluateJavaScriptSync(view.page(), "var inputEle = document.getElementById('input1'); inputEle.focus(); inputEle.select();"); + QTRY_COMPARE(selectionChangedSpy.count(), 1); + QVERIFY(QApplication::clipboard()->text(QClipboard::Selection).isEmpty()); + + // Deselect the selection (this moves the current cursor to the end of the text) + QPoint textInputCenter = elementCenter(view.page(), "input1"); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QVERIFY(selectionChangedSpy.wait()); + QCOMPARE(selectionChangedSpy.count(), 2); + QVERIFY(QApplication::clipboard()->text(QClipboard::Selection).isEmpty()); + + // Select to the start of the line + QTest::keyClick(view.focusProxy(), Qt::Key_Home, Qt::ShiftModifier); + QVERIFY(selectionChangedSpy.wait()); + QCOMPARE(selectionChangedSpy.count(), 3); + QCOMPARE(QApplication::clipboard()->text(QClipboard::Selection), QStringLiteral("QtWebEngine")); +} +#endif + +void tst_QWebEngineView::noContextMenu() +{ + QWidget wrapper; + wrapper.setContextMenuPolicy(Qt::CustomContextMenu); + + connect(&wrapper, &QWidget::customContextMenuRequested, [&wrapper](const QPoint &pt) { + QMenu* menu = new QMenu(&wrapper); + menu->addAction("Action1"); + menu->addAction("Action2"); + menu->popup(pt); + }); + + QWebEngineView view(&wrapper); + view.setContextMenuPolicy(Qt::NoContextMenu); + wrapper.show(); + + QVERIFY(view.findChildren<QMenu *>().isEmpty()); + QVERIFY(wrapper.findChildren<QMenu *>().isEmpty()); + QTest::mouseMove(wrapper.windowHandle(), QPoint(10,10)); + QTest::mouseClick(wrapper.windowHandle(), Qt::RightButton); + + QTRY_COMPARE(wrapper.findChildren<QMenu *>().count(), 1); + QVERIFY(view.findChildren<QMenu *>().isEmpty()); +} + +void tst_QWebEngineView::contextMenu_data() +{ + QTest::addColumn<int>("childrenCount"); + QTest::addColumn<Qt::ContextMenuPolicy>("contextMenuPolicy"); + QTest::newRow("defaultContextMenu") << 1 << Qt::DefaultContextMenu; + QTest::newRow("customContextMenu") << 1 << Qt::CustomContextMenu; + QTest::newRow("preventContextMenu") << 0 << Qt::PreventContextMenu; +} + +void tst_QWebEngineView::contextMenu() +{ + QFETCH(int, childrenCount); + QFETCH(Qt::ContextMenuPolicy, contextMenuPolicy); + + QWebEngineView view; + + if (contextMenuPolicy == Qt::CustomContextMenu) { + connect(&view, &QWebEngineView::customContextMenuRequested, [&view](const QPoint &pt) { + QMenu* menu = new QMenu(&view); + menu->addAction("Action1"); + menu->addAction("Action2"); + menu->popup(pt); + }); + } + + view.setContextMenuPolicy(contextMenuPolicy); + view.show(); + + QVERIFY(view.findChildren<QMenu *>().isEmpty()); + QTest::mouseMove(view.windowHandle(), QPoint(10,10)); + QTest::mouseClick(view.windowHandle(), Qt::RightButton); + QTRY_COMPARE(view.findChildren<QMenu *>().count(), childrenCount); +} + void tst_QWebEngineView::mouseLeave() { QScopedPointer<QWidget> containerWidget(new QWidget); @@ -2252,7 +2470,9 @@ void tst_QWebEngineView::mouseLeave() "</body>" "</html>"); QVERIFY(loadFinishedSpy.wait()); - QVERIFY(innerText().isEmpty()); + // Make sure the testDiv text is empty. + evaluateJavaScriptSync(view->page(), "document.getElementById('testDiv').innerText = ''"); + QTRY_VERIFY(innerText().isEmpty()); QTest::mouseMove(containerWidget->windowHandle(), QPoint(50, 150)); QTRY_COMPARE(innerText(), QStringLiteral("Mouse IN")); @@ -2260,5 +2480,89 @@ void tst_QWebEngineView::mouseLeave() QTRY_COMPARE(innerText(), QStringLiteral("Mouse OUT")); } +void tst_QWebEngineView::webUIURLs_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<bool>("supported"); + QTest::newRow("about") << QUrl("chrome://about") << false; + QTest::newRow("accessibility") << QUrl("chrome://accessibility") << false; + QTest::newRow("appcache-internals") << QUrl("chrome://appcache-internals") << true; + QTest::newRow("apps") << QUrl("chrome://apps") << false; + QTest::newRow("blob-internals") << QUrl("chrome://blob-internals") << true; + QTest::newRow("bluetooth-internals") << QUrl("chrome://bluetooth-internals") << false; + QTest::newRow("bookmarks") << QUrl("chrome://bookmarks") << false; + QTest::newRow("cache") << QUrl("chrome://cache") << false; + QTest::newRow("chrome") << QUrl("chrome://chrome") << false; + QTest::newRow("chrome-urls") << QUrl("chrome://chrome-urls") << false; + QTest::newRow("components") << QUrl("chrome://components") << false; + QTest::newRow("crashes") << QUrl("chrome://crashes") << false; + QTest::newRow("credits") << QUrl("chrome://credits") << false; + QTest::newRow("device-log") << QUrl("chrome://device-log") << false; + QTest::newRow("devices") << QUrl("chrome://devices") << false; + QTest::newRow("dino") << QUrl("chrome://dino") << false; // It works but this is an error page + QTest::newRow("dns") << QUrl("chrome://dns") << false; + QTest::newRow("downloads") << QUrl("chrome://downloads") << false; + QTest::newRow("extensions") << QUrl("chrome://extensions") << false; + QTest::newRow("flags") << QUrl("chrome://flags") << false; + QTest::newRow("flash") << QUrl("chrome://flash") << false; + QTest::newRow("gcm-internals") << QUrl("chrome://gcm-internals") << false; + QTest::newRow("gpu") << QUrl("chrome://gpu") << true; + QTest::newRow("help") << QUrl("chrome://help") << false; + QTest::newRow("histograms") << QUrl("chrome://histograms") << true; + QTest::newRow("indexeddb-internals") << QUrl("chrome://indexeddb-internals") << true; + QTest::newRow("inspect") << QUrl("chrome://inspect") << false; + QTest::newRow("invalidations") << QUrl("chrome://invalidations") << false; + QTest::newRow("linux-proxy-config") << QUrl("chrome://linux-proxy-config") << false; + QTest::newRow("local-state") << QUrl("chrome://local-state") << false; + QTest::newRow("media-internals") << QUrl("chrome://media-internals") << true; + QTest::newRow("net-export") << QUrl("chrome://net-export") << false; + QTest::newRow("net-internals") << QUrl("chrome://net-internals") << false; + QTest::newRow("network-error") << QUrl("chrome://network-error") << false; + QTest::newRow("network-errors") << QUrl("chrome://network-errors") << true; + QTest::newRow("newtab") << QUrl("chrome://newtab") << false; + QTest::newRow("ntp-tiles-internals") << QUrl("chrome://ntp-tiles-internals") << false; + QTest::newRow("omnibox") << QUrl("chrome://omnibox") << false; + QTest::newRow("password-manager-internals") << QUrl("chrome://password-manager-internals") << false; + QTest::newRow("policy") << QUrl("chrome://policy") << false; + QTest::newRow("predictors") << QUrl("chrome://predictors") << false; + QTest::newRow("print") << QUrl("chrome://print") << false; + QTest::newRow("profiler") << QUrl("chrome://profiler") << false; + QTest::newRow("quota-internals") << QUrl("chrome://quota-internals") << false; + QTest::newRow("safe-browsing") << QUrl("chrome://safe-browsing") << false; + QTest::newRow("sandbox") << QUrl("chrome://sandbox") << false; + QTest::newRow("serviceworker-internals") << QUrl("chrome://serviceworker-internals") << true; + QTest::newRow("settings") << QUrl("chrome://settings") << false; + QTest::newRow("signin-internals") << QUrl("chrome://signin-internals") << false; + QTest::newRow("site-engagement") << QUrl("chrome://site-engagement") << false; + QTest::newRow("suggestions") << QUrl("chrome://suggestions") << false; + QTest::newRow("supervised-user-internals") << QUrl("chrome://supervised-user-internals") << false; + QTest::newRow("sync-internals") << QUrl("chrome://sync-internals") << false; + QTest::newRow("system") << QUrl("chrome://system") << false; + QTest::newRow("taskscheduler-internals") << QUrl("chrome://taskscheduler-internals") << false; + QTest::newRow("terms") << QUrl("chrome://terms") << false; + QTest::newRow("thumbnails") << QUrl("chrome://thumbnails") << false; + QTest::newRow("tracing") << QUrl("chrome://tracing") << false; + QTest::newRow("translate-internals") << QUrl("chrome://translate-internals") << false; + QTest::newRow("usb-internals") << QUrl("chrome://usb-internals") << false; + QTest::newRow("user-actions") << QUrl("chrome://user-actions") << false; + QTest::newRow("version") << QUrl("chrome://version") << false; + QTest::newRow("view-http-cache") << QUrl("chrome://view-http-cache") << true; + QTest::newRow("webrtc-internals") << QUrl("chrome://webrtc-internals") << true; + QTest::newRow("webrtc-logs") << QUrl("chrome://webrtc-logs") << false; +} + +void tst_QWebEngineView::webUIURLs() +{ + QFETCH(QUrl, url); + QFETCH(bool, supported); + + QWebEngineView view; + view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); + QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); + view.load(url); + QVERIFY(loadFinishedSpy.wait()); + QCOMPARE(loadFinishedSpy.takeFirst().at(0).toBool(), supported); +} + QTEST_MAIN(tst_QWebEngineView) #include "tst_qwebengineview.moc" diff --git a/tests/auto/widgets/qwebengineview/tst_qwebengineview.qrc b/tests/auto/widgets/qwebengineview/tst_qwebengineview.qrc index 4809bbebf..53b11bca8 100644 --- a/tests/auto/widgets/qwebengineview/tst_qwebengineview.qrc +++ b/tests/auto/widgets/qwebengineview/tst_qwebengineview.qrc @@ -4,7 +4,6 @@ <file>resources/frame_a.html</file> <file>resources/input_types.html</file> <file>resources/scrolltest_page.html</file> - <file>resources/basic_printing_page.html</file> <file>resources/keyboardEvents.html</file> </qresource> </RCC> diff --git a/tests/auto/widgets/tests.pri b/tests/auto/widgets/tests.pri index d77cd5af5..7bd00834c 100644 --- a/tests/auto/widgets/tests.pri +++ b/tests/auto/widgets/tests.pri @@ -1,3 +1,5 @@ +QT_FOR_CONFIG += webengine-private + TEMPLATE = app CONFIG += testcase diff --git a/tests/auto/widgets/util.h b/tests/auto/widgets/util.h index 356cf6ebb..ab3b9e6b9 100644 --- a/tests/auto/widgets/util.h +++ b/tests/auto/widgets/util.h @@ -67,15 +67,16 @@ public: }; template<typename T, typename R> -struct RefWrapper { - R &ref; +struct CallbackWrapper { + QPointer<R> p; void operator()(const T& result) { - ref(result); + if (p) + (*p)(result); } }; template<typename T> -class CallbackSpy { +class CallbackSpy: public QObject { public: CallbackSpy() : called(false) { timeoutTimer.setSingleShot(true); @@ -100,10 +101,9 @@ public: eventLoop.quit(); } - // Cheap rip-off of boost/std::ref - RefWrapper<T, CallbackSpy<T> > ref() + CallbackWrapper<T, CallbackSpy<T> > ref() { - RefWrapper<T, CallbackSpy<T> > wrapper = {*this}; + CallbackWrapper<T, CallbackSpy<T> > wrapper = {this}; return wrapper; } diff --git a/tests/auto/widgets/widgets.pro b/tests/auto/widgets/widgets.pro index 7bab18f00..c414e99f2 100644 --- a/tests/auto/widgets/widgets.pro +++ b/tests/auto/widgets/widgets.pro @@ -1,3 +1,5 @@ +QT_FOR_CONFIG += webengine + TEMPLATE = subdirs SUBDIRS += \ @@ -6,19 +8,19 @@ SUBDIRS += \ qwebenginefaviconmanager \ qwebenginepage \ qwebenginehistory \ - qwebenginehistoryinterface \ qwebengineinspector \ qwebengineprofile \ qwebenginescript \ qwebenginesettings \ + qwebengineshutdown \ qwebengineview qtConfig(accessibility) { SUBDIRS += qwebengineaccessibility } -contains(WEBENGINE_CONFIG, use_spellchecker):!cross_compile { - !contains(WEBENGINE_CONFIG, use_native_spellchecker) { +qtConfig(webengine-spellchecker):!cross_compile { + !qtConfig(webengine-native-spellchecker) { SUBDIRS += qwebenginespellcheck } else { message("Spellcheck test will not be built because it depends on usage of Hunspell dictionaries.") |