diff options
Diffstat (limited to 'tests/auto/widgets')
135 files changed, 7397 insertions, 5121 deletions
diff --git a/tests/auto/widgets/CMakeLists.txt b/tests/auto/widgets/CMakeLists.txt new file mode 100644 index 000000000..9246be68a --- /dev/null +++ b/tests/auto/widgets/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +add_subdirectory(defaultsurfaceformat) +add_subdirectory(qwebenginepage) +add_subdirectory(qwebengineprofile) +add_subdirectory(qwebengineview) +add_subdirectory(favicon) +add_subdirectory(loadsignals) +add_subdirectory(proxy) +add_subdirectory(proxypac) +add_subdirectory(schemes) +add_subdirectory(shutdown) +add_subdirectory(qwebenginedownloadrequest) +add_subdirectory(qwebenginehistory) +add_subdirectory(qwebenginescript) +if(LINUX) + add_subdirectory(offscreen) + add_subdirectory(qtbug_110287) +endif() +if(NOT MACOS) + add_subdirectory(touchinput) +endif() +if(QT_FEATURE_accessibility) + add_subdirectory(accessibility) +endif() +if(QT_FEATURE_webengine_printing_and_pdf) + add_subdirectory(printing) +endif() +if(QT_FEATURE_webengine_spellchecker AND NOT CMAKE_CROSSCOMPILING AND NOT QT_FEATURE_webengine_native_spellchecker) + add_subdirectory(spellchecking) +endif() diff --git a/tests/auto/widgets/accessibility/BLACKLIST b/tests/auto/widgets/accessibility/BLACKLIST new file mode 100644 index 000000000..6fdb0dd58 --- /dev/null +++ b/tests/auto/widgets/accessibility/BLACKLIST @@ -0,0 +1,5 @@ +[roles:ax::mojom::Role::kSvgRoot] +b2qt + +[focusChild] +* diff --git a/tests/auto/widgets/accessibility/CMakeLists.txt b/tests/auto/widgets/accessibility/CMakeLists.txt new file mode 100644 index 000000000..f6a08c9d3 --- /dev/null +++ b/tests/auto/widgets/accessibility/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../util/util.cmake) + +qt_internal_add_test(tst_webengine_accessibility + SOURCES + tst_accessibility.cpp + LIBRARIES + Qt::WebEngineWidgets + Qt::WebEngineCorePrivate + Test::Util +) diff --git a/tests/auto/widgets/accessibility/accessibility.pro b/tests/auto/widgets/accessibility/accessibility.pro deleted file mode 100644 index e99c7f493..000000000 --- a/tests/auto/widgets/accessibility/accessibility.pro +++ /dev/null @@ -1 +0,0 @@ -include(../tests.pri) diff --git a/tests/auto/widgets/accessibility/tst_accessibility.cpp b/tests/auto/widgets/accessibility/tst_accessibility.cpp index d69a4c0a7..1579b61e2 100644 --- a/tests/auto/widgets/accessibility/tst_accessibility.cpp +++ b/tests/auto/widgets/accessibility/tst_accessibility.cpp @@ -17,12 +17,17 @@ Boston, MA 02110-1301, USA. */ +#include <QtWebEngineCore/private/qtwebenginecore-config_p.h> #include <qtest.h> -#include "../util.h" +#include <widgetutil.h> + +#include <QHBoxLayout> +#include <QMainWindow> #include <qaccessible.h> #include <qwebengineview.h> #include <qwebenginepage.h> +#include <qwebenginesettings.h> #include <qwidget.h> class tst_Accessibility : public QObject @@ -38,10 +43,15 @@ public Q_SLOTS: private Q_SLOTS: void noPage(); void hierarchy(); + void focusChild(); + void focusChild_data(); void text(); void value(); void roles_data(); void roles(); + void objectName(); + void crossTreeParent(); + void tableCellInterface(); }; // This will be called before the first test function is executed. @@ -71,11 +81,10 @@ void tst_Accessibility::noPage() QWebEngineView webView; webView.show(); - QTest::qWait(1000); - QAccessibleInterface *view = QAccessible::queryAccessibleInterface(&webView); - QVERIFY(view); + QAccessibleInterface *view = nullptr; + QTRY_VERIFY((view = QAccessible::queryAccessibleInterface(&webView))); QCOMPARE(view->role(), QAccessible::Client); - QCOMPARE(view->childCount(), 1); + QTRY_COMPARE(view->childCount(), 1); QAccessibleInterface *document = view->child(0); QCOMPARE(document->role(), QAccessible::WebDocument); QCOMPARE(document->parent(), view); @@ -98,7 +107,7 @@ void tst_Accessibility::hierarchy() QCOMPARE(view->role(), QAccessible::Client); QCOMPARE(view->childCount(), 1); // Wait for accessibility to be fully initialized - QTRY_VERIFY(view->child(0)->childCount() == 1); + QTRY_COMPARE(view->child(0)->childCount(), 1); QAccessibleInterface *document = view->child(0); QCOMPARE(document->role(), QAccessible::WebDocument); QCOMPARE(document->parent(), view); @@ -142,6 +151,80 @@ void tst_Accessibility::hierarchy() QCOMPARE(input, child); } +void tst_Accessibility::focusChild_data() +{ + QTest::addColumn<QString>("interfaceName"); + QTest::addColumn<QList<QAccessible::Role>>("ancestorRoles"); + + QTest::newRow("QWebEngineView") << QString("QWebEngineView") << QList<QAccessible::Role>({QAccessible::Client}); + QTest::newRow("RenderWidgetHostViewQtDelegate") << QString("RenderWidgetHostViewQtDelegate") << QList<QAccessible::Role>({QAccessible::Client}); + QTest::newRow("QMainWindow") << QString("QMainWindow") << QList<QAccessible::Role>({QAccessible::Window, QAccessible::Client /* central widget */, QAccessible::Client /* view */}); +} + +void tst_Accessibility::focusChild() +{ + auto traverseToWebDocumentAccessibleInterface = [](QAccessibleInterface *iface) -> QAccessibleInterface * { + QFETCH(QList<QAccessible::Role>, ancestorRoles); + for (int i = 0; i < ancestorRoles.size(); ++i) { + if (iface->childCount() == 0 || iface->role() != ancestorRoles[i]) + return nullptr; + iface = iface->child(0); + } + + if (iface->role() != QAccessible::WebDocument) + return nullptr; + + return iface; + }; + + QMainWindow mainWindow; + QWebEngineView *webView = new QWebEngineView; + QWidget *centralWidget = new QWidget; + QHBoxLayout *centralLayout = new QHBoxLayout; + centralWidget->setLayout(centralLayout); + mainWindow.setCentralWidget(centralWidget); + centralLayout->addWidget(webView); + + mainWindow.show(); + QVERIFY(QTest::qWaitForWindowExposed(&mainWindow)); + + webView->settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + webView->setHtml("<html><body>" \ + "<input id='input1' type='text' value='some text'/>" \ + "</body></html>"); + webView->show(); + QSignalSpy spyFinished(webView, &QWebEngineView::loadFinished); + QVERIFY(spyFinished.wait()); + + QVERIFY(webView->focusWidget()); + QAccessibleInterface *iface = nullptr; + QFETCH(QString, interfaceName); + if (interfaceName == "QWebEngineView") + iface = QAccessible::queryAccessibleInterface(webView); + else if (interfaceName == "RenderWidgetHostViewQtDelegate") + iface = QAccessible::queryAccessibleInterface(webView->focusWidget()); + else if (interfaceName == "QMainWindow") + iface = QAccessible::queryAccessibleInterface(&mainWindow); + QVERIFY(iface); + + // Make sure the input field does not have the focus. + evaluateJavaScriptSync(webView->page(), "document.getElementById('input1').blur()"); + QTRY_VERIFY(evaluateJavaScriptSync(webView->page(), "document.activeElement.id").toString().isEmpty()); + + QVERIFY(iface->focusChild()); + QTRY_COMPARE(iface->focusChild()->role(), QAccessible::WebDocument); + QCOMPARE(traverseToWebDocumentAccessibleInterface(iface), iface->focusChild()); + + // Set active focus on the input field. + evaluateJavaScriptSync(webView->page(), "document.getElementById('input1').focus()"); + QTRY_COMPARE(evaluateJavaScriptSync(webView->page(), "document.activeElement.id").toString(), QStringLiteral("input1")); + + QVERIFY(iface->focusChild()); + QTRY_COMPARE(iface->focusChild()->role(), QAccessible::EditableText); + // <html> -> <body> -> <input> + QCOMPARE(traverseToWebDocumentAccessibleInterface(iface)->child(0)->child(0), iface->focusChild()); +} + void tst_Accessibility::text() { QWebEngineView webView; @@ -159,7 +242,7 @@ void tst_Accessibility::text() QAccessibleInterface *view = QAccessible::queryAccessibleInterface(&webView); // Wait for accessibility to be fully initialized - QTRY_VERIFY(view->child(0)->childCount() == 5); + QTRY_COMPARE(view->child(0)->childCount(), 5); QAccessibleInterface *document = view->child(0); QVERIFY(document); @@ -253,145 +336,205 @@ void tst_Accessibility::value() void tst_Accessibility::roles_data() { QTest::addColumn<QString>("html"); - QTest::addColumn<bool>("isSection"); + QTest::addColumn<int>("nested"); 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_BUTTON") << QString("<button>a</button>") << false << QAccessible::Button; - //QTest::newRow("AX_ROLE_BUTTON_DROP_DOWN"); // TODO: Remove this during the next Chromium update: https://chromium-review.googlesource.com/842475 - //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_GROUPING") << QString("<div role='combobox'><input></div>") << true << QAccessible::ComboBox; - QTest::newRow("AX_ROLE_COMBO_BOX_MENU_BUTTON") << QString("<div tabindex=0 role='combobox'>Select</div>") << true << QAccessible::ComboBox; - QTest::newRow("AX_ROLE_TEXT_FIELD_WITH_COMBO_BOX") << QString("<input role='combobox'>") << 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_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 multiple></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_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><option>a</option></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_SCROLL_BAR") << QString("<div role='scrollbar'>a</a>") << true << QAccessible::ScrollBar; - 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_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 + QTest::newRow("ax::mojom::Role::kAbbr") << QString("<abbr>a</abbr>") << 1 << QAccessible::StaticText; + QTest::newRow("ax::mojom::Role::kAlert") << QString("<div role='alert'>alert</div>") << 0 << QAccessible::AlertMessage; + QTest::newRow("ax::mojom::Role::kAlertDialog") << QString("<div role='alertdialog'>alert</div>") << 0 << QAccessible::AlertMessage; + QTest::newRow("ax::mojom::Role::kAnchor") << QString("<a id='a'>Chapter a</a>") << 1 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kApplication") << QString("<div role='application'>landmark</div>") << 0 << QAccessible::Document; + QTest::newRow("ax::mojom::Role::kArticle") << QString("<article>a</article>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kAudio") << QString("<audio controls><source src='test.mp3' type='audio/mpeg'></audio>") << 1 << QAccessible::Sound; + QTest::newRow("ax::mojom::Role::kBanner") << QString("<div role='banner'>a</div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kBlockquote") << QString("<blockquote>a</blockquote>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kButton") << QString("<button>a</button>") << 1 << QAccessible::Button; + //QTest::newRow("ax::mojom::Role::kCanvas") << QString("<canvas width='10' height='10'></canvas>") << 0 << QAccessible::Canvas; // FIXME: The test case might be wrong (see AXLayoutObject.cpp) + QTest::newRow("ax::mojom::Role::kCaption") << QString("<table><caption>a</caption></table>") << 1 << QAccessible::Heading; + //QTest::newRow("ax::mojom::Role::kCaret"); // No mapping to ARIA role + QTest::newRow("ax::mojom::Role::kCell") << QString("<table role=table><tr><td>a</td></tr></table>") << 2 << QAccessible::Cell; + QTest::newRow("ax::mojom::Role::kCheckBox") << QString("<input type='checkbox'>a</input>") << 1 << QAccessible::CheckBox; + QTest::newRow("ax::mojom::Role::kClient") << QString("") << 0 << QAccessible::Client; + QTest::newRow("ax::mojom::Role::kCode") << QString("<code>a</code>") << 1 << QAccessible::StaticText; + QTest::newRow("ax::mojom::Role::kColorWell") << QString("<input type='color'>a</input>") << 1 << QAccessible::ColorChooser; + //QTest::newRow("ax::mojom::Role::kColumn") << QString("<table><tr><td>a</td></tr></table>") << 0 << QAccessible::Column; // FIXME: The test case might be wrong (see AXTableColumn.h) + QTest::newRow("ax::mojom::Role::kColumnHeader") << QString("<table role=table><tr><th>a</th></tr><tr><td>a</td></tr></table>") << 2 << QAccessible::ColumnHeader; + QTest::newRow("ax::mojom::Role::kComboBoxGrouping") << QString("<div role='combobox'><input></div>") << 0 << QAccessible::ComboBox; + QTest::newRow("ax::mojom::Role::kComboBoxMenuButton") << QString("<div tabindex=0 role='combobox'>Select</div>") << 0 << QAccessible::ComboBox; + QTest::newRow("ax::mojom::Role::kTextFieldWithComboBox") << QString("<input role='combobox'>") << 1 << QAccessible::ComboBox; + QTest::newRow("ax::mojom::Role::kComplementary") << QString("<aside>a</aside>") << 0 << QAccessible::ComplementaryContent; + QTest::newRow("ax::mojom::Role::kComment") << QString("<div role='comment'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kContentDeletion") << QString("<div role='deletion'></div>") << 0 << QAccessible::Grouping; + QTest::newRow("ax::mojom::Role::kContentInsertion") << QString("<div role='insertion'></div>") << 0 << QAccessible::Grouping; + QTest::newRow("ax::mojom::Role::kContentInfo") << QString("<div role='contentinfo'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kData") << QString("<input type='date'></input>") << 1 << QAccessible::Clock; + QTest::newRow("ax::mojom::Role::kDateTime") << QString("<input type='datetime-local'></input>") << 1 << QAccessible::Clock; + QTest::newRow("ax::mojom::Role::kDefinition") << QString("<div role='definition'>landmark</div>") << 0 << QAccessible::Paragraph; + QTest::newRow("ax::mojom::Role::kDescriptionList") << QString("<dl>a</dl>") << 0 << QAccessible::List; + QTest::newRow("ax::mojom::Role::kDescriptionListDetail") << QString("<dd>a</dd>") << 0 << QAccessible::Paragraph; + QTest::newRow("ax::mojom::Role::kDescriptionListTerm") << QString("<dt>a</dt>") << 0 << QAccessible::ListItem; + QTest::newRow("ax::mojom::Role::kDetails") << QString("<details>a</details>") << 0 << QAccessible::Grouping; + //QTest::newRow("ax::mojom::Role::kDesktop"); // No mapping to ARIA role + QTest::newRow("ax::mojom::Role::kDialog") << QString("<div role='dialog'></div>") << 0 << QAccessible::Dialog; + //QTest::newRow("ax::mojom::Role::kDirectory") << QString("<ul role='directory'></ul>") << 0 << QAccessible::List; // FIXME: Aria role 'directory' should work + QTest::newRow("ax::mojom::Role::kDisclosureTriangle") << QString("<details><summary>a</summary></details>") << 1 << QAccessible::Button; + QTest::newRow("ax::mojom::Role::kGenericContainer") << QString("<div>a</div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocCover") << QString("<div role='doc-cover'></div>") << 0 << QAccessible::Graphic; + QTest::newRow("ax::mojom::Role::kDocBackLink") << QString("<div role='doc-backlink'></div>") << 0 << QAccessible::Link; + QTest::newRow("ax::mojom::Role::kDocBiblioRef") << QString("<div role='doc-biblioref'></div>") << 0 << QAccessible::Link; + QTest::newRow("ax::mojom::Role::kDocGlossRef") << QString("<div role='doc-glossref'></div>") << 0 << QAccessible::Link; + QTest::newRow("ax::mojom::Role::kDocNoteRef") << QString("<div role='doc-noteref'></div>") << 0 << QAccessible::Link; + QTest::newRow("ax::mojom::Role::kDocBiblioEntry") << QString("<div role='doc-biblioentry'></div>") << 0 << QAccessible::ListItem; + QTest::newRow("ax::mojom::Role::kDocEndnote") << QString("<div role='doc-endnote'></div>") << 0 << QAccessible::ListItem; + QTest::newRow("ax::mojom::Role::kDocFootnote") << QString("<div role='doc-footnote'></div>") << 0 << QAccessible::ListItem; + QTest::newRow("ax::mojom::Role::kDocPageBreak") << QString("<div role='doc-pagebreak'></div>") << 0 << QAccessible::Separator; + QTest::newRow("ax::mojom::Role::kDocAbstract") << QString("<div role='doc-abstract'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocAcknowledgements") << QString("<div role='doc-acknowledgments'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocAfterword") << QString("<div role='doc-afterword'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocAppendix") << QString("<div role='doc-appendix'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocBibliography") << QString("<div role='doc-bibliography'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocChapter") << QString("<div role='doc-chapter'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocColophon") << QString("<div role='doc-colophon'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocConclusion") << QString("<div role='doc-conclusion'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocCredit") << QString("<div role='doc-credit'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocCredits") << QString("<div role='doc-credits'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocDedication") << QString("<div role='doc-dedication'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocEndnotes") << QString("<div role='doc-endnotes'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocEpigraph") << QString("<div role='doc-epigraph'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocEpilogue") << QString("<div role='doc-epilogue'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocErrata") << QString("<div role='doc-errata'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocExample") << QString("<div role='doc-example'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocFooter") << QString("<section role='doc-pagefooter'>a</section>") << 0 << QAccessible::Footer; + QTest::newRow("ax::mojom::Role::kDocForeword") << QString("<div role='doc-foreword'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocGlossary") << QString("<div role='doc-glossary'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocHeader") << QString("<section role='doc-pageheader'>a</section>") << 0 << QAccessible::Heading; + QTest::newRow("ax::mojom::Role::kDocIndex") << QString("<div role='doc-index'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocIntroduction") << QString("<div role='doc-introduction'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocNotice") << QString("<div role='doc-notice'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocPageList") << QString("<div role='doc-pagelist'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocPart") << QString("<div role='doc-part'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocPreface") << QString("<div role='doc-preface'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocPrologue") << QString("<div role='doc-prologue'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocPullquote") << QString("<div role='doc-pullquote'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocQna") << QString("<div role='doc-qna'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocSubtitle") << QString("<div role='doc-subtitle'></div>") << 0 << QAccessible::Heading; + QTest::newRow("ax::mojom::Role::kDocTip") << QString("<div role='doc-tip'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocToc") << QString("<div role='doc-toc'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kDocument") << QString("<div role='document'>a</div>") << 0 << QAccessible::Document; + QTest::newRow("ax::mojom::Role::kEmbeddedObject") << QString("<object width='10' height='10'></object>") << 1 << QAccessible::Grouping; + QTest::newRow("ax::mojom::Role::kEmphasis") << QString("<em>a</em>") << 1 << QAccessible::StaticText; + QTest::newRow("ax::mojom::Role::kFeed") << QString("<div role='feed'>a</div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kFigcaption") << QString("<figcaption>a</figcaption>") << 0 << QAccessible::Heading; + QTest::newRow("ax::mojom::Role::kFigure") << QString("<figure>a</figure>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kFooter") << QString("<footer>a</footer>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kFooterAsNonLandmark") << QString("<article><footer>a</footer><article>") << 1 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kForm") << QString("<form aria-label=Name></form>") << 0 << QAccessible::Form; + QTest::newRow("ax::mojom::Role::kGraphicsDocument") << QString("<div role='graphics-document'></div>") << 0 << QAccessible::Document; + QTest::newRow("ax::mojom::Role::kGraphicsObject") << QString("<div role='graphics-object'></div>") << 0 << QAccessible::Pane; + QTest::newRow("ax::mojom::Role::kGraphicsSymbol") << QString("<div role='graphics-symbol'></div>") << 0 << QAccessible::Graphic; + QTest::newRow("ax::mojom::Role::kGrid") << QString("<div role='grid'></div>") << 0 << QAccessible::Table; + QTest::newRow("ax::mojom::Role::kGroup") << QString("<fieldset></fieldset>") << 0 << QAccessible::Grouping; + QTest::newRow("ax::mojom::Role::Header") << QString("<header>a</header>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::HeaderAsNonLandMark") << QString("<article><header>a</header><article>") << 1 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kHeading") << QString("<h1>a</h1>") << 0 << QAccessible::Heading; + QTest::newRow("ax::mojom::Role::kIframe") << QString("<iframe>a</iframe>") << 1 << QAccessible::WebDocument; + QTest::newRow("ax::mojom::Role::kIframePresentational") << QString("<iframe role='presentation'>a</iframe>") << 1 << QAccessible::Grouping; + //QTest::newRow("ax::mojom::Role::kIgnored") << QString("<tag>a</tag>") << 0 << QAccessible::NoRole; // FIXME: The HTML element should not be exposed as an element (see AXNodeObject.cpp) + QTest::newRow("ax::mojom::Role::kImage") << QString("<img>") << 1 << QAccessible::Graphic; + //QTest::newRow("ax::mojom::Role::kImageMap") << QString("<img usemap='map'>") << 0 << QAccessible::Document; // FIXME: AXLayoutObject::DetermineAccessiblityRole returns kImageMap but something overrides it + //QTest::newRow("ax::mojom::Role::kInlineTextBox"); // No mapping to ARIA role + QTest::newRow("ax::mojom::Role::kInputTime") << QString("<input type='time'></input>") << 1 << QAccessible::SpinBox; + //QTest::newRow("ax::mojom::Role::kKeyboard"); // No mapping to ARIA role + QTest::newRow("ax::mojom::Role::kLabelText") << QString("<label>a</label>") << 1 << QAccessible::StaticText; + QTest::newRow("ax::mojom::Role::kLayoutTable") << QString("<table><tr><td></td></tr></table>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kLayoutTableCell") << QString("<table><tr><td></td></tr></table>") << 2 << QAccessible::Section; + //QTest::newRow("ax::mojom::Role::kLayoutTableColumn") << QString("<table><tr></tr></table>") << 1 << QAccessible::Section; // FIXME: The test case might be wrong + QTest::newRow("ax::mojom::Role::kLayoutTableRow") << QString("<table><tr><td></td></tr></table>") << 1 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kLegend") << QString("<legend>a</legend>") << 0 << QAccessible::StaticText; + QTest::newRow("ax::mojom::Role::kLineBreak") << QString("<br>") << 1 << QAccessible::Separator; + QTest::newRow("ax::mojom::Role::kLink") << QString("<a href=''>link</a>") << 1 << QAccessible::Link; + QTest::newRow("ax::mojom::Role::kList") << QString("<ul></ul>") << 0 << QAccessible::List; + QTest::newRow("ax::mojom::Role::kListBox") << QString("<select multiple></select>") << 1 << QAccessible::ComboBox; + QTest::newRow("ax::mojom::Role::kListBoxOption") << QString("<option>a</option>") << 0 << QAccessible::ListItem; + QTest::newRow("ax::mojom::Role::kListItem") << QString("<li>a</li>") << 0 << QAccessible::ListItem; + //QTest::newRow("ax::mojom::Role::kListGrid"); // No mapping to ARIA role + QTest::newRow("ax::mojom::Role::kListMarker") << QString("<li><ul></ul></li>") << 1 << QAccessible::StaticText; + QTest::newRow("ax::mojom::Role::kLog") << QString("<div role='log'>a</div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kMain") << QString("<main>a</main>") << 0 << QAccessible::Grouping; + QTest::newRow("ax::mojom::Role::kMark") << QString("<mark>a</mark>") << 1 << QAccessible::StaticText; + QTest::newRow("ax::mojom::Role::kMarquee") << QString("<div role='marquee'>a</div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kMath") << QString("<math>a</math>") << 1 << QAccessible::Equation; + QTest::newRow("ax::mojom::Role::kMenu") << QString("<div role='menu'>a</div>") << 0 << QAccessible::PopupMenu; + QTest::newRow("ax::mojom::Role::kMenuBar") << QString("<div role='menubar'>a</div>") << 0 << QAccessible::MenuBar; + QTest::newRow("ax::mojom::Role::kMenuItem") << QString("<menu role='group'><div role='menuitem'>a</div></menu>") << 1 << QAccessible::MenuItem; + QTest::newRow("ax::mojom::Role::kMenuItemCheckBox") << QString("<menu role='menu'><input type='checkbox'></input></menu>") << 1 << QAccessible::CheckBox; + QTest::newRow("ax::mojom::Role::kMenuItemRadio") << QString("<menu role='menu'><input type='radio'></input></menu>") << 1 << QAccessible::RadioButton; + QTest::newRow("ax::mojom::Role::kMenuButton") << QString("<menu role='menu'><input type='button' /></menu>") << 1 << QAccessible::Button; + QTest::newRow("ax::mojom::Role::kMenuListOption") << QString("<select role='menu'><option>a</option></select>") << 3 << QAccessible::MenuItem; + QTest::newRow("ax::mojom::Role::kMenuListPopup") << QString("<select role='menu'><option>a</option></select>") << 2 << QAccessible::PopupMenu; + QTest::newRow("ax::mojom::Role::kMeter") << QString("<meter>a</meter>") << 1 << QAccessible::Chart; + QTest::newRow("ax::mojom::Role::kNavigation") << QString("<nav>a</nav>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kNote") << QString("<div role='note'>a</div>") << 0 << QAccessible::Note; + //QTest::newRow("ax::mojom::Role::kPane"); // No mapping to ARIA role + QTest::newRow("ax::mojom::Role::kParagraph") << QString("<p>a</p>") << 0 << QAccessible::Paragraph; + QTest::newRow("ax::mojom::Role::kPopUpButton") << QString("<select><option>a</option></select>") << 1 << QAccessible::PopupMenu; + QTest::newRow("ax::mojom::Role::kPre") << QString("<pre>a</pre>") << 0 << QAccessible::Section; + //QTest::newRow("ax::mojom::Role::kPresentational") << QString("<div role='presentation'>a</div>") << 0 << QAccessible::NoRole; // FIXME: Aria role 'presentation' should work + QTest::newRow("ax::mojom::Role::kProgressIndicator") << QString("<div role='progressbar' aria-valuenow='77' aria-valuemin='22' aria-valuemax='99'></div>") << 0 << QAccessible::ProgressBar; + QTest::newRow("ax::mojom::Role::kRadioButton") << QString("<input type='radio'></input>") << 1 << QAccessible::RadioButton; + QTest::newRow("ax::mojom::Role::kRadioGroup") << QString("<fieldset role='radiogroup'></fieldset>") << 0 << QAccessible::Grouping; + QTest::newRow("ax::mojom::Role::kRegion") << QString("<div role='region'>a</div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kRow") << QString("<table role=table><tr><td>a</td></tr></table>") << 1 << QAccessible::Row; + QTest::newRow("ax::mojom::Role::kRowGroup") << QString("<table role=table><tbody role=rowgroup><tr><td>a</td></tr></tbody></table>") << 1 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kRowHeader") << QString("<table role=table><tr><th>a</td><td>b</td></tr></table>") << 2 << QAccessible::RowHeader; + QTest::newRow("ax::mojom::Role::kRuby") << QString("<ruby>a</ruby>") << 1 << QAccessible::Grouping; + //QTest::newRow("ax::mojom::Role::kRubyAnnotation") // No mapping to ARIA role (presents as property on enclosing ruby element) + QTest::newRow("ax::mojom::Role::kScrollBar") << QString("<div role='scrollbar'>a</a>") << 0 << QAccessible::ScrollBar; + //QTest::newRow("ax::mojom::Role::kScrollView"); // No mapping to ARIA role + QTest::newRow("ax::mojom::Role::kSearch") << QString("<div role='search'>landmark</div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kSearchBox") << QString("<input type='search'></input>") << 1 << QAccessible::EditableText; + QTest::newRow("ax::mojom::Role::kSection") << QString("<section></section>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kSlider") << QString("<input type='range'>") << 1 << QAccessible::Slider; + //QTest::newRow("ax::mojom::Role::kSliderThumb") << QString("<input type='range'>") << 1 << QAccessible::Slider; // TODO: blink/renderer/modules/accessibility/ax_slider.cc + QTest::newRow("ax::mojom::Role::kSpinButton") << QString("<input type='number'></input>") << 1 << QAccessible::SpinBox; + QTest::newRow("ax::mojom::Role::kSplitter") << QString("<hr>") << 0 << QAccessible::Splitter; + QTest::newRow("ax::mojom::Role::kStaticText") << QString("a") << 1 << QAccessible::StaticText; + QTest::newRow("ax::mojom::Role::kStatus") << QString("<output>a</output>") << 1 << QAccessible::Indicator; + QTest::newRow("ax::mojom::Role::kStrong") << QString("<strong>a</strong>") << 1 << QAccessible::StaticText; + QTest::newRow("ax::mojom::Role::kSuggestion") << QString("<div role='suggestion'></div>") << 0 << QAccessible::Section; + QTest::newRow("ax::mojom::Role::kSvgRoot") << QString("<svg width='10' height='10'><text font-size='10'>SVG</text></svg>") << 1 << QAccessible::WebDocument; + QTest::newRow("ax::mojom::Role::kSwitch") << QString("<button aria-checked='false'>a</button>") << 1 << QAccessible::Button; + QTest::newRow("ax::mojom::Role::kTable") << QString("<table role=table><td>a</td></table>") << 0 << QAccessible::Table; + //QTest::newRow("ax::mojom::Role::kTableHeaderContainer"); // No mapping to ARIA role + QTest::newRow("ax::mojom::Role::kTab") << QString("<div role='tab'>a</div>") << 0 << QAccessible::PageTab; + QTest::newRow("ax::mojom::Role::kTabList") << QString("<div role='tablist'>a</div>") << 0 << QAccessible::PageTabList; + QTest::newRow("ax::mojom::Role::kTabPanel") << QString("<div role='tabpanel'>a</div>") << 0 << QAccessible::Pane; + QTest::newRow("ax::mojom::Role::kTerm") << QString("<div role='term'>a</div>") << 0 << QAccessible::StaticText; + QTest::newRow("ax::mojom::Role::kTextField") << QString("<input type='text'></input>") << 1 << QAccessible::EditableText; + QTest::newRow("ax::mojom::Role::kTime") << QString("<time>a</time>") << 1 << QAccessible::Clock; + QTest::newRow("ax::mojom::Role::kTimer") << QString("<div role='timer'>a</div>") << 0 << QAccessible::Clock; + //QTest::newRow("ax::mojom::Role::kTitleBar"); // No mapping to ARIA role + QTest::newRow("ax::mojom::Role::kToggleButton") << QString("<button aria-pressed='false'>a</button>") << 1 << QAccessible::Button; + QTest::newRow("ax::mojom::Role::kToolbar") << QString("<div role='toolbar'>a</div>") << 0 << QAccessible::ToolBar; + QTest::newRow("ax::mojom::Role::kToolTip") << QString("<div role='tooltip'>a</div>") << 0 << QAccessible::ToolTip; + QTest::newRow("ax::mojom::Role::kTree") << QString("<div role='tree'>a</div>") << 0 << QAccessible::Tree; + QTest::newRow("ax::mojom::Role::kTreeGrid") << QString("<div role='treegrid'>a</div>") << 0 << QAccessible::Tree; + QTest::newRow("ax::mojom::Role::kTreeItem") << QString("<div role='treeitem'>a</div>") << 0 << QAccessible::TreeItem; + QTest::newRow("ax::mojom::Role::kVideo") << QString("<video><source src='test.mp4' type='video/mp4'></video>") << 1 << QAccessible::Animation; + //QTest::newRow("ax::mojom::Role::kWindow"); // No mapping to ARIA role } void tst_Accessibility::roles() { QFETCH(QString, html); - QFETCH(bool, isSection); + QFETCH(int, nested); QFETCH(QAccessible::Role, role); QWebEngineView webView; + QSignalSpy spyFinished(&webView, &QWebEngineView::loadFinished); webView.setHtml("<html><body>" + html + "</body></html>"); webView.show(); - QSignalSpy spyFinished(&webView, &QWebEngineView::loadFinished); - QVERIFY(spyFinished.wait()); + QTRY_COMPARE_WITH_TIMEOUT(spyFinished.size(), 1, 20000); QAccessibleInterface *view = QAccessible::queryAccessibleInterface(&webView); @@ -401,22 +544,128 @@ void tst_Accessibility::roles() return; } - QTRY_COMPARE(view->child(0)->childCount(), 1); + QTRY_COMPARE_WITH_TIMEOUT(view->child(0)->childCount(), 1, 20000); QAccessibleInterface *document = view->child(0); - QAccessibleInterface *section = document->child(0); + QAccessibleInterface *element = document->child(0); - if (isSection) { - QCOMPARE(section->role(), role); - return; + while (nested--) { + QTRY_VERIFY(element->child(0)); + element = element->child(0); } - QVERIFY(section->childCount() > 0); - QAccessibleInterface *element = section->child(0); QCOMPARE(element->role(), role); } +void tst_Accessibility::objectName() +{ + QWebEngineView webView; + QSignalSpy spyFinished(&webView, &QWebEngineView::loadFinished); + webView.setHtml("<html><body><p id='my_id'></p></body></html>"); + webView.show(); + QVERIFY(spyFinished.wait()); + QAccessibleInterface *view = QAccessible::queryAccessibleInterface(&webView); + QAccessibleInterface *document = view->child(0); + QTRY_COMPARE(document->childCount(), 1); + QAccessibleInterface *p = document->child(0); + QVERIFY(p); + QVERIFY(p->object()); + QCOMPARE(p->role(), QAccessible::Paragraph); + QCOMPARE(p->object()->objectName(), QStringLiteral("my_id")); +} + +void tst_Accessibility::crossTreeParent() +{ + QWebEngineView webView; + QSignalSpy spyFinished(&webView, &QWebEngineView::loadFinished); + webView.setHtml("<html><body><iframe src='data:text/html,<html><body><p id=my_id></p></body></html>'>Fallback text</iframe></body></html>"); + webView.show(); + QVERIFY(spyFinished.wait()); + QAccessibleInterface *view = QAccessible::queryAccessibleInterface(&webView); + QAccessibleInterface *document = view->child(0); + QCOMPARE(document->role(), QAccessible::WebDocument); + QTRY_COMPARE(document->childCount(), 1); + QAccessibleInterface *p = document->child(0); + QVERIFY(p); + QCOMPARE(p->parent(), document); + p = p->child(0); + QVERIFY(p); + QCOMPARE(p->role(), QAccessible::WebDocument); + QCOMPARE(p->parent()->parent(), document); + QTRY_COMPARE(p->childCount(), 1); + p = p->child(0); + QVERIFY(p); + QAccessibleInterface *subdocument = p; + QCOMPARE(p->role(), QAccessible::WebDocument); + QCOMPARE(p->parent()->parent()->parent(), document); + p = p->child(0); + QVERIFY(p); + QVERIFY(p->object()); + QCOMPARE(p->role(), QAccessible::Paragraph); + QCOMPARE(p->parent(), subdocument); + QCOMPARE(p->parent()->parent()->parent()->parent(), document); + QCOMPARE(p->parent()->parent()->parent()->parent()->parent(), view); + QCOMPARE(p->object()->objectName(), QStringLiteral("my_id")); +} + +void tst_Accessibility::tableCellInterface() +{ + QWebEngineView webView; + webView.resize(400, 400); + webView.show(); + QVERIFY(QTest::qWaitForWindowExposed(&webView)); + + QSignalSpy spyFinished(&webView, &QWebEngineView::loadFinished); + webView.setHtml(QLatin1String( + "<html><body>" + " <ul>" + " <li><a href='#link1' id='link1'>Link in ListItem</a></li>" + " </ul>" + "" + " <div role='rowgroup'>" + " <div role='row'>" + " <span role='cell'><a href='#link2' id='link2'>Link in Cell</a></span>" + " </div>" + " </div>" + "</body></html>")); + QTRY_COMPARE(spyFinished.size(), 1); + + QAccessibleInterface *view = QAccessible::queryAccessibleInterface(&webView); + QAccessibleInterface *document = view->child(0); + QTRY_COMPARE(document->childCount(), 2); + + // ListItem without Table parent. + { + QAccessibleInterface *list = document->child(0); + QAccessibleInterface *listItem = list->child(0); + QVERIFY(!listItem->tableCellInterface()); + + // Should not crash. + QPoint linkCenter = elementCenter(webView.page(), QLatin1String("link1")); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, linkCenter); + QTRY_COMPARE(webView.url().fragment(), QLatin1String("link1")); + } + + // Cell without Table parent. + { + QAccessibleInterface *rowgroup = document->child(1); + QAccessibleInterface *row = rowgroup->child(0); + QAccessibleInterface *cell = row->child(0); + QVERIFY(!cell->tableCellInterface()); + + // Should not crash. + QPoint linkCenter = elementCenter(webView.page(), QLatin1String("link2")); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, linkCenter); + QTRY_COMPARE(webView.url().fragment(), QLatin1String("link2")); + } +} + static QByteArrayList params = QByteArrayList() - << "--force-renderer-accessibility"; + << "--force-renderer-accessibility" + << "--enable-features=AccessibilityExposeARIAAnnotations" +#if QT_CONFIG(webengine_embedded_build) + << "--disable-features=TimedHTMLParserBudget" +#endif + ; W_QTEST_MAIN(tst_Accessibility, params) #include "tst_accessibility.moc" diff --git a/tests/auto/widgets/certificateerror/certificateerror.pro b/tests/auto/widgets/certificateerror/certificateerror.pro deleted file mode 100644 index 73ba7515b..000000000 --- a/tests/auto/widgets/certificateerror/certificateerror.pro +++ /dev/null @@ -1,3 +0,0 @@ -include(../tests.pri) -include(../../shared/https.pri) -QT *= core-private diff --git a/tests/auto/widgets/certificateerror/tst_certificateerror.cpp b/tests/auto/widgets/certificateerror/tst_certificateerror.cpp deleted file mode 100644 index 5fd765ed5..000000000 --- a/tests/auto/widgets/certificateerror/tst_certificateerror.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -#include <httpsserver.h> -#include <util.h> - -#include <QWebEngineCertificateError> -#include <QWebEnginePage> -#include <QWebEngineSettings> - -#include <QtTest/QtTest> - -class tst_CertificateError : public QObject -{ - Q_OBJECT -public: - tst_CertificateError() { } - -private Q_SLOTS: - void handleError_data(); - void handleError(); -}; - -struct PageWithCertificateErrorHandler : QWebEnginePage -{ - PageWithCertificateErrorHandler(bool defer, bool accept, QObject *p = nullptr) - : QWebEnginePage(p), deferError(defer), acceptCertificate(accept) { - connect(this, &QWebEnginePage::loadFinished, [&] (bool result) { spyLoad(result); }); - } - - bool deferError, acceptCertificate; - - CallbackSpy<bool> spyLoad; - QScopedPointer<QWebEngineCertificateError> error; - - bool certificateError(const QWebEngineCertificateError &e) override { - error.reset(new QWebEngineCertificateError(e)); - if (deferError) - error->defer(); - return acceptCertificate; - } -}; - -void tst_CertificateError::handleError_data() -{ - QTest::addColumn<bool>("deferError"); - QTest::addColumn<bool>("acceptCertificate"); - QTest::addColumn<QString>("expectedContent"); - QTest::addRow("Reject") << false << false << QString(); - QTest::addRow("DeferReject") << true << false << QString(); - QTest::addRow("DeferAccept") << true << true << "TEST"; -} - -void tst_CertificateError::handleError() -{ - HttpsServer server; - server.setExpectError(true); - QVERIFY(server.start()); - - connect(&server, &HttpsServer::newRequest, [&] (HttpReqRep *rr) { - rr->setResponseBody(QByteArrayLiteral("<html><body>TEST</body></html>")); - rr->sendResponse(); - }); - - QFETCH(bool, deferError); - QFETCH(bool, acceptCertificate); - QFETCH(QString, expectedContent); - - PageWithCertificateErrorHandler page(deferError, acceptCertificate); - page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); - - page.setUrl(server.url()); - QTRY_VERIFY(page.error); - QVERIFY(page.error->isOverridable()); - auto chain = page.error->chain(); - QCOMPARE(chain.size(), 2); - QCOMPARE(chain[0].serialNumber(), "3b:dd:1a:b7:2f:40:32:3b:c1:bf:37:d4:86:bd:56:c1:d0:6b:2a:43"); - QCOMPARE(chain[1].serialNumber(), "6d:52:fb:b4:57:3b:b2:03:c8:62:7b:7e:44:45:5c:d3:08:87:74:17"); - - if (deferError) { - QVERIFY(page.error->deferred()); - QVERIFY(!page.error->answered()); - QVERIFY(!page.spyLoad.wasCalled()); - QCOMPARE(toPlainTextSync(&page), QString()); - - if (acceptCertificate) - page.error->ignoreCertificateError(); - else - page.error->rejectCertificate(); - - QVERIFY(page.error->answered()); - page.error.reset(); - } - - bool loadResult = page.spyLoad.waitForResult(); - QVERIFY(page.spyLoad.wasCalled()); - QCOMPARE(loadResult, acceptCertificate); - QCOMPARE(toPlainTextSync(&page), expectedContent); -} - -QTEST_MAIN(tst_CertificateError) -#include <tst_certificateerror.moc> diff --git a/tests/auto/widgets/defaultsurfaceformat/CMakeLists.txt b/tests/auto/widgets/defaultsurfaceformat/CMakeLists.txt new file mode 100644 index 000000000..d95c1355b --- /dev/null +++ b/tests/auto/widgets/defaultsurfaceformat/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../util/util.cmake) + +qt_internal_add_test(tst_defaultsurfaceformat + SOURCES + tst_defaultsurfaceformat.cpp + LIBRARIES + Qt::WebEngineWidgets + Test::Util +) + +set(tst_defaultsurfaceformat_resource_files + "resources/index.html" +) + +qt_internal_add_resource(tst_defaultsurfaceformat "tst_defaultsurfaceformat" + PREFIX + "/" + FILES + ${tst_defaultsurfaceformat_resource_files} +) diff --git a/tests/auto/widgets/defaultsurfaceformat/defaultsurfaceformat.pro b/tests/auto/widgets/defaultsurfaceformat/defaultsurfaceformat.pro deleted file mode 100644 index e99c7f493..000000000 --- a/tests/auto/widgets/defaultsurfaceformat/defaultsurfaceformat.pro +++ /dev/null @@ -1 +0,0 @@ -include(../tests.pri) diff --git a/tests/auto/widgets/defaultsurfaceformat/tst_defaultsurfaceformat.cpp b/tests/auto/widgets/defaultsurfaceformat/tst_defaultsurfaceformat.cpp index 697ed3d08..c53f6f5b3 100644 --- a/tests/auto/widgets/defaultsurfaceformat/tst_defaultsurfaceformat.cpp +++ b/tests/auto/widgets/defaultsurfaceformat/tst_defaultsurfaceformat.cpp @@ -1,33 +1,8 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <qtest.h> -#include "../util.h" +#include <util.h> #include <QSurfaceFormat> #include <QTimer> diff --git a/tests/auto/widgets/defaultsurfaceformat/tst_defaultsurfaceformat.qrc b/tests/auto/widgets/defaultsurfaceformat/tst_defaultsurfaceformat.qrc deleted file mode 100644 index 3d5f1b3b2..000000000 --- a/tests/auto/widgets/defaultsurfaceformat/tst_defaultsurfaceformat.qrc +++ /dev/null @@ -1,5 +0,0 @@ -<!DOCTYPE RCC><RCC version="1.0"> -<qresource> - <file>resources/index.html</file> -</qresource> -</RCC> diff --git a/tests/auto/widgets/devtools/devtools.pro b/tests/auto/widgets/devtools/devtools.pro deleted file mode 100644 index e99c7f493..000000000 --- a/tests/auto/widgets/devtools/devtools.pro +++ /dev/null @@ -1 +0,0 @@ -include(../tests.pri) diff --git a/tests/auto/widgets/devtools/tst_devtools.cpp b/tests/auto/widgets/devtools/tst_devtools.cpp deleted file mode 100644 index 8f3b90a14..000000000 --- a/tests/auto/widgets/devtools/tst_devtools.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QtTest/QtTest> - -#include <qwebenginepage.h> - -class tst_DevTools : public QObject { - Q_OBJECT - -private Q_SLOTS: - void attachAndDestroyPageFirst(); - void attachAndDestroyInspectorFirst(); -}; - -void tst_DevTools::attachAndDestroyPageFirst() -{ - // External inspector + manual destruction of page first - QWebEnginePage* page = new QWebEnginePage(); - QWebEnginePage* inspector = new QWebEnginePage(); - - QSignalSpy spy(page, &QWebEnginePage::loadFinished); - page->load(QUrl("data:text/plain,foobarbaz")); - QTRY_COMPARE(spy.count(), 1); - - inspector->setInspectedPage(page); - page->triggerAction(QWebEnginePage::InspectElement); - - // This is deliberately racy: - QTest::qWait(10); - - delete page; - delete inspector; -} - -void tst_DevTools::attachAndDestroyInspectorFirst() -{ - // External inspector + manual destruction of inspector first - QWebEnginePage* page = new QWebEnginePage(); - QWebEnginePage* inspector = new QWebEnginePage(); - inspector->setInspectedPage(page); - - QSignalSpy spy(page, &QWebEnginePage::loadFinished); - page->setHtml(QStringLiteral("<body><h1>FOO BAR!</h1></body>")); - QTRY_COMPARE(spy.count(), 1); - - page->triggerAction(QWebEnginePage::InspectElement); - - delete inspector; - - page->triggerAction(QWebEnginePage::InspectElement); - - // This is deliberately racy: - QTest::qWait(10); - - delete page; -} - - -QTEST_MAIN(tst_DevTools) - -#include "tst_devtools.moc" diff --git a/tests/auto/widgets/favicon/CMakeLists.txt b/tests/auto/widgets/favicon/CMakeLists.txt new file mode 100644 index 000000000..0deae6a37 --- /dev/null +++ b/tests/auto/widgets/favicon/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../util/util.cmake) + +qt_internal_add_test(tst_favicon + SOURCES + tst_favicon.cpp + LIBRARIES + Qt::WebEngineWidgets + Test::Util +) + +set(tst_favicon_resource_files + "resources/favicon-misc.html" + "resources/favicon-multi.html" + "resources/favicon-shortcut.html" + "resources/favicon-single.html" + "resources/favicon-touch.html" + "resources/favicon-unavailable.html" + "resources/icons/qt144.png" + "resources/icons/qt32.ico" + "resources/icons/qtmulti.ico" + "resources/test1.html" +) + +qt_internal_add_resource(tst_favicon "tst_favicon" + PREFIX + "/" + FILES + ${tst_favicon_resource_files} +) diff --git a/tests/auto/widgets/faviconmanager/resources/favicon-misc.html b/tests/auto/widgets/favicon/resources/favicon-misc.html index 9e788bdf4..ea587886f 100644 --- a/tests/auto/widgets/faviconmanager/resources/favicon-misc.html +++ b/tests/auto/widgets/favicon/resources/favicon-misc.html @@ -1,8 +1,8 @@ <html> <head> <title>Favicon Test</title> - <link rel="shortcut icon" href="icons/qt32.ico" /> - <link rel="apple-touch-icon" href="icons/qt144.png" /> + <link rel="shortcut icon" href="icons/qt32.ico" sizes="32x32" /> + <link rel="apple-touch-icon" href="icons/qt144.png" sizes="144x144" /> <link rel="shortcut icon" href="icons/unavailable.ico" /> </head> <body> diff --git a/tests/auto/widgets/faviconmanager/resources/favicon-multi.html b/tests/auto/widgets/favicon/resources/favicon-multi.html index cc5f3fd66..56eeca8c4 100644 --- a/tests/auto/widgets/faviconmanager/resources/favicon-multi.html +++ b/tests/auto/widgets/favicon/resources/favicon-multi.html @@ -1,7 +1,7 @@ <html> <head> <title>Multi-sized Favicon Test</title> - <link rel="shortcut icon" sizes="16x16 32x23 64x64" href="icons/qtmulti.ico" /> + <link rel="shortcut icon" sizes="16x16 32x32 64x64" href="icons/qtmulti.ico" /> </head> <body> <h1>Multi-sized Favicon Test</h1> diff --git a/tests/auto/widgets/faviconmanager/resources/favicon-shortcut.html b/tests/auto/widgets/favicon/resources/favicon-shortcut.html index 786cdb816..786cdb816 100644 --- a/tests/auto/widgets/faviconmanager/resources/favicon-shortcut.html +++ b/tests/auto/widgets/favicon/resources/favicon-shortcut.html diff --git a/tests/auto/widgets/faviconmanager/resources/favicon-single.html b/tests/auto/widgets/favicon/resources/favicon-single.html index eb4675c75..eb4675c75 100644 --- a/tests/auto/widgets/faviconmanager/resources/favicon-single.html +++ b/tests/auto/widgets/favicon/resources/favicon-single.html diff --git a/tests/auto/widgets/faviconmanager/resources/favicon-touch.html b/tests/auto/widgets/favicon/resources/favicon-touch.html index 271783434..271783434 100644 --- a/tests/auto/widgets/faviconmanager/resources/favicon-touch.html +++ b/tests/auto/widgets/favicon/resources/favicon-touch.html diff --git a/tests/auto/widgets/faviconmanager/resources/favicon-unavailable.html b/tests/auto/widgets/favicon/resources/favicon-unavailable.html index c45664294..c45664294 100644 --- a/tests/auto/widgets/faviconmanager/resources/favicon-unavailable.html +++ b/tests/auto/widgets/favicon/resources/favicon-unavailable.html diff --git a/tests/auto/widgets/faviconmanager/resources/icons/qt144.png b/tests/auto/widgets/favicon/resources/icons/qt144.png Binary files differindex 050b1e066..050b1e066 100644 --- a/tests/auto/widgets/faviconmanager/resources/icons/qt144.png +++ b/tests/auto/widgets/favicon/resources/icons/qt144.png diff --git a/tests/auto/widgets/faviconmanager/resources/icons/qt32.ico b/tests/auto/widgets/favicon/resources/icons/qt32.ico Binary files differindex 2f6fcb5bc..2f6fcb5bc 100644 --- a/tests/auto/widgets/faviconmanager/resources/icons/qt32.ico +++ b/tests/auto/widgets/favicon/resources/icons/qt32.ico diff --git a/tests/auto/widgets/faviconmanager/resources/icons/qtmulti.ico b/tests/auto/widgets/favicon/resources/icons/qtmulti.ico Binary files differindex 81e5a22e8..81e5a22e8 100644 --- a/tests/auto/widgets/faviconmanager/resources/icons/qtmulti.ico +++ b/tests/auto/widgets/favicon/resources/icons/qtmulti.ico diff --git a/tests/auto/widgets/faviconmanager/resources/test1.html b/tests/auto/widgets/favicon/resources/test1.html index b323f966e..b323f966e 100644 --- a/tests/auto/widgets/faviconmanager/resources/test1.html +++ b/tests/auto/widgets/favicon/resources/test1.html diff --git a/tests/auto/widgets/favicon/tst_favicon.cpp b/tests/auto/widgets/favicon/tst_favicon.cpp new file mode 100644 index 000000000..c70aa1182 --- /dev/null +++ b/tests/auto/widgets/favicon/tst_favicon.cpp @@ -0,0 +1,869 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QtTest/QtTest> +#include <util.h> + +#include <QWebEngineHistory> +#include <QWebEnginePage> +#include <QWebEngineProfile> +#include <QWebEngineSettings> +#include <QWebEngineView> + +class tst_Favicon : public QObject +{ + Q_OBJECT + +public Q_SLOTS: + void init(); + void initTestCase(); + void cleanupTestCase(); + void cleanup(); + +private Q_SLOTS: + void faviconLoad(); + void faviconLoadFromResources(); + void faviconLoadEncodedUrl(); + void faviconLoadAfterHistoryNavigation(); + void faviconLoadPushState(); + void noFavicon(); + void aboutBlank(); + void unavailableFavicon(); + void errorPageEnabled(); + void errorPageDisabled(); + void touchIcon(); + void multiIcon(); + void downloadIconsDisabled_data(); + void downloadIconsDisabled(); + void downloadTouchIconsEnabled_data(); + void downloadTouchIconsEnabled(); + void dynamicFavicon(); + void touchIconWithSameURL(); + + void iconDatabaseOTR(); + void requestIconForIconURL_data(); + void requestIconForIconURL(); + void requestIconForPageURL_data(); + void requestIconForPageURL(); + void desiredSize(); + +private: + QWebEngineView *m_view; + QWebEnginePage *m_page; + QWebEngineProfile *m_profile; +}; + +void tst_Favicon::init() +{ + m_profile = new QWebEngineProfile(this); + m_view = new QWebEngineView(); + m_page = new QWebEnginePage(m_profile, m_view); + m_view->setPage(m_page); +} + +void tst_Favicon::initTestCase() { } + +void tst_Favicon::cleanupTestCase() { } + +void tst_Favicon::cleanup() +{ + delete m_view; + delete m_profile; +} + +void tst_Favicon::faviconLoad() +{ + if (!QDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'") + .arg(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()) + .toLatin1() + .constData(), + SkipAll); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + QUrl url = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + QLatin1String("/resources/favicon-single.html")); + m_page->load(url); + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QTRY_COMPARE(iconChangedSpy.size(), 1); + + QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QCOMPARE(iconUrl, m_page->iconUrl()); + QCOMPARE(iconUrl, + QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + QLatin1String("/resources/icons/qt32.ico"))); + + const QIcon &icon = m_page->icon(); + QVERIFY(!icon.isNull()); + + QCOMPARE(icon.availableSizes().size(), 2); + QVERIFY(icon.availableSizes().contains(QSize(16, 16))); + QVERIFY(icon.availableSizes().contains(QSize(32, 32))); +} + +void tst_Favicon::faviconLoadFromResources() +{ + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + QUrl url("qrc:/resources/favicon-single.html"); + m_page->load(url); + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QTRY_COMPARE(iconChangedSpy.size(), 1); + + QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QCOMPARE(iconUrl, m_page->iconUrl()); + QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qt32.ico")); + + const QIcon &icon = m_page->icon(); + QVERIFY(!icon.isNull()); + + QCOMPARE(icon.availableSizes().size(), 2); + QVERIFY(icon.availableSizes().contains(QSize(16, 16))); + QVERIFY(icon.availableSizes().contains(QSize(32, 32))); +} + +void tst_Favicon::faviconLoadEncodedUrl() +{ + if (!QDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'") + .arg(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()) + .toLatin1() + .constData(), + SkipAll); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + QString urlString = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + QLatin1String("/resources/favicon-single.html")) + .toString(); + QUrl url(urlString + QLatin1String("?favicon=load should work with#whitespace!")); + m_page->load(url); + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QTRY_COMPARE(iconChangedSpy.size(), 1); + + QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QCOMPARE(m_page->iconUrl(), iconUrl); + QCOMPARE(iconUrl, + QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + QLatin1String("/resources/icons/qt32.ico"))); + + const QIcon &icon = m_page->icon(); + QVERIFY(!icon.isNull()); + + QCOMPARE(icon.availableSizes().size(), 2); + QVERIFY(icon.availableSizes().contains(QSize(16, 16))); + QVERIFY(icon.availableSizes().contains(QSize(32, 32))); +} + +void tst_Favicon::faviconLoadAfterHistoryNavigation() +{ + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + m_page->load(QUrl("qrc:/resources/favicon-single.html")); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QTRY_COMPARE(iconChangedSpy.size(), 1); + QCOMPARE(m_page->iconUrl(), QUrl("qrc:/resources/icons/qt32.ico")); + + m_page->load(QUrl("qrc:/resources/favicon-multi.html")); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 2, 30000); + QTRY_COMPARE(iconUrlChangedSpy.size(), 3); + QTRY_COMPARE(iconChangedSpy.size(), 3); + QCOMPARE(m_page->iconUrl(), QUrl("qrc:/resources/icons/qtmulti.ico")); + + m_page->triggerAction(QWebEnginePage::Back); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 3, 30000); + QTRY_COMPARE(iconUrlChangedSpy.size(), 5); + QTRY_COMPARE(iconChangedSpy.size(), 5); + QCOMPARE(m_page->iconUrl(), QUrl("qrc:/resources/icons/qt32.ico")); + + m_page->triggerAction(QWebEnginePage::Forward); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 4, 30000); + QTRY_COMPARE(iconUrlChangedSpy.size(), 7); + QTRY_COMPARE(iconChangedSpy.size(), 7); + QCOMPARE(m_page->iconUrl(), QUrl("qrc:/resources/icons/qtmulti.ico")); +} + +void tst_Favicon::faviconLoadPushState() +{ + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + QUrl url("qrc:/resources/favicon-single.html"); + m_page->load(url); + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QTRY_COMPARE(iconChangedSpy.size(), 1); + + QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QCOMPARE(iconUrl, m_page->iconUrl()); + QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qt32.ico")); + + const QIcon &icon = m_page->icon(); + QVERIFY(!icon.isNull()); + + iconUrlChangedSpy.clear(); + iconChangedSpy.clear(); + + // pushState() is a same document navigation and should not reset or + // update favicon. + QCOMPARE(m_page->history()->count(), 1); + evaluateJavaScriptSync(m_page, "history.pushState('', '')"); + QTRY_COMPARE(m_page->history()->count(), 2); + + // Favicon change is not expected. + QCOMPARE(iconUrlChangedSpy.size(), 0); + QCOMPARE(iconChangedSpy.size(), 0); + QCOMPARE(m_page->iconUrl(), QUrl("qrc:/resources/icons/qt32.ico")); +} + +void tst_Favicon::noFavicon() +{ + if (!QDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'") + .arg(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()) + .toLatin1() + .constData(), + SkipAll); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + QUrl url = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + QLatin1String("/resources/test1.html")); + m_page->load(url); + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QCOMPARE(iconUrlChangedSpy.size(), 0); + QCOMPARE(iconChangedSpy.size(), 0); + + QVERIFY(m_page->iconUrl().isEmpty()); + QVERIFY(m_page->icon().isNull()); +} + +void tst_Favicon::aboutBlank() +{ + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + QUrl url("about:blank"); + m_page->load(url); + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QCOMPARE(iconUrlChangedSpy.size(), 0); + QCOMPARE(iconChangedSpy.size(), 0); + + QVERIFY(m_page->iconUrl().isEmpty()); + QVERIFY(m_page->icon().isNull()); +} + +void tst_Favicon::unavailableFavicon() +{ + if (!QDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'") + .arg(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()) + .toLatin1() + .constData(), + SkipAll); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + QUrl url = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + QLatin1String("/resources/favicon-unavailable.html")); + m_page->load(url); + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QCOMPARE(iconUrlChangedSpy.size(), 0); + QCOMPARE(iconChangedSpy.size(), 0); + + QVERIFY(m_page->iconUrl().isEmpty()); + QVERIFY(m_page->icon().isNull()); +} + +void tst_Favicon::errorPageEnabled() +{ + m_page->settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, true); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + QUrl url("http://url.invalid"); + m_page->load(url); + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QCOMPARE(iconUrlChangedSpy.size(), 0); + QCOMPARE(iconChangedSpy.size(), 0); + + QVERIFY(m_page->iconUrl().isEmpty()); + QVERIFY(m_page->icon().isNull()); +} + +void tst_Favicon::errorPageDisabled() +{ + m_page->settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + QUrl url("http://url.invalid"); + m_page->load(url); + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QCOMPARE(iconUrlChangedSpy.size(), 0); + QCOMPARE(iconChangedSpy.size(), 0); + + QVERIFY(m_page->iconUrl().isEmpty()); + QVERIFY(m_page->icon().isNull()); +} + +void tst_Favicon::touchIcon() +{ + if (!QDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'") + .arg(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()) + .toLatin1() + .constData(), + SkipAll); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + QUrl url = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + QLatin1String("/resources/favicon-touch.html")); + m_page->load(url); + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QCOMPARE(iconUrlChangedSpy.size(), 0); + QCOMPARE(iconChangedSpy.size(), 0); + + QVERIFY(m_page->iconUrl().isEmpty()); + QVERIFY(m_page->icon().isNull()); +} + +void tst_Favicon::multiIcon() +{ + if (!QDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'") + .arg(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()) + .toLatin1() + .constData(), + SkipAll); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + QUrl url = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + QLatin1String("/resources/favicon-multi.html")); + QUrl iconUrl; + QIcon icon; + + // If touch icons are disabled, the favicon is provided in two sizes (16x16 and 32x32) according + // to the supported scale factors (100P, 200P). + m_page->settings()->setAttribute(QWebEngineSettings::TouchIconsEnabled, false); + m_page->load(url); + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QTRY_COMPARE(iconChangedSpy.size(), 1); + + iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QCOMPARE(m_page->iconUrl(), iconUrl); + QCOMPARE(iconUrl, + QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + QLatin1String("/resources/icons/qtmulti.ico"))); + + icon = m_page->icon(); + QVERIFY(!icon.isNull()); + QCOMPARE(icon.availableSizes().size(), 2); + QVERIFY(icon.availableSizes().contains(QSize(16, 16))); + QVERIFY(icon.availableSizes().contains(QSize(32, 32))); + + // Reset + loadFinishedSpy.clear(); + m_page->load(QUrl("about:blank")); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + iconUrlChangedSpy.clear(); + iconChangedSpy.clear(); + loadFinishedSpy.clear(); + icon = QIcon(); + + // If touch icons are enabled, the largest icon is provided. + m_page->settings()->setAttribute(QWebEngineSettings::TouchIconsEnabled, true); + m_page->load(url); + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QTRY_COMPARE(iconChangedSpy.size(), 1); + + iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QCOMPARE(m_page->iconUrl(), iconUrl); + QCOMPARE(iconUrl, + QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + + QLatin1String("/resources/icons/qtmulti.ico"))); + + icon = m_page->icon(); + QVERIFY(!icon.isNull()); + QCOMPARE(icon.availableSizes().size(), 1); + QVERIFY(icon.availableSizes().contains(QSize(64, 64))); +} + +void tst_Favicon::downloadIconsDisabled_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::newRow("misc") << QUrl("qrc:/resources/favicon-misc.html"); + QTest::newRow("shortcut") << QUrl("qrc:/resources/favicon-shortcut.html"); + QTest::newRow("single") << QUrl("qrc:/resources/favicon-single.html"); + QTest::newRow("touch") << QUrl("qrc:/resources/favicon-touch.html"); + QTest::newRow("unavailable") << QUrl("qrc:/resources/favicon-unavailable.html"); +} + +void tst_Favicon::downloadIconsDisabled() +{ + QFETCH(QUrl, url); + + m_page->settings()->setAttribute(QWebEngineSettings::AutoLoadIconsForPage, false); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + m_page->load(url); + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QCOMPARE(iconUrlChangedSpy.size(), 0); + QCOMPARE(iconChangedSpy.size(), 0); + + QVERIFY(m_page->iconUrl().isEmpty()); + QVERIFY(m_page->icon().isNull()); +} + +void tst_Favicon::downloadTouchIconsEnabled_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QUrl>("expectedIconUrl"); + QTest::addColumn<QSize>("expectedIconSize"); + QTest::newRow("misc") << QUrl("qrc:/resources/favicon-misc.html") + << QUrl("qrc:/resources/icons/qt144.png") << QSize(144, 144); + QTest::newRow("shortcut") << QUrl("qrc:/resources/favicon-shortcut.html") + << QUrl("qrc:/resources/icons/qt144.png") << QSize(144, 144); + QTest::newRow("single") << QUrl("qrc:/resources/favicon-single.html") + << QUrl("qrc:/resources/icons/qt32.ico") << QSize(32, 32); + QTest::newRow("touch") << QUrl("qrc:/resources/favicon-touch.html") + << QUrl("qrc:/resources/icons/qt144.png") << QSize(144, 144); +} + +void tst_Favicon::downloadTouchIconsEnabled() +{ + QFETCH(QUrl, url); + QFETCH(QUrl, expectedIconUrl); + QFETCH(QSize, expectedIconSize); + + m_page->settings()->setAttribute(QWebEngineSettings::TouchIconsEnabled, true); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + m_page->load(url); + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QTRY_COMPARE(iconChangedSpy.size(), 1); + + const QUrl &iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QCOMPARE(m_page->iconUrl(), iconUrl); + QCOMPARE(iconUrl, expectedIconUrl); + + const QIcon &icon = m_page->icon(); + QVERIFY(!icon.isNull()); + + QCOMPARE(icon.availableSizes().size(), 1); + QCOMPARE(icon.availableSizes().first(), expectedIconSize); +} + +void tst_Favicon::dynamicFavicon() +{ + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + QMap<Qt::GlobalColor, QString> colors; + colors.insert(Qt::red, + QString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==")); + colors.insert(Qt::green, + QString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M/wHwAEBgIApD5fRAAAAABJRU5ErkJggg==")); + colors.insert(Qt::blue, + QString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPj/HwADBwIAMCbHYQAAAABJRU5ErkJggg==")); + + m_page->setHtml("<html>" + "<link rel='icon' type='image/png' " + "href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII='/>" + "</html>"); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QTRY_COMPARE(iconChangedSpy.size(), 1); + + QCOMPARE(m_page->icon().pixmap(1, 1).toImage().pixelColor(0, 0), QColor(Qt::black)); + + for (Qt::GlobalColor color : colors.keys()) { + iconChangedSpy.clear(); + evaluateJavaScriptSync( + m_page, + "document.getElementsByTagName('link')[0].href = 'data:image/png;base64," + colors[color] + "';"); + QTRY_COMPARE(iconChangedSpy.size(), 1); + QTRY_COMPARE(m_page->iconUrl().toString(), + QString("data:image/png;base64," + colors[color])); + QCOMPARE(m_page->icon().pixmap(1, 1).toImage().pixelColor(0, 0), QColor(color)); + } +} + +void tst_Favicon::touchIconWithSameURL() +{ + m_page->settings()->setAttribute(QWebEngineSettings::TouchIconsEnabled, false); + + const QString icon("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="); + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); + + m_page->setHtml("<html>" + "<link rel='icon' type='image/png' href='" + icon + "'/>" + "<link rel='apple-touch-icon' type='image/png' href='" + icon + "'/>" + "</html>"); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + + // The default favicon has to be loaded even if its URL is also set as a touch icon while touch + // icons are disabled. + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QCOMPARE(m_page->iconUrl().toString(), icon); + QTRY_COMPARE(iconChangedSpy.size(), 1); + + loadFinishedSpy.clear(); + iconUrlChangedSpy.clear(); + iconChangedSpy.clear(); + + m_page->setHtml("<html>" + "<link rel='apple-touch-icon' type='image/png' href='" + icon + "'/>" + "</html>"); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + + // This page only has a touch icon. With disabled touch icons we don't expect any icon to be + // shown even if the same icon was loaded previously. + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QVERIFY(m_page->iconUrl().toString().isEmpty()); + QTRY_COMPARE(iconChangedSpy.size(), 1); +} + +void tst_Favicon::iconDatabaseOTR() +{ + QWebEngineProfile profile; + QWebEngineView view; + QWebEnginePage *page = new QWebEnginePage(&profile, &view); + view.setPage(page); + + QSignalSpy loadFinishedSpy(page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(page, SIGNAL(iconChanged(QIcon))); + + page->load(QUrl("qrc:/resources/favicon-misc.html")); + + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QTRY_COMPARE(iconChangedSpy.size(), 1); + + { + bool iconRequestDone = false; + profile.requestIconForIconURL(page->iconUrl(), 0, + [page, &iconRequestDone](const QIcon &icon, const QUrl &iconUrl) { + QVERIFY(icon.isNull()); + QCOMPARE(iconUrl, page->iconUrl()); + iconRequestDone = true; + }); + QTRY_VERIFY(iconRequestDone); + } + + { + bool iconRequestDone = false; + profile.requestIconForPageURL(page->url(), 0, + [page, &iconRequestDone](const QIcon &icon, const QUrl &iconUrl, const QUrl &pageUrl) { + QVERIFY(icon.isNull()); + QVERIFY(iconUrl.isEmpty()); + QCOMPARE(pageUrl, page->url()); + iconRequestDone = true; + }); + QTRY_VERIFY(iconRequestDone); + } +} + +void tst_Favicon::requestIconForIconURL_data() +{ + QTest::addColumn<bool>("touchIconsEnabled"); + QTest::newRow("touch icons enabled") << true; + QTest::newRow("touch icons disabled") << false; +} + +void tst_Favicon::requestIconForIconURL() +{ + QFETCH(bool, touchIconsEnabled); + + QTemporaryDir tmpDir; + QWebEngineProfile profile("iconDatabase-iconurl"); + profile.setPersistentStoragePath(tmpDir.path()); + profile.settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, true); + profile.settings()->setAttribute(QWebEngineSettings::TouchIconsEnabled, touchIconsEnabled); + + QWebEngineView view; + QWebEnginePage *page = new QWebEnginePage(&profile, &view); + view.setPage(page); + + QSignalSpy loadFinishedSpy(page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(page, SIGNAL(iconChanged(QIcon))); + + page->load(QUrl("qrc:/resources/favicon-misc.html")); + + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QTRY_COMPARE(iconChangedSpy.size(), 1); + + page->load(QUrl("about:blank")); + + QTRY_COMPARE(loadFinishedSpy.size(), 2); + QTRY_COMPARE(iconUrlChangedSpy.size(), 2); + QTRY_COMPARE(iconChangedSpy.size(), 2); + QVERIFY(page->icon().isNull()); + QVERIFY(page->iconUrl().isEmpty()); + + { + bool iconRequestDone = false; + profile.requestIconForIconURL(QUrl("qrc:/resources/icons/qt144.png"), 0, + [touchIconsEnabled, &iconRequestDone](const QIcon &icon, const QUrl &iconUrl) { + if (touchIconsEnabled) { + QVERIFY(!icon.isNull()); + QCOMPARE(icon.pixmap(QSize(32, 32), 1.0).toImage().pixel(16, 16), 0xfff2f9ec); + } else { + QVERIFY(icon.isNull()); + } + + QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qt144.png")); + iconRequestDone = true; + }); + QTRY_VERIFY(iconRequestDone); + } + + { + bool iconRequestDone = false; + profile.requestIconForIconURL(QUrl("qrc:/resources/icons/qt32.ico"), 0, + [&iconRequestDone](const QIcon &icon, const QUrl &iconUrl) { + QVERIFY(!icon.isNull()); + QCOMPARE(icon.pixmap(QSize(32, 32), 1.0).toImage().pixel(16, 16), 0xffeef7e6); + QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qt32.ico")); + iconRequestDone = true; + }); + QTRY_VERIFY(iconRequestDone); + } +} + +void tst_Favicon::requestIconForPageURL_data() +{ + QTest::addColumn<bool>("touchIconsEnabled"); + QTest::newRow("touch icons enabled") << true; + QTest::newRow("touch icons disabled") << false; +} + +void tst_Favicon::requestIconForPageURL() +{ + QFETCH(bool, touchIconsEnabled); + + QTemporaryDir tmpDir; + QWebEngineProfile profile("iconDatabase-pageurl"); + profile.setPersistentStoragePath(tmpDir.path()); + profile.settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, true); + profile.settings()->setAttribute(QWebEngineSettings::TouchIconsEnabled, touchIconsEnabled); + + + QWebEngineView view; + QWebEnginePage *page = new QWebEnginePage(&profile, &view); + view.setPage(page); + + QSignalSpy loadFinishedSpy(page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(page, SIGNAL(iconChanged(QIcon))); + + page->load(QUrl("qrc:/resources/favicon-misc.html")); + + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QTRY_COMPARE(iconChangedSpy.size(), 1); + + page->load(QUrl("about:blank")); + + QTRY_COMPARE(loadFinishedSpy.size(), 2); + QTRY_COMPARE(iconUrlChangedSpy.size(), 2); + QTRY_COMPARE(iconChangedSpy.size(), 2); + QVERIFY(page->icon().isNull()); + QVERIFY(page->iconUrl().isEmpty()); + + { + bool iconRequestDone = false; + profile.requestIconForPageURL(QUrl("qrc:/resources/favicon-misc.html"), 0, + [touchIconsEnabled, &iconRequestDone](const QIcon &icon, const QUrl &iconUrl, const QUrl &pageUrl) { + QVERIFY(!icon.isNull()); + if (touchIconsEnabled) { + QCOMPARE(icon.pixmap(QSize(32, 32), 1.0).toImage().pixel(16, 16), 0xfff2f9ec); + QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qt144.png")); + } else { + QCOMPARE(icon.pixmap(QSize(32, 32), 1.0).toImage().pixel(16, 16), 0xffeef7e6); + QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qt32.ico")); + } + + QCOMPARE(pageUrl, QUrl("qrc:/resources/favicon-misc.html")); + iconRequestDone = true; + }); + QTRY_VERIFY(iconRequestDone); + } +} + +void tst_Favicon::desiredSize() +{ + QTemporaryDir tmpDir; + QWebEngineProfile profile("iconDatabase-desiredsize"); + profile.setPersistentStoragePath(tmpDir.path()); + profile.settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, true); + + QWebEngineView view; + QWebEnginePage *page = new QWebEnginePage(&profile, &view); + view.setPage(page); + + // Disable touch icons: icon with size 16x16 will be loaded. + { + profile.settings()->setAttribute(QWebEngineSettings::TouchIconsEnabled, false); + + QSignalSpy loadFinishedSpy(page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(page, SIGNAL(iconChanged(QIcon))); + + page->load(QUrl("qrc:/resources/favicon-multi.html")); + + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QTRY_COMPARE(iconChangedSpy.size(), 1); + + page->load(QUrl("about:blank")); + + QTRY_COMPARE(loadFinishedSpy.size(), 2); + QTRY_COMPARE(iconUrlChangedSpy.size(), 2); + QTRY_COMPARE(iconChangedSpy.size(), 2); + QVERIFY(page->icon().isNull()); + QVERIFY(page->iconUrl().isEmpty()); + } + + int desiredSizeInPixel = 16; + QRgb expectedPixel = 0xfffdfefc; + + // Request icon with size 16x16 (desiredSizeInPixel). + { + bool iconRequestDone = false; + profile.requestIconForPageURL(QUrl("qrc:/resources/favicon-multi.html"), desiredSizeInPixel, + [desiredSizeInPixel, expectedPixel, &iconRequestDone](const QIcon &icon, const QUrl &iconUrl, const QUrl &pageUrl) { + QVERIFY(!icon.isNull()); + QRgb pixel = icon.pixmap(QSize(desiredSizeInPixel, desiredSizeInPixel), 1.0) + .toImage() + .pixel(8, 8); + QCOMPARE(pixel, expectedPixel); + QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qtmulti.ico")); + QCOMPARE(pageUrl, QUrl("qrc:/resources/favicon-multi.html")); + iconRequestDone = true; + }); + QTRY_VERIFY(iconRequestDone); + } + + // Enable touch icons: icon with the largest size (64x64) will be loaded. + { + profile.settings()->setAttribute(QWebEngineSettings::TouchIconsEnabled, true); + + QSignalSpy loadFinishedSpy(page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(page, SIGNAL(iconUrlChanged(QUrl))); + QSignalSpy iconChangedSpy(page, SIGNAL(iconChanged(QIcon))); + + page->load(QUrl("qrc:/resources/favicon-multi.html")); + + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QTRY_COMPARE(iconUrlChangedSpy.size(), 1); + QTRY_COMPARE(iconChangedSpy.size(), 1); + + page->load(QUrl("about:blank")); + + QTRY_COMPARE(loadFinishedSpy.size(), 2); + QTRY_COMPARE(iconUrlChangedSpy.size(), 2); + QTRY_COMPARE(iconChangedSpy.size(), 2); + QVERIFY(page->icon().isNull()); + QVERIFY(page->iconUrl().isEmpty()); + } + + // Request icon with size 16x16. + // The icon is stored with two sizes in the database. This request should result same pixel + // as the first one. + { + bool iconRequestDone = false; + profile.requestIconForPageURL(QUrl("qrc:/resources/favicon-multi.html"), desiredSizeInPixel, + [desiredSizeInPixel, expectedPixel, &iconRequestDone](const QIcon &icon, const QUrl &iconUrl, const QUrl &pageUrl) { + QVERIFY(!icon.isNull()); + QRgb pixel = icon.pixmap(QSize(desiredSizeInPixel, desiredSizeInPixel), 1.0) + .toImage() + .pixel(8, 8); + QCOMPARE(pixel, expectedPixel); + QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qtmulti.ico")); + QCOMPARE(pageUrl, QUrl("qrc:/resources/favicon-multi.html")); + iconRequestDone = true; + }); + QTRY_VERIFY(iconRequestDone); + } + + // Request icon with size 64x64. + // This requests the another size from the database. The pixel should differ. + { + bool iconRequestDone = false; + profile.requestIconForPageURL(QUrl("qrc:/resources/favicon-multi.html"), 64, + [desiredSizeInPixel, expectedPixel, &iconRequestDone](const QIcon &icon, const QUrl &iconUrl, const QUrl &pageUrl) { + QVERIFY(!icon.isNull()); + QRgb pixel = icon.pixmap(QSize(desiredSizeInPixel, desiredSizeInPixel), 1.0) + .toImage() + .pixel(8, 8); + QVERIFY(pixel != expectedPixel); + QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qtmulti.ico")); + QCOMPARE(pageUrl, QUrl("qrc:/resources/favicon-multi.html")); + iconRequestDone = true; + }); + QTRY_VERIFY(iconRequestDone); + } +} + +QTEST_MAIN(tst_Favicon) + +#include "tst_favicon.moc" diff --git a/tests/auto/widgets/faviconmanager/faviconmanager.pro b/tests/auto/widgets/faviconmanager/faviconmanager.pro deleted file mode 100644 index e99c7f493..000000000 --- a/tests/auto/widgets/faviconmanager/faviconmanager.pro +++ /dev/null @@ -1 +0,0 @@ -include(../tests.pri) diff --git a/tests/auto/widgets/faviconmanager/tst_faviconmanager.cpp b/tests/auto/widgets/faviconmanager/tst_faviconmanager.cpp deleted file mode 100644 index 540c8d505..000000000 --- a/tests/auto/widgets/faviconmanager/tst_faviconmanager.cpp +++ /dev/null @@ -1,551 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QtTest/QtTest> -#include "../util.h" - -#include <qwebenginepage.h> -#include <qwebengineprofile.h> -#include <qwebenginesettings.h> -#include <qwebengineview.h> - - -class tst_FaviconManager : public QObject { - Q_OBJECT - -public Q_SLOTS: - void init(); - void initTestCase(); - void cleanupTestCase(); - void cleanup(); - -private Q_SLOTS: - void faviconLoad(); - void faviconLoadFromResources(); - void faviconLoadEncodedUrl(); - void noFavicon(); - void aboutBlank(); - void unavailableFavicon(); - void errorPageEnabled(); - void errorPageDisabled(); - void bestFavicon(); - void touchIcon(); - void multiIcon(); - void candidateIcon(); - void downloadIconsDisabled_data(); - void downloadIconsDisabled(); - void downloadTouchIconsEnabled_data(); - void downloadTouchIconsEnabled(); - void dynamicFavicon(); - void touchIconWithSameURL(); - -private: - QWebEngineView *m_view; - QWebEnginePage *m_page; - QWebEngineProfile *m_profile; -}; - - -void tst_FaviconManager::init() -{ - m_profile = new QWebEngineProfile(this); - m_view = new QWebEngineView(); - m_page = new QWebEnginePage(m_profile, m_view); - m_view->setPage(m_page); -} - - -void tst_FaviconManager::initTestCase() -{ -} - -void tst_FaviconManager::cleanupTestCase() -{ -} - - -void tst_FaviconManager::cleanup() -{ - delete m_view; - delete m_profile; -} - -void tst_FaviconManager::faviconLoad() -{ - if (!QDir(TESTS_SOURCE_DIR).exists()) - W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); - - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - QUrl url = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("faviconmanager/resources/favicon-single.html")); - m_page->load(url); - - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QTRY_COMPARE(iconUrlChangedSpy.count(), 1); - QTRY_COMPARE(iconChangedSpy.count(), 1); - - QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); - QCOMPARE(iconUrl, m_page->iconUrl()); - QCOMPARE(iconUrl, QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("faviconmanager/resources/icons/qt32.ico"))); - - const QIcon &icon = m_page->icon(); - QVERIFY(!icon.isNull()); - - QCOMPARE(icon.availableSizes().count(), 1); - QSize iconSize = icon.availableSizes().first(); - QCOMPARE(iconSize, QSize(32, 32)); -} - -void tst_FaviconManager::faviconLoadFromResources() -{ - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - QUrl url("qrc:/resources/favicon-single.html"); - m_page->load(url); - - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QTRY_COMPARE(iconUrlChangedSpy.count(), 1); - QTRY_COMPARE(iconChangedSpy.count(), 1); - - QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); - QCOMPARE(iconUrl, m_page->iconUrl()); - QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qt32.ico")); - - const QIcon &icon = m_page->icon(); - QVERIFY(!icon.isNull()); - - QCOMPARE(icon.availableSizes().count(), 1); - QSize iconSize = icon.availableSizes().first(); - QCOMPARE(iconSize, QSize(32, 32)); -} - -void tst_FaviconManager::faviconLoadEncodedUrl() -{ - if (!QDir(TESTS_SOURCE_DIR).exists()) - W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); - - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - QString urlString = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("faviconmanager/resources/favicon-single.html")).toString(); - QUrl url(urlString + QLatin1String("?favicon=load should work with#whitespace!")); - m_page->load(url); - - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QTRY_COMPARE(iconUrlChangedSpy.count(), 1); - QTRY_COMPARE(iconChangedSpy.count(), 1); - - QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); - QCOMPARE(m_page->iconUrl(), iconUrl); - QCOMPARE(iconUrl, QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("faviconmanager/resources/icons/qt32.ico"))); - - const QIcon &icon = m_page->icon(); - QVERIFY(!icon.isNull()); - - QCOMPARE(icon.availableSizes().count(), 1); - QSize iconSize = icon.availableSizes().first(); - QCOMPARE(iconSize, QSize(32, 32)); -} - -void tst_FaviconManager::noFavicon() -{ - if (!QDir(TESTS_SOURCE_DIR).exists()) - W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); - - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - QUrl url = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("faviconmanager/resources/test1.html")); - m_page->load(url); - - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QCOMPARE(iconUrlChangedSpy.count(), 0); - QCOMPARE(iconChangedSpy.count(), 0); - - QVERIFY(m_page->iconUrl().isEmpty()); - QVERIFY(m_page->icon().isNull()); -} - -void tst_FaviconManager::aboutBlank() -{ - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - QUrl url("about:blank"); - m_page->load(url); - - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QCOMPARE(iconUrlChangedSpy.count(), 0); - QCOMPARE(iconChangedSpy.count(), 0); - - QVERIFY(m_page->iconUrl().isEmpty()); - QVERIFY(m_page->icon().isNull()); -} - -void tst_FaviconManager::unavailableFavicon() -{ - if (!QDir(TESTS_SOURCE_DIR).exists()) - W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); - - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - QUrl url = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("faviconmanager/resources/favicon-unavailable.html")); - m_page->load(url); - - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QCOMPARE(iconUrlChangedSpy.count(), 0); - QCOMPARE(iconChangedSpy.count(), 0); - - QVERIFY(m_page->iconUrl().isEmpty()); - QVERIFY(m_page->icon().isNull()); -} - -void tst_FaviconManager::errorPageEnabled() -{ - m_page->settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, true); - - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - QUrl url("http://url.invalid"); - m_page->load(url); - - QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 20000); - QCOMPARE(iconUrlChangedSpy.count(), 0); - QCOMPARE(iconChangedSpy.count(), 0); - - QVERIFY(m_page->iconUrl().isEmpty()); - QVERIFY(m_page->icon().isNull()); -} - -void tst_FaviconManager::errorPageDisabled() -{ - m_page->settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); - - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - QUrl url("http://url.invalid"); - m_page->load(url); - - QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 12000); - QCOMPARE(iconUrlChangedSpy.count(), 0); - QCOMPARE(iconChangedSpy.count(), 0); - - QVERIFY(m_page->iconUrl().isEmpty()); - QVERIFY(m_page->icon().isNull()); -} - -void tst_FaviconManager::bestFavicon() -{ - if (!QDir(TESTS_SOURCE_DIR).exists()) - W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); - - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - QUrl url, iconUrl; - QIcon icon; - QSize iconSize; - - url = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("faviconmanager/resources/favicon-misc.html")); - m_page->load(url); - - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QTRY_COMPARE(iconUrlChangedSpy.count(), 1); - QTRY_COMPARE(iconChangedSpy.count(), 1); - - iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); - QCOMPARE(iconUrl, m_page->iconUrl()); - // Touch icon is ignored - QCOMPARE(iconUrl, QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("faviconmanager/resources/icons/qt32.ico"))); - - icon = m_page->icon(); - QVERIFY(!icon.isNull()); - - QCOMPARE(icon.availableSizes().count(), 1); - iconSize = icon.availableSizes().first(); - QCOMPARE(iconSize, QSize(32, 32)); - - loadFinishedSpy.clear(); - iconUrlChangedSpy.clear(); - iconChangedSpy.clear(); - - url = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("faviconmanager/resources/favicon-shortcut.html")); - m_page->load(url); - - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QTRY_VERIFY(iconUrlChangedSpy.count() >= 1); - QTRY_VERIFY(iconChangedSpy.count() >= 1); - - iconUrl = iconUrlChangedSpy.last().at(0).toString(); - - // If the icon URL is empty we have to wait for - // the second iconChanged signal that propagates the expected URL - if (iconUrl.isEmpty()) { - QTRY_COMPARE(iconUrlChangedSpy.count(), 2); - QTRY_COMPARE(iconChangedSpy.count(), 2); - iconUrl = iconUrlChangedSpy.last().at(0).toString(); - } - - QCOMPARE(iconUrl, m_page->iconUrl()); - QCOMPARE(iconUrl, QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("faviconmanager/resources/icons/qt144.png"))); - - icon = m_page->icon(); - QVERIFY(!icon.isNull()); - - QVERIFY(icon.availableSizes().count() >= 1); - QVERIFY(icon.availableSizes().contains(QSize(144, 144))); -} - -void tst_FaviconManager::touchIcon() -{ - if (!QDir(TESTS_SOURCE_DIR).exists()) - W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); - - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - QUrl url = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("faviconmanager/resources/favicon-touch.html")); - m_page->load(url); - - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QCOMPARE(iconUrlChangedSpy.count(), 0); - QCOMPARE(iconChangedSpy.count(), 0); - - QVERIFY(m_page->iconUrl().isEmpty()); - QVERIFY(m_page->icon().isNull()); -} - -void tst_FaviconManager::multiIcon() -{ - if (!QDir(TESTS_SOURCE_DIR).exists()) - W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); - - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - QUrl url = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("faviconmanager/resources/favicon-multi.html")); - m_page->load(url); - - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QTRY_COMPARE(iconUrlChangedSpy.count(), 1); - QTRY_COMPARE(iconChangedSpy.count(), 1); - - QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); - QCOMPARE(m_page->iconUrl(), iconUrl); - QCOMPARE(iconUrl, QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("faviconmanager/resources/icons/qtmulti.ico"))); - - const QIcon &icon = m_page->icon(); - QVERIFY(!icon.isNull()); - QCOMPARE(icon.availableSizes().count(), 3); - QVERIFY(icon.availableSizes().contains(QSize(16, 16))); - QVERIFY(icon.availableSizes().contains(QSize(32, 32))); - QVERIFY(icon.availableSizes().contains(QSize(64, 64))); -} - -void tst_FaviconManager::candidateIcon() -{ - if (!QDir(TESTS_SOURCE_DIR).exists()) - W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); - - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - QUrl url = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("faviconmanager/resources/favicon-shortcut.html")); - m_page->load(url); - - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QTRY_COMPARE(iconUrlChangedSpy.count(), 1); - QTRY_COMPARE(iconChangedSpy.count(), 1); - - QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); - QCOMPARE(m_page->iconUrl(), iconUrl); - QCOMPARE(iconUrl, QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("faviconmanager/resources/icons/qt144.png"))); - - const QIcon &icon = m_page->icon(); - QVERIFY(!icon.isNull()); - QCOMPARE(icon.availableSizes().count(), 2); - QVERIFY(icon.availableSizes().contains(QSize(32, 32))); - QVERIFY(icon.availableSizes().contains(QSize(144, 144))); -} - -void tst_FaviconManager::downloadIconsDisabled_data() -{ - QTest::addColumn<QUrl>("url"); - QTest::newRow("misc") << QUrl("qrc:/resources/favicon-misc.html"); - QTest::newRow("shortcut") << QUrl("qrc:/resources/favicon-shortcut.html"); - QTest::newRow("single") << QUrl("qrc:/resources/favicon-single.html"); - QTest::newRow("touch") << QUrl("qrc:/resources/favicon-touch.html"); - QTest::newRow("unavailable") << QUrl("qrc:/resources/favicon-unavailable.html"); -} - -void tst_FaviconManager::downloadIconsDisabled() -{ - QFETCH(QUrl, url); - - m_page->settings()->setAttribute(QWebEngineSettings::AutoLoadIconsForPage, false); - - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - m_page->load(url); - - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QCOMPARE(iconUrlChangedSpy.count(), 0); - QCOMPARE(iconChangedSpy.count(), 0); - - QVERIFY(m_page->iconUrl().isEmpty()); - QVERIFY(m_page->icon().isNull()); -} - -void tst_FaviconManager::downloadTouchIconsEnabled_data() -{ - QTest::addColumn<QUrl>("url"); - QTest::addColumn<QUrl>("expectedIconUrl"); - QTest::addColumn<QSize>("expectedIconSize"); - QTest::newRow("misc") << QUrl("qrc:/resources/favicon-misc.html") << QUrl("qrc:/resources/icons/qt144.png") << QSize(144, 144); - QTest::newRow("shortcut") << QUrl("qrc:/resources/favicon-shortcut.html") << QUrl("qrc:/resources/icons/qt144.png") << QSize(144, 144); - QTest::newRow("single") << QUrl("qrc:/resources/favicon-single.html") << QUrl("qrc:/resources/icons/qt32.ico") << QSize(32, 32); - QTest::newRow("touch") << QUrl("qrc:/resources/favicon-touch.html") << QUrl("qrc:/resources/icons/qt144.png") << QSize(144, 144); -} - -void tst_FaviconManager::downloadTouchIconsEnabled() -{ - QFETCH(QUrl, url); - QFETCH(QUrl, expectedIconUrl); - QFETCH(QSize, expectedIconSize); - - m_page->settings()->setAttribute(QWebEngineSettings::TouchIconsEnabled, true); - - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - m_page->load(url); - - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QTRY_COMPARE(iconUrlChangedSpy.count(), 1); - QTRY_COMPARE(iconChangedSpy.count(), 1); - - const QUrl &iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); - QCOMPARE(m_page->iconUrl(), iconUrl); - QCOMPARE(iconUrl, expectedIconUrl); - - const QIcon &icon = m_page->icon(); - QVERIFY(!icon.isNull()); - - QVERIFY(icon.availableSizes().count() >= 1); - QVERIFY(icon.availableSizes().contains(expectedIconSize)); -} - -void tst_FaviconManager::dynamicFavicon() -{ - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - QMap<Qt::GlobalColor, QString> colors; - colors.insert(Qt::red, QString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==")); - colors.insert(Qt::green, QString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M/wHwAEBgIApD5fRAAAAABJRU5ErkJggg==")); - colors.insert(Qt::blue, QString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPj/HwADBwIAMCbHYQAAAABJRU5ErkJggg==")); - - m_page->setHtml("<html>" - "<link rel='icon' type='image/png' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII='/>" - "</html>"); - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QTRY_COMPARE(iconUrlChangedSpy.count(), 1); - QTRY_COMPARE(iconChangedSpy.count(), 1); - - QCOMPARE(m_page->icon().pixmap(1, 1).toImage().pixelColor(0, 0), QColor(Qt::black)); - - for (Qt::GlobalColor color : colors.keys()) { - iconChangedSpy.clear(); - evaluateJavaScriptSync(m_page, "document.getElementsByTagName('link')[0].href = 'data:image/png;base64," + colors[color] + "';"); - QTRY_COMPARE(iconChangedSpy.count(), 1); - QTRY_COMPARE(m_page->iconUrl().toString(), QString("data:image/png;base64," + colors[color])); - QCOMPARE(m_page->icon().pixmap(1, 1).toImage().pixelColor(0, 0), QColor(color)); - } -} - -void tst_FaviconManager::touchIconWithSameURL() -{ - m_page->settings()->setAttribute(QWebEngineSettings::TouchIconsEnabled, false); - - const QString icon("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="); - QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); - QSignalSpy iconChangedSpy(m_page, SIGNAL(iconChanged(QIcon))); - - m_page->setHtml("<html>" - "<link rel='icon' type='image/png' href='" + icon + "'/>" - "<link rel='apple-touch-icon' type='image/png' href='" + icon + "'/>" - "</html>"); - QTRY_COMPARE(loadFinishedSpy.count(), 1); - - // The default favicon has to be loaded even if its URL is also set as a touch icon while touch icons are disabled. - QTRY_COMPARE(iconUrlChangedSpy.count(), 1); - QCOMPARE(m_page->iconUrl().toString(), icon); - QTRY_COMPARE(iconChangedSpy.count(), 1); - - loadFinishedSpy.clear(); - iconUrlChangedSpy.clear(); - iconChangedSpy.clear(); - - m_page->setHtml("<html>" - "<link rel='apple-touch-icon' type='image/png' href='" + icon + "'/>" - "</html>"); - QTRY_COMPARE(loadFinishedSpy.count(), 1); - - // This page only has a touch icon. With disabled touch icons we don't expect any icon to be shown even if the same icon - // was loaded previously. - QTRY_COMPARE(iconUrlChangedSpy.count(), 1); - QVERIFY(m_page->iconUrl().toString().isEmpty()); - QTRY_COMPARE(iconChangedSpy.count(), 1); - -} - -QTEST_MAIN(tst_FaviconManager) - -#include "tst_faviconmanager.moc" diff --git a/tests/auto/widgets/faviconmanager/tst_faviconmanager.qrc b/tests/auto/widgets/faviconmanager/tst_faviconmanager.qrc deleted file mode 100644 index a352f8a83..000000000 --- a/tests/auto/widgets/faviconmanager/tst_faviconmanager.qrc +++ /dev/null @@ -1,14 +0,0 @@ -<!DOCTYPE RCC><RCC version="1.0"> -<qresource> - <file>resources/favicon-misc.html</file> - <file>resources/favicon-multi.html</file> - <file>resources/favicon-shortcut.html</file> - <file>resources/favicon-single.html</file> - <file>resources/favicon-touch.html</file> - <file>resources/favicon-unavailable.html</file> - <file>resources/icons/qt144.png</file> - <file>resources/icons/qt32.ico</file> - <file>resources/icons/qtmulti.ico</file> - <file>resources/test1.html</file> -</qresource> -</RCC> diff --git a/tests/auto/widgets/loadsignals/BLACKLIST b/tests/auto/widgets/loadsignals/BLACKLIST deleted file mode 100644 index 570666a83..000000000 --- a/tests/auto/widgets/loadsignals/BLACKLIST +++ /dev/null @@ -1,14 +0,0 @@ -[secondLoadForError_WhenErrorPageEnabled:ErrorPageEnabled] -* - -# QTBUG-65223 -[loadStartedAndFinishedCount:WithAnchorClickedFromJS] -* - -# QTBUG-66869 (https://codereview.qt-project.org/#/c/222112/ is only a workaround) -[loadAfterInPageNavigation_qtbug66869] -* - -# QTBUG-66661 -[fileDownloadDoesNotTriggerLoadSignals_qtbug66661] -* diff --git a/tests/auto/widgets/loadsignals/CMakeLists.txt b/tests/auto/widgets/loadsignals/CMakeLists.txt new file mode 100644 index 000000000..bbd0387d9 --- /dev/null +++ b/tests/auto/widgets/loadsignals/CMakeLists.txt @@ -0,0 +1,63 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../httpserver/httpserver.cmake) +include(../../util/util.cmake) + +qt_internal_add_test(tst_loadsignals + SOURCES + tst_loadsignals.cpp + LIBRARIES + Qt::WebEngineWidgets + Test::HttpServer + Test::Util +) + +get_target_property(sharedData Test::HttpServer SHARED_DATA) + +set_source_files_properties("${sharedData}/loadprogress/downloadable.tar.gz" + PROPERTIES QT_RESOURCE_ALIAS "downloadable.tar.gz" +) +set_source_files_properties("${sharedData}/loadprogress/page1.html" + PROPERTIES QT_RESOURCE_ALIAS "page1.html" +) +set_source_files_properties("${sharedData}/loadprogress/page2.html" + PROPERTIES QT_RESOURCE_ALIAS "page2.html" +) +set_source_files_properties("${sharedData}/loadprogress/page3.html" + PROPERTIES QT_RESOURCE_ALIAS "page3.html" +) +set_source_files_properties("${sharedData}/loadprogress/page4.html" + PROPERTIES QT_RESOURCE_ALIAS "page4.html" +) +set_source_files_properties("${sharedData}/loadprogress/page5.html" + PROPERTIES QT_RESOURCE_ALIAS "page5.html" +) +set_source_files_properties("${sharedData}/loadprogress/page6.html" + PROPERTIES QT_RESOURCE_ALIAS "page6.html" +) +set_source_files_properties("${sharedData}/loadprogress/page7.html" + PROPERTIES QT_RESOURCE_ALIAS "page7.html" +) +set_source_files_properties("${sharedData}/loadprogress/page8.html" + PROPERTIES QT_RESOURCE_ALIAS "page8.html" +) + +set(tst_loadsignals_resource_files + "${sharedData}/loadprogress/downloadable.tar.gz" + "${sharedData}/loadprogress/page1.html" + "${sharedData}/loadprogress/page2.html" + "${sharedData}/loadprogress/page3.html" + "${sharedData}/loadprogress/page4.html" + "${sharedData}/loadprogress/page5.html" + "${sharedData}/loadprogress/page6.html" + "${sharedData}/loadprogress/page7.html" + "${sharedData}/loadprogress/page8.html" +) + +qt_internal_add_resource(tst_loadsignals "tst_loadsignals" + PREFIX + "/resources" + FILES + ${tst_loadsignals_resource_files} +) diff --git a/tests/auto/widgets/loadsignals/loadsignals.pro b/tests/auto/widgets/loadsignals/loadsignals.pro deleted file mode 100644 index e99c7f493..000000000 --- a/tests/auto/widgets/loadsignals/loadsignals.pro +++ /dev/null @@ -1 +0,0 @@ -include(../tests.pri) diff --git a/tests/auto/widgets/loadsignals/resources/downloadable.tar.gz b/tests/auto/widgets/loadsignals/resources/downloadable.tar.gz Binary files differdeleted file mode 100644 index 741cb8ca6..000000000 --- a/tests/auto/widgets/loadsignals/resources/downloadable.tar.gz +++ /dev/null diff --git a/tests/auto/widgets/loadsignals/resources/page1.html b/tests/auto/widgets/loadsignals/resources/page1.html deleted file mode 100644 index 5cd479ab6..000000000 --- a/tests/auto/widgets/loadsignals/resources/page1.html +++ /dev/null @@ -1,8 +0,0 @@ -<html> - <head> - <title>page1</title> - </head> - <body> - <h1>page1</h1> - </body> -</html> diff --git a/tests/auto/widgets/loadsignals/resources/page2.html b/tests/auto/widgets/loadsignals/resources/page2.html deleted file mode 100644 index e3031f56a..000000000 --- a/tests/auto/widgets/loadsignals/resources/page2.html +++ /dev/null @@ -1,14 +0,0 @@ -<html> - <head> - <title>page2</title> - </head> - <style> - .fardown { - position: absolute; - top: 2500px; - } - </style> - <body> - <div class="fardown" id="anchor">page2 anchor</div> - </body> -</html> diff --git a/tests/auto/widgets/loadsignals/resources/page3.html b/tests/auto/widgets/loadsignals/resources/page3.html deleted file mode 100644 index d38ca31f0..000000000 --- a/tests/auto/widgets/loadsignals/resources/page3.html +++ /dev/null @@ -1,20 +0,0 @@ -<html> - <head> - <title>page3</title> - </head> - <script> - setTimeout(function(){ - document.getElementById('anchorLink').click(); - },500); - </script> - <style> - .fardown { - position: absolute; - top: 2500px; - } - </style> - <body> - <div><a id="anchorLink" href="#anchor">page3</a></div> - <div class="fardown" id="anchor">page3 anchor</div> - </body> -</html> diff --git a/tests/auto/widgets/loadsignals/resources/page4.html b/tests/auto/widgets/loadsignals/resources/page4.html deleted file mode 100644 index 61976b4fb..000000000 --- a/tests/auto/widgets/loadsignals/resources/page4.html +++ /dev/null @@ -1,8 +0,0 @@ -<html> - <head> - <title>page4</title> - </head> - <body onload="document.getElementById('downloadLink').focus();"> - <a id="downloadLink" href="downloadable.tar.gz">download</a> - </body> -</html> diff --git a/tests/auto/widgets/loadsignals/tst_loadsignals.cpp b/tests/auto/widgets/loadsignals/tst_loadsignals.cpp index c0bb8d5c5..6140b3766 100644 --- a/tests/auto/widgets/loadsignals/tst_loadsignals.cpp +++ b/tests/auto/widgets/loadsignals/tst_loadsignals.cpp @@ -1,97 +1,128 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <QtTest/QtTest> -#include "../util.h" +#include "httpserver.h" +#include <util.h> #include "qdebug.h" +#include "qwebengineloadinginfo.h" #include "qwebenginepage.h" #include "qwebengineprofile.h" #include "qwebenginesettings.h" #include "qwebengineview.h" +enum { + LoadStarted = QWebEngineLoadingInfo::LoadStartedStatus, + LoadStopped = QWebEngineLoadingInfo::LoadStoppedStatus, + LoadSucceeded = QWebEngineLoadingInfo::LoadSucceededStatus, + LoadFailed = QWebEngineLoadingInfo::LoadFailedStatus, +}; +static const QList<int> SignalsOrderOnce({ LoadStarted, LoadSucceeded}); +static const QList<int> SignalsOrderTwice({ LoadStarted, LoadSucceeded, LoadStarted, LoadSucceeded }); +static const QList<int> SignalsOrderOnceFailure({ LoadStarted, LoadFailed }); +static const QList<int> SignalsOrderTwiceWithFailure({ LoadStarted, LoadSucceeded, LoadStarted, LoadFailed }); + +class TestPage : public QWebEnginePage +{ +public: + QSet<QUrl> blacklist; + int navigationRequestCount = 0; + QList<int> signalsOrder; + QList<int> loadProgress; + QList<QWebEngineLoadingInfo> loadingInfos; + + explicit TestPage(QObject *parent = nullptr) : TestPage(nullptr, parent) { } + TestPage(QWebEngineProfile *profile, QObject *parent = nullptr) : QWebEnginePage(profile, parent) { + connect(this, &QWebEnginePage::loadStarted, [this] () { signalsOrder.append(LoadStarted); }); + connect(this, &QWebEnginePage::loadProgress, [this] (int p) { loadProgress.append(p); }); + connect(this, &QWebEnginePage::loadFinished, [this] (bool r) { signalsOrder.append(r ? LoadSucceeded : LoadFailed); }); + connect(this, &QWebEnginePage::loadingChanged, [this] (const QWebEngineLoadingInfo &i) { loadingInfos.append(i); }); + } + ~TestPage() { Q_ASSERT(signalsOrder.size() == loadingInfos.size()); } + + void reset() + { + blacklist.clear(); + navigationRequestCount = 0; + signalsOrder.clear(); + loadProgress.clear(); + loadingInfos.clear(); + } + +protected: + bool acceptNavigationRequest(const QUrl &url, NavigationType, bool) override + { + ++navigationRequestCount; + return !blacklist.contains(url); + } +}; + class tst_LoadSignals : public QObject { Q_OBJECT -public: - tst_LoadSignals(); - virtual ~tst_LoadSignals(); - public Q_SLOTS: void initTestCase(); void init(); - void cleanup(); private Q_SLOTS: void monotonicity(); void loadStartedAndFinishedCount_data(); void loadStartedAndFinishedCount(); - void secondLoadForError_WhenErrorPageEnabled_data(); - void secondLoadForError_WhenErrorPageEnabled(); + void loadStartedAndFinishedCountClick_data(); + void loadStartedAndFinishedCountClick(); + void rejectNavigationRequest_data(); + void rejectNavigationRequest(); void loadAfterInPageNavigation_qtbug66869(); - void fileDownloadDoesNotTriggerLoadSignals_qtbug66661(); + void fileDownload(); + void numberOfStartedAndFinishedSignalsIsSame_data(); + void numberOfStartedAndFinishedSignalsIsSame(); + void loadFinishedAfterNotFoundError_data(); + void loadFinishedAfterNotFoundError(); + void errorPageTriggered_data(); + void errorPageTriggered(); private: - QWebEngineView* view; - QScopedPointer<QSignalSpy> loadStartedSpy; - QScopedPointer<QSignalSpy> loadProgressSpy; - QScopedPointer<QSignalSpy> loadFinishedSpy; + void clickLink(QPoint linkPos); + + QWebEngineProfile profile; + TestPage page{&profile}; + QWebEngineView view; + QSignalSpy loadStartedSpy{&page, &QWebEnginePage::loadStarted}; + QSignalSpy loadFinishedSpy{&page, &QWebEnginePage::loadFinished}; + void resetSpies() { + loadStartedSpy.clear(); + loadFinishedSpy.clear(); + } }; -tst_LoadSignals::tst_LoadSignals() -{ -} - -tst_LoadSignals::~tst_LoadSignals() -{ -} - void tst_LoadSignals::initTestCase() { + view.setPage(&page); + view.resize(640, 480); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); } void tst_LoadSignals::init() { - view = new QWebEngineView(); - view->resize(1024,768); - view->show(); - loadStartedSpy.reset(new QSignalSpy(view->page(), &QWebEnginePage::loadStarted)); - loadProgressSpy.reset(new QSignalSpy(view->page(), &QWebEnginePage::loadProgress)); - loadFinishedSpy.reset(new QSignalSpy(view->page(), &QWebEnginePage::loadFinished)); + // Reset content + if (!view.url().isEmpty()) { + loadFinishedSpy.clear(); + view.load(QUrl("about:blank")); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + } + resetSpies(); + page.reset(); } -void tst_LoadSignals::cleanup() +void tst_LoadSignals::clickLink(QPoint linkPos) { - loadFinishedSpy.reset(); - loadProgressSpy.reset(); - loadStartedSpy.reset(); - delete view; + // Simulate left-clicking on link. + QTRY_VERIFY(view.focusProxy()); + QWidget *renderWidget = view.focusProxy(); + QTest::mouseClick(renderWidget, Qt::LeftButton, {}, linkPos); } /** @@ -100,94 +131,168 @@ void tst_LoadSignals::cleanup() void tst_LoadSignals::loadStartedAndFinishedCount_data() { QTest::addColumn<QUrl>("url"); - QTest::addColumn<int>("expectedLoadCount"); - QTest::newRow("Normal") << QUrl("qrc:///resources/page1.html") << 1; - QTest::newRow("WithAnchor") << QUrl("qrc:///resources/page2.html#anchor") << 1; - - // In this case, we get an unexpected additional loadStarted, but no corresponding - // loadFinished, so expectedLoadCount=2 would also not work. See also QTBUG-65223 - QTest::newRow("WithAnchorClickedFromJS") << QUrl("qrc:///resources/page3.html") << 1; + QTest::addColumn<QList<int>>("expectedSignals"); + QTest::newRow("Simple") << QUrl("qrc:///resources/page1.html") << SignalsOrderOnce; + QTest::newRow("SimpleWithAnchor") << QUrl("qrc:///resources/page2.html#anchor") << SignalsOrderOnce; + QTest::newRow("SamePageImmediate") << QUrl("qrc:///resources/page5.html") << SignalsOrderOnce; + QTest::newRow("SamePageDeferred") << QUrl("qrc:///resources/page3.html") << SignalsOrderOnce; + QTest::newRow("OtherPageImmediate") << QUrl("qrc:///resources/page6.html") << SignalsOrderOnce; + QTest::newRow("OtherPageDeferred") << QUrl("qrc:///resources/page7.html") << SignalsOrderTwice; + QTest::newRow("SamePageImmediateJS") << QUrl("qrc:///resources/page8.html") << SignalsOrderOnce; } void tst_LoadSignals::loadStartedAndFinishedCount() { QFETCH(QUrl, url); - QFETCH(int, expectedLoadCount); + QFETCH(QList<int>, expectedSignals); - view->load(url); - QTRY_COMPARE(loadFinishedSpy->size(), expectedLoadCount); - bool loadSucceeded = (*loadFinishedSpy)[0][0].toBool(); - QVERIFY(loadSucceeded); + view.load(url); - // Wait for 10 seconds (abort waiting if another loadStarted or loadFinished occurs) - QTRY_LOOP_IMPL((loadStartedSpy->size() != expectedLoadCount) - || (loadFinishedSpy->size() != expectedLoadCount), 10000, 100); + int expectedLoadCount = expectedSignals.size() / 2; + QTRY_COMPARE(loadStartedSpy.size(), expectedLoadCount); + QTRY_COMPARE(loadFinishedSpy.size(), expectedLoadCount); - // No further loadStarted should have occurred within this time - QCOMPARE(loadStartedSpy->size(), expectedLoadCount); - QCOMPARE(loadFinishedSpy->size(), expectedLoadCount); + // verify no more signals is emitted by waiting for another loadStarted or loadFinished + QTRY_LOOP_IMPL(loadStartedSpy.size() != expectedLoadCount || loadFinishedSpy.size() != expectedLoadCount, 1000, 100); + + // No further signals should have occurred within this time and expected number of signals is preserved + QCOMPARE(loadStartedSpy.size(), expectedLoadCount); + QCOMPARE(loadFinishedSpy.size(), expectedLoadCount); + QCOMPARE(page.signalsOrder, expectedSignals); + QCOMPARE(page.signalsOrder.size(), page.loadingInfos.size()); + for (int i = 0; i < page.signalsOrder.size(); ++i) + QCOMPARE(page.signalsOrder[i], page.loadingInfos[i].status()); } /** - * Test monotonicity of loadProgress signals - */ -void tst_LoadSignals::monotonicity() + * Load a URL, then simulate a click to load a different URL. + */ +void tst_LoadSignals::loadStartedAndFinishedCountClick_data() { - view->load(QUrl("qrc:///resources/page1.html")); - QTRY_COMPARE(loadFinishedSpy->size(), 1); - bool loadSucceeded = (*loadFinishedSpy)[0][0].toBool(); - QVERIFY(loadSucceeded); - - // first loadProgress should have 0% progress - QCOMPARE(loadProgressSpy->first()[0].toInt(), 0); + QTest::addColumn<QUrl>("url"); + QTest::addColumn<int>("numberOfSignals"); + QTest::newRow("SamePage") << QUrl("qrc:///resources/page2.html") << 0; // in-page navigation to anchor shouldn't emit anything + QTest::newRow("OtherPage") << QUrl("qrc:///resources/page1.html") << 1; +} - // every loadProgress should have at least as much progress as the one before - int progress = 0; - for (auto item : *loadProgressSpy) { - QVERIFY(item[0].toInt() >= progress); - progress = item[0].toInt(); +void tst_LoadSignals::loadStartedAndFinishedCountClick() +{ + QFETCH(QUrl, url); + QFETCH(int, numberOfSignals); + + view.load(url); + QTRY_COMPARE(loadStartedSpy.size(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QVERIFY(loadFinishedSpy[0][0].toBool()); + resetSpies(); + + clickLink(QPoint(10, 10)); + if (numberOfSignals > 0) { + QTRY_COMPARE(loadStartedSpy.size(), numberOfSignals); + QTRY_COMPARE(loadFinishedSpy.size(), numberOfSignals); + QVERIFY(loadFinishedSpy[0][0].toBool()); } - // last loadProgress should have 100% progress - QCOMPARE(loadProgressSpy->last()[0].toInt(), 100); + // verify no more signals is emitted by waiting for another loadStarted or loadFinished + QTRY_LOOP_IMPL(loadStartedSpy.size() != numberOfSignals || loadFinishedSpy.size() != numberOfSignals, 1000, 100); + + // No further loadStarted should have occurred within this time + QCOMPARE(loadStartedSpy.size(), numberOfSignals); + QCOMPARE(loadFinishedSpy.size(), numberOfSignals); + QCOMPARE(page.signalsOrder, numberOfSignals > 0 ? SignalsOrderTwice : SignalsOrderOnce); +} + +void tst_LoadSignals::rejectNavigationRequest_data() +{ + QTest::addColumn<QUrl>("initialUrl"); + QTest::addColumn<QUrl>("rejectedUrl"); + QTest::addColumn<int>("expectedNavigations"); + QTest::addColumn<QList<int>>("expectedSignals"); + QTest::addColumn<int>("errorCode"); + QTest::addColumn<QWebEngineLoadingInfo::ErrorDomain>("errorDomain"); + QTest::newRow("Simple") + << QUrl("qrc:///resources/page1.html") + << QUrl("qrc:///resources/page1.html") + << 1 << SignalsOrderOnceFailure << -3 << QWebEngineLoadingInfo::InternalErrorDomain; + QTest::newRow("SamePageImmediate") + << QUrl("qrc:///resources/page5.html") + << QUrl("qrc:///resources/page5.html#anchor") + << 1 << SignalsOrderOnce << 200 << QWebEngineLoadingInfo::HttpStatusCodeDomain; + QTest::newRow("SamePageDeferred") + << QUrl("qrc:///resources/page3.html") + << QUrl("qrc:///resources/page3.html#anchor") + << 1 << SignalsOrderOnce << 200 << QWebEngineLoadingInfo::HttpStatusCodeDomain; + QTest::newRow("OtherPageImmediate") + << QUrl("qrc:///resources/page6.html") + << QUrl("qrc:///resources/page2.html#anchor") + << 2 << SignalsOrderOnceFailure << -3 << QWebEngineLoadingInfo::InternalErrorDomain; + QTest::newRow("OtherPageDeferred") + << QUrl("qrc:///resources/page7.html") + << QUrl("qrc:///resources/page2.html#anchor") + << 2 << SignalsOrderTwiceWithFailure << -3 << QWebEngineLoadingInfo::InternalErrorDomain; } /** - * Test that we get a second loadStarted and loadFinished signal - * for error-pages (unless error-pages are disabled) - */ -void tst_LoadSignals::secondLoadForError_WhenErrorPageEnabled_data() + * Returning false from acceptNavigationRequest means that the load + * fails, not that the load never starts. + * + * See QTBUG-75185. + */ +void tst_LoadSignals::rejectNavigationRequest() { - QTest::addColumn<bool>("enabled"); - // in this case, we get no second loadStarted and loadFinished, although we had - // agreed on making the navigation to an error page an individual load - QTest::newRow("ErrorPageEnabled") << true; - QTest::newRow("ErrorPageDisabled") << false; + QFETCH(QUrl, initialUrl); + QFETCH(QUrl, rejectedUrl); + QFETCH(int, expectedNavigations); + QFETCH(QList<int>, expectedSignals); + QFETCH(int, errorCode); + QFETCH(QWebEngineLoadingInfo::ErrorDomain, errorDomain); + + page.blacklist.insert(rejectedUrl); + page.load(initialUrl); + QTRY_COMPARE(page.navigationRequestCount, expectedNavigations); + int expectedLoadCount = expectedSignals.size() / 2; + QTRY_COMPARE(loadFinishedSpy.size(), expectedLoadCount); + QCOMPARE(page.signalsOrder, expectedSignals); + + // verify no more signals is emitted by waiting for another loadStarted or loadFinished + QTRY_LOOP_IMPL(loadStartedSpy.size() != expectedLoadCount || loadFinishedSpy.size() != expectedLoadCount, 1000, 100); + + // No further loadStarted should have occurred within this time + QCOMPARE(loadStartedSpy.size(), expectedLoadCount); + QCOMPARE(loadFinishedSpy.size(), expectedLoadCount); + QCOMPARE(page.loadingInfos.last().errorCode(), errorCode); + QCOMPARE(page.loadingInfos.last().errorDomain(), errorDomain); } -void tst_LoadSignals::secondLoadForError_WhenErrorPageEnabled() +/** + * Test monotonicity of loadProgress signals + */ +void tst_LoadSignals::monotonicity() { - QFETCH(bool, enabled); - view->settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, enabled); - int expectedLoadCount = (enabled ? 2 : 1); - - // RFC 2606 guarantees that this will never become a valid domain - view->load(QUrl("http://nonexistent.invalid")); - QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy->size(), expectedLoadCount, 10000); - bool loadSucceeded = (*loadFinishedSpy)[0][0].toBool(); - QVERIFY(!loadSucceeded); - if (enabled) { - bool errorPageLoadSucceeded = (*loadFinishedSpy)[1][0].toBool(); - QVERIFY(errorPageLoadSucceeded); - } + HttpServer server; + server.setResourceDirs({ server.sharedDataDir() }); + connect(&server, &HttpServer::newRequest, [] (HttpReqRep *) { + QTest::qWait(250); // just add delay to trigger some progress for every sub resource + }); + QVERIFY(server.start()); + + view.load(server.url("/loadprogress/main.html")); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 10000); + QVERIFY(loadFinishedSpy[0][0].toBool()); + + QVERIFY(page.loadProgress.size() >= 3); + // first loadProgress should have 0% progress + QCOMPARE(page.loadProgress.first(), 0); - // Wait for 10 seconds (abort waiting if another loadStarted or loadFinished occurs) - QTRY_LOOP_IMPL((loadStartedSpy->size() != expectedLoadCount) - || (loadFinishedSpy->size() != expectedLoadCount), 10000, 100); + // every loadProgress should have more progress than the one before + int progress = -1; + for (int p : page.loadProgress) { + QVERIFY(progress < p); + progress = p; + } - // No further loadStarted should have occurred within this time - QCOMPARE(loadStartedSpy->size(), expectedLoadCount); - QCOMPARE(loadFinishedSpy->size(), expectedLoadCount); + // last loadProgress should have 100% progress + QCOMPARE(page.loadProgress.last(), 100); } /** @@ -196,70 +301,236 @@ void tst_LoadSignals::secondLoadForError_WhenErrorPageEnabled() */ void tst_LoadSignals::loadAfterInPageNavigation_qtbug66869() { - view->load(QUrl("qrc:///resources/page3.html")); - QTRY_COMPARE(loadFinishedSpy->size(), 1); - bool loadSucceeded = (*loadFinishedSpy)[0][0].toBool(); - QVERIFY(loadSucceeded); + view.load(QUrl("qrc:///resources/page3.html")); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QVERIFY(loadFinishedSpy[0][0].toBool()); // page3 does an in-page navigation after 500ms - QTest::qWait(2000); - loadFinishedSpy->clear(); - loadProgressSpy->clear(); - loadStartedSpy->clear(); + QTRY_COMPARE(view.url(), QUrl("qrc:///resources/page3.html#anchor")); // second load - view->load(QUrl("qrc:///resources/page1.html")); - QTRY_COMPARE(loadFinishedSpy->size(), 1); - loadSucceeded = (*loadFinishedSpy)[0][0].toBool(); - QVERIFY(loadSucceeded); + view.load(QUrl("qrc:///resources/page1.html")); + QTRY_COMPARE(loadFinishedSpy.size(), 2); + QVERIFY(loadFinishedSpy[0][0].toBool()); // loadStarted and loadFinished should have been signalled - QCOMPARE(loadStartedSpy->size(), 1); - - // reminder that we still need to solve the core issue - QFAIL("https://codereview.qt-project.org/#/c/222112/ only hides the symptom, the core issue still needs to be solved"); + QCOMPARE(loadStartedSpy.size(), 2); } -/** - * Test that file-downloads don't trigger loadStarted or loadFinished signals. - * See QTBUG-66661 - */ -void tst_LoadSignals::fileDownloadDoesNotTriggerLoadSignals_qtbug66661() +void tst_LoadSignals::fileDownload() { - view->load(QUrl("qrc:///resources/page4.html")); - QTRY_COMPARE(loadFinishedSpy->size(), 1); - bool loadSucceeded = (*loadFinishedSpy)[0][0].toBool(); - QVERIFY(loadSucceeded); + view.load(QUrl("qrc:///resources/page4.html")); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QVERIFY(loadFinishedSpy[0][0].toBool()); // allow the download QTemporaryDir tempDir; - QWebEngineDownloadItem::DownloadState downloadState = QWebEngineDownloadItem::DownloadRequested; - connect(view->page()->profile(), &QWebEngineProfile::downloadRequested, - [this, &downloadState, &tempDir](QWebEngineDownloadItem* item){ - connect(item, &QWebEngineDownloadItem::stateChanged, [&downloadState](QWebEngineDownloadItem::DownloadState newState){ - downloadState = newState; - }); - item->setDownloadDirectory(tempDir.filePath(QFileInfo(item->path()).path())); - item->setDownloadFileName(QFileInfo(item->path()).fileName()); - item->accept(); - }); + QVERIFY(tempDir.isValid()); + QWebEngineDownloadRequest::DownloadState downloadState = QWebEngineDownloadRequest::DownloadRequested; + ScopedConnection sc1 = + connect(&profile, &QWebEngineProfile::downloadRequested, + [&downloadState, &tempDir](QWebEngineDownloadRequest *item) { + connect(item, &QWebEngineDownloadRequest::stateChanged, + [&downloadState](QWebEngineDownloadRequest::DownloadState newState) { + downloadState = newState; + }); + item->setDownloadDirectory(tempDir.path()); + item->accept(); + }); // trigger the download link that becomes focused on page4 - QTest::qWait(1000); - QTest::sendKeyEvent(QTest::Press, view->focusProxy(), Qt::Key_Return, QString("\r"), Qt::NoModifier); - QTest::sendKeyEvent(QTest::Release, view->focusProxy(), Qt::Key_Return, QString("\r"), Qt::NoModifier); - - // Wait for 10 seconds (abort waiting if another loadStarted or loadFinished occurs) - QTRY_LOOP_IMPL((loadStartedSpy->size() != 1) - || (loadFinishedSpy->size() != 1), 10000, 100); + QTest::sendKeyEvent(QTest::Press, view.focusProxy(), Qt::Key_Return, QString("\r"), Qt::NoModifier); + QTest::sendKeyEvent(QTest::Release, view.focusProxy(), Qt::Key_Return, QString("\r"), Qt::NoModifier); // Download must have occurred - QTRY_COMPARE(downloadState, QWebEngineDownloadItem::DownloadCompleted); + QTRY_COMPARE(downloadState, QWebEngineDownloadRequest::DownloadCompleted); + QTRY_COMPARE(loadFinishedSpy.size() + loadStartedSpy.size(), 4); - // No further loadStarted should have occurred within this time - QCOMPARE(loadStartedSpy->size(), 1); - QCOMPARE(loadFinishedSpy->size(), 1); + // verify no more signals is emitted by waiting for another loadStarted or loadFinished + QTRY_LOOP_IMPL(loadStartedSpy.size() != 2 || loadFinishedSpy.size() != 2, 1000, 100); + + QCOMPARE(page.signalsOrder, SignalsOrderTwiceWithFailure); + QCOMPARE(page.loadingInfos[3].errorCode(), -3); + QCOMPARE(page.loadingInfos[3].errorDomain(), QWebEngineLoadingInfo::InternalErrorDomain); +} + +void tst_LoadSignals::numberOfStartedAndFinishedSignalsIsSame_data() +{ + QTest::addColumn<bool>("imageFromServer"); + QTest::addColumn<QString>("imageResourceUrl"); + // triggers these calls in delegate internally: + // just two ordered triples DidStartNavigation/DidFinishNavigation/DidFinishLoad + QTest::newRow("no_image_resource") << false << ""; + // out of order: DidStartNavigation/DidFinishNavigation/DidStartNavigation/DidFailLoad/DidFinishNavigation/DidFinishLoad + QTest::newRow("with_invalid_image") << false << "https://non.existent.locahost/image.png"; + // out of order: DidStartNavigation/DidFinishNavigation/DidStartNavigation/DidFinishLoad/DidFinishNavigation/DidFinishLoad + QTest::newRow("with_server_image") << true << ""; +} + +void tst_LoadSignals::numberOfStartedAndFinishedSignalsIsSame() +{ + QFETCH(bool, imageFromServer); + QFETCH(QString, imageResourceUrl); + + HttpServer server; + server.setResourceDirs({ server.sharedDataDir() }); + QVERIFY(server.start()); + + QUrl serverImage = server.url("/hedgehog.png"); + QString imageUrl(!imageFromServer && imageResourceUrl.isEmpty() + ? "" : (imageFromServer ? serverImage.toEncoded() : imageResourceUrl)); + + auto html = "<html><head><link rel='icon' href='data:,'></head><body>" + "%1" "<form method='GET' name='hiddenform' action='qrc:///resources/page1.html' />" + "<script language='javascript'>document.forms[0].submit();</script>" + "</body></html>"; + view.page()->setHtml(QString(html).arg(imageUrl.isEmpty() ? "" : "<img src='" + imageUrl + "'>")); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + + resetSpies(); + QTRY_LOOP_IMPL(loadStartedSpy.size() || loadFinishedSpy.size(), 1000, 100); + QCOMPARE(page.signalsOrder, SignalsOrderOnce); + QCOMPARE(page.loadingInfos[1].errorCode(), 200); + QCOMPARE(page.loadingInfos[1].errorDomain(), QWebEngineLoadingInfo::HttpStatusCodeDomain); +} + +void tst_LoadSignals::loadFinishedAfterNotFoundError_data() +{ + QTest::addColumn<bool>("rfcInvalid"); + QTest::addColumn<bool>("withServer"); + QTest::addColumn<int>("errorCode"); + QTest::addColumn<int>("errorDomain"); + QTest::addRow("rfc_invalid") << true << false << -105 << int(QWebEngineLoadingInfo::ConnectionErrorDomain); + QTest::addRow("non_existent") << false << false << -105 << int(QWebEngineLoadingInfo::ConnectionErrorDomain); + QTest::addRow("server_404") << false << true << 404 << int(QWebEngineLoadingInfo::HttpStatusCodeDomain); } +void tst_LoadSignals::loadFinishedAfterNotFoundError() +{ + QFETCH(bool, rfcInvalid); + QFETCH(bool, withServer); + QFETCH(int, errorCode); + QFETCH(int, errorDomain); + + QScopedPointer<HttpServer> server; + if (withServer) { + server.reset(new HttpServer); + QVERIFY(server->start()); + } + view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); + auto url = server + ? server->url("/not-found-page.html") + : QUrl(rfcInvalid ? "http://some.invalid" : "http://non.existent/url"); + view.load(url); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 20000); + QVERIFY(!loadFinishedSpy.at(0).at(0).toBool()); + QCOMPARE(toPlainTextSync(view.page()), QString()); + QCOMPARE(loadFinishedSpy.size(), 1); + QCOMPARE(loadStartedSpy.size(), 1); + QVERIFY(std::is_sorted(page.loadProgress.begin(), page.loadProgress.end())); + page.loadProgress.clear(); + + { auto &&loadStart = page.loadingInfos[0], &&loadFinish = page.loadingInfos[1]; + QCOMPARE(loadStart.status(), QWebEngineLoadingInfo::LoadStartedStatus); + QCOMPARE(loadStart.isErrorPage(), false); + QCOMPARE(loadStart.errorCode(), 0); + QCOMPARE(loadStart.errorDomain(), QWebEngineLoadingInfo::NoErrorDomain); + QCOMPARE(loadStart.errorString(), QString()); + QCOMPARE(loadFinish.status(), QWebEngineLoadingInfo::LoadFailedStatus); + QCOMPARE(loadFinish.isErrorPage(), false); + QCOMPARE(loadFinish.errorCode(), errorCode); + QCOMPARE(loadFinish.errorDomain(), errorDomain); + QVERIFY(!loadFinish.errorString().isEmpty()); + } + + view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, true); + url = server + ? server->url("/another-missing-one.html") + : QUrl(rfcInvalid ? "http://some.other.invalid" : "http://another.non.existent/url"); + view.load(url); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 2, 20000); + QVERIFY(!loadFinishedSpy.at(1).at(0).toBool()); + QCOMPARE(loadStartedSpy.size(), 2); + + QEXPECT_FAIL("", "No more loads (like separate load for error pages) are expected", Continue); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 3, 1000); + QCOMPARE(loadStartedSpy.size(), 2); + QVERIFY(std::is_sorted(page.loadProgress.begin(), page.loadProgress.end())); + + { auto &&loadStart = page.loadingInfos[2], &&loadFinish = page.loadingInfos[3]; + QCOMPARE(loadStart.status(), QWebEngineLoadingInfo::LoadStartedStatus); + QCOMPARE(loadStart.isErrorPage(), false); + QCOMPARE(loadStart.errorCode(), 0); + QCOMPARE(loadStart.errorDomain(), QWebEngineLoadingInfo::NoErrorDomain); + QCOMPARE(loadStart.errorString(), QString()); + QCOMPARE(loadFinish.status(), QWebEngineLoadingInfo::LoadFailedStatus); + QCOMPARE(loadFinish.isErrorPage(), true); + QCOMPARE(loadFinish.errorCode(), errorCode); + QCOMPARE(loadFinish.errorDomain(), errorDomain); + QVERIFY(!loadFinish.errorString().isEmpty()); + } +} + +void tst_LoadSignals::errorPageTriggered_data() +{ + QTest::addColumn<QString>("urlPath"); + QTest::addColumn<bool>("loadSucceed"); + QTest::addColumn<bool>("triggersErrorPage"); + QTest::addColumn<int>("errorCode"); + QTest::addColumn<QWebEngineLoadingInfo::ErrorDomain>("errorDomain"); + QTest::newRow("/content/200") << QStringLiteral("/content/200") << true << false << 200 << QWebEngineLoadingInfo::HttpStatusCodeDomain; + QTest::newRow("/empty/200") << QStringLiteral("/content/200") << true << false << 200 << QWebEngineLoadingInfo::HttpStatusCodeDomain; + QTest::newRow("/content/404") << QStringLiteral("/content/404") << false << false << 404 << QWebEngineLoadingInfo::HttpStatusCodeDomain; + QTest::newRow("/empty/404") << QStringLiteral("/empty/404") << false << true << 404 << QWebEngineLoadingInfo::HttpStatusCodeDomain; +} + +void tst_LoadSignals::errorPageTriggered() +{ + HttpServer server; + connect(&server, &HttpServer::newRequest, [] (HttpReqRep *rr) { + QList<QByteArray> parts = rr->requestPath().split('/'); + if (parts.size() != 3) { + // For example, /favicon.ico + rr->sendResponse(404); + return; + } + bool isDocumentEmpty = (parts[1] == "empty"); + int httpStatusCode = parts[2].toInt(); + + rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html")); + if (!isDocumentEmpty) { + rr->setResponseBody(QByteArrayLiteral("<html></html>")); + } + rr->sendResponse(httpStatusCode); + }); + QVERIFY(server.start()); + + QFETCH(QString, urlPath); + QFETCH(bool, loadSucceed); + QFETCH(bool, triggersErrorPage); + QFETCH(int, errorCode); + QFETCH(QWebEngineLoadingInfo::ErrorDomain, errorDomain); + + view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, true); + view.load(server.url(urlPath)); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QCOMPARE(loadFinishedSpy[0][0].toBool(), loadSucceed); + if (triggersErrorPage) + QVERIFY(toPlainTextSync(view.page()).contains("HTTP ERROR 404")); + else + QVERIFY(toPlainTextSync(view.page()).isEmpty()); + QCOMPARE(page.loadingInfos[1].errorCode(), errorCode); + QCOMPARE(page.loadingInfos[1].errorDomain(), errorDomain); + loadFinishedSpy.clear(); + + view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); + view.load(server.url(urlPath)); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QCOMPARE(loadFinishedSpy[0][0].toBool(), loadSucceed); + QVERIFY(toPlainTextSync(view.page()).isEmpty()); + QCOMPARE(page.loadingInfos[3].errorCode(), errorCode); + QCOMPARE(page.loadingInfos[3].errorDomain(), errorDomain); + loadFinishedSpy.clear(); +} QTEST_MAIN(tst_LoadSignals) #include "tst_loadsignals.moc" diff --git a/tests/auto/widgets/loadsignals/tst_loadsignals.qrc b/tests/auto/widgets/loadsignals/tst_loadsignals.qrc deleted file mode 100644 index 316deecb8..000000000 --- a/tests/auto/widgets/loadsignals/tst_loadsignals.qrc +++ /dev/null @@ -1,9 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>resources/page1.html</file> - <file>resources/page2.html</file> - <file>resources/page3.html</file> - <file>resources/page4.html</file> - <file>resources/downloadable.tar.gz</file> - </qresource> -</RCC> diff --git a/tests/auto/widgets/offscreen/CMakeLists.txt b/tests/auto/widgets/offscreen/CMakeLists.txt new file mode 100644 index 000000000..756e53c43 --- /dev/null +++ b/tests/auto/widgets/offscreen/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_test(tst_offscreen + SOURCES + tst_offscreen.cpp + LIBRARIES + Qt::WebEngineWidgets +) + +set(tst_offscreen_resource_files + "test.html" +) + +set_tests_properties(tst_offscreen PROPERTIES + ENVIRONMENT QT_QPA_PLATFORM=offscreen +) + +qt_internal_add_resource(tst_offscreen "tst_offscreen" + PREFIX + "/" + FILES + ${tst_offscreen_resource_files} +) + diff --git a/tests/auto/widgets/offscreen/offscreen.pro b/tests/auto/widgets/offscreen/offscreen.pro deleted file mode 100644 index b8e5632f9..000000000 --- a/tests/auto/widgets/offscreen/offscreen.pro +++ /dev/null @@ -1,6 +0,0 @@ -include(../tests.pri) -QT += webengine -qpa.name = QT_QPA_PLATFORM -qpa.value = offscreen -QT_TOOL_ENV += qpa - diff --git a/tests/auto/widgets/offscreen/tst_offscreen.cpp b/tests/auto/widgets/offscreen/tst_offscreen.cpp index 7573b0537..553dc653b 100644 --- a/tests/auto/widgets/offscreen/tst_offscreen.cpp +++ b/tests/auto/widgets/offscreen/tst_offscreen.cpp @@ -1,32 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "qtwebengineglobal.h" #include <QTest> #include <QSignalSpy> #include <QWebEngineProfile> @@ -52,7 +26,7 @@ void tst_OffScreen::offscreen() page.load(QUrl("qrc:/test.html")); view.show(); QTRY_COMPARE(view.isVisible(), true); - QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.count() > 0, true, 20000); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size() > 0, true, 20000); QCOMPARE(loadFinishedSpy.takeFirst().at(0).toBool(), true); } diff --git a/tests/auto/widgets/offscreen/tst_offscreen.qrc b/tests/auto/widgets/offscreen/tst_offscreen.qrc deleted file mode 100644 index 8a998fe85..000000000 --- a/tests/auto/widgets/offscreen/tst_offscreen.qrc +++ /dev/null @@ -1,6 +0,0 @@ -<!DOCTYPE RCC><RCC version="1.0"> -<qresource> - <file>test.html</file> -</qresource> -</RCC> - diff --git a/tests/auto/widgets/origins/origins.pro b/tests/auto/widgets/origins/origins.pro deleted file mode 100644 index 7498354de..000000000 --- a/tests/auto/widgets/origins/origins.pro +++ /dev/null @@ -1,7 +0,0 @@ -include(../tests.pri) -CONFIG += c++14 -qtConfig(webengine-webchannel):qtHaveModule(websockets) { - QT += websockets - DEFINES += WEBSOCKETS -} - diff --git a/tests/auto/widgets/origins/resources/createObjectURL.html b/tests/auto/widgets/origins/resources/createObjectURL.html deleted file mode 100644 index 133f636bb..000000000 --- a/tests/auto/widgets/origins/resources/createObjectURL.html +++ /dev/null @@ -1,11 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>createObjectURL</title> - <script> - const blob = new Blob(['foo']); - const result = URL.createObjectURL(blob); - </script> - </head> - <body></body> -</html> diff --git a/tests/auto/widgets/origins/resources/dedicatedWorker.html b/tests/auto/widgets/origins/resources/dedicatedWorker.html deleted file mode 100644 index cb4f14e73..000000000 --- a/tests/auto/widgets/origins/resources/dedicatedWorker.html +++ /dev/null @@ -1,19 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>dedicatedWorker</title> - <script> - var done = false; - var result; - var error; - try { - let worker = new Worker("dedicatedWorker.js"); - worker.onmessage = (e) => { done = true; result = e.data; }; - worker.postMessage(41); - } catch (e) { - done = true; error = e.message; - } - </script> - </head> - <body></body> -</html> diff --git a/tests/auto/widgets/origins/resources/dedicatedWorker.js b/tests/auto/widgets/origins/resources/dedicatedWorker.js deleted file mode 100644 index 2631939d7..000000000 --- a/tests/auto/widgets/origins/resources/dedicatedWorker.js +++ /dev/null @@ -1 +0,0 @@ -onmessage = (e) => { postMessage(e.data + 1); }; diff --git a/tests/auto/widgets/origins/resources/mixedSchemes.html b/tests/auto/widgets/origins/resources/mixedSchemes.html deleted file mode 100644 index c73e9ecdc..000000000 --- a/tests/auto/widgets/origins/resources/mixedSchemes.html +++ /dev/null @@ -1,31 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>Mixed</title> - <script> - var result; - var canary; - - function setIFrameUrl(url) { - result = undefined; - canary = undefined; - document.getElementById("iframe").setAttribute("src", url); - // Early fire is OK unless the test is expecting cannotLoad. - // If timeout is too short then a false positive is possible. - setTimeout(() => { result = result || "cannotLoad"; }, 500); - } - - addEventListener("load", function() { - document.getElementById("iframe").addEventListener("load", function() { - if (canary && window[0].canary) - result = "canLoadAndAccess"; - else - result = "canLoadButNotAccess"; - }); - }); - </script> - </head> - <body> - <iframe id="iframe"></iframe> - </body> -</html> diff --git a/tests/auto/widgets/origins/resources/mixedSchemesWithCsp.html b/tests/auto/widgets/origins/resources/mixedSchemesWithCsp.html deleted file mode 100644 index ad7cbeeb7..000000000 --- a/tests/auto/widgets/origins/resources/mixedSchemesWithCsp.html +++ /dev/null @@ -1,32 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <meta http-equiv="Content-Security-Policy" content="frame-src 'none'"> - <title>Mixed</title> - <script> - var result; - var canary; - - function setIFrameUrl(url) { - result = undefined; - canary = undefined; - document.getElementById("iframe").setAttribute("src", url); - // Early fire is OK unless the test is expecting cannotLoad. - // If timeout is too short then a false positive is possible. - setTimeout(() => { result = result || "cannotLoad"; }, 500); - } - - addEventListener("load", function() { - document.getElementById("iframe").addEventListener("load", function() { - if (canary && window[0].canary) - result = "canLoadAndAccess"; - else - result = "canLoadButNotAccess"; - }); - }); - </script> - </head> - <body> - <iframe id="iframe"></iframe> - </body> -</html> diff --git a/tests/auto/widgets/origins/resources/mixedSchemes_frame.html b/tests/auto/widgets/origins/resources/mixedSchemes_frame.html deleted file mode 100644 index 00c20ba37..000000000 --- a/tests/auto/widgets/origins/resources/mixedSchemes_frame.html +++ /dev/null @@ -1,11 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>Mixed - Frame</title> - <script> - var canary = true; - parent.canary = true; - </script> - </head> - <body></body> -</html> diff --git a/tests/auto/widgets/origins/resources/mixedXHR.html b/tests/auto/widgets/origins/resources/mixedXHR.html deleted file mode 100644 index 3dfd90006..000000000 --- a/tests/auto/widgets/origins/resources/mixedXHR.html +++ /dev/null @@ -1,19 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>Mixed</title> - <script> - var result; - function sendXHR(url) { - result = undefined; - let req = new XMLHttpRequest(); - req.addEventListener("load", () => { result = req.responseText }); - req.addEventListener("error", () => { result = "error"; }); - req.open("GET", url); - req.send(); - } - </script> - </head> - <body> - </body> -</html> diff --git a/tests/auto/widgets/origins/resources/mixedXHR.txt b/tests/auto/widgets/origins/resources/mixedXHR.txt deleted file mode 100644 index b5754e203..000000000 --- a/tests/auto/widgets/origins/resources/mixedXHR.txt +++ /dev/null @@ -1 +0,0 @@ -ok
\ No newline at end of file diff --git a/tests/auto/widgets/origins/resources/redirect.css b/tests/auto/widgets/origins/resources/redirect.css deleted file mode 100644 index 41d7560cc..000000000 --- a/tests/auto/widgets/origins/resources/redirect.css +++ /dev/null @@ -1,8 +0,0 @@ -@font-face { - font-family: 'MyWebFont'; - src: url('redirect1:/resources/Akronim-Regular.woff2') format('woff2'); -} - -body { - font-family: 'MyWebFont', Fallback, sans-serif; -} diff --git a/tests/auto/widgets/origins/resources/redirect.html b/tests/auto/widgets/origins/resources/redirect.html deleted file mode 100644 index 04948e14b..000000000 --- a/tests/auto/widgets/origins/resources/redirect.html +++ /dev/null @@ -1,10 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>redirect</title> - <link rel="stylesheet" href="redirect1:/resources/redirect.css"> - </head> - <body> - Text - </body> -</html> diff --git a/tests/auto/widgets/origins/resources/serviceWorker.html b/tests/auto/widgets/origins/resources/serviceWorker.html deleted file mode 100644 index 27890c98f..000000000 --- a/tests/auto/widgets/origins/resources/serviceWorker.html +++ /dev/null @@ -1,18 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>serviceWorker</title> - <script> - var done = false; - var error; - try { - navigator.serviceWorker.register("serviceWorker.js") - .then((r) => { done = true; }) - .catch((e) => { done = true; error = e.message; }); - } catch (e) { - done = true; error = e.message; - } - </script> - </head> - <body></body> -</html> diff --git a/tests/auto/widgets/origins/resources/serviceWorker.js b/tests/auto/widgets/origins/resources/serviceWorker.js deleted file mode 100644 index 40a8c178f..000000000 --- a/tests/auto/widgets/origins/resources/serviceWorker.js +++ /dev/null @@ -1 +0,0 @@ -/* empty */ diff --git a/tests/auto/widgets/origins/resources/sharedWorker.html b/tests/auto/widgets/origins/resources/sharedWorker.html deleted file mode 100644 index 8b5a0a794..000000000 --- a/tests/auto/widgets/origins/resources/sharedWorker.html +++ /dev/null @@ -1,19 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>sharedWorker</title> - <script> - var done; - var result; - var error; - try { - let worker = new SharedWorker("sharedWorker.js"); - worker.port.onmessage = (e) => { done = true; result = e.data; }; - worker.port.postMessage(41); - } catch (e) { - done = true; error = e.message; - } - </script> - </head> - <body></body> -</html> diff --git a/tests/auto/widgets/origins/resources/sharedWorker.js b/tests/auto/widgets/origins/resources/sharedWorker.js deleted file mode 100644 index 60ef93a5f..000000000 --- a/tests/auto/widgets/origins/resources/sharedWorker.js +++ /dev/null @@ -1,6 +0,0 @@ -onconnect = function(e) { - let port = e.ports[0]; - port.onmessage = function(e) { - port.postMessage(e.data + 1); - }; -}; diff --git a/tests/auto/widgets/origins/resources/subdir/frame2.html b/tests/auto/widgets/origins/resources/subdir/frame2.html deleted file mode 100644 index 3a2f664ca..000000000 --- a/tests/auto/widgets/origins/resources/subdir/frame2.html +++ /dev/null @@ -1,10 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>Subdir - Frame 2</title> - <script> - parent.msg[1] = "world"; - </script> - </head> - <body></body> -</html> diff --git a/tests/auto/widgets/origins/resources/subdir/index.html b/tests/auto/widgets/origins/resources/subdir/index.html deleted file mode 100644 index 9c5d5d782..000000000 --- a/tests/auto/widgets/origins/resources/subdir/index.html +++ /dev/null @@ -1,26 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>Subdir</title> - - <script> - var msg = []; - </script> - - <!-- for manual testing --> - <script> - window.addEventListener("load", () => { - for (let i of [0, 1]) { - let p = document.createElement("p"); - p.appendChild(document.createTextNode(`frame ${i+1} says: ${msg[i]}`)); - document.body.insertBefore(p, null); - } - }); - </script> - - </head> - <body> - <iframe src="../subdir_frame1.html"></iframe> - <iframe src="frame2.html"></iframe> - </body> -</html> diff --git a/tests/auto/widgets/origins/resources/subdir_frame1.html b/tests/auto/widgets/origins/resources/subdir_frame1.html deleted file mode 100644 index 63973f2f4..000000000 --- a/tests/auto/widgets/origins/resources/subdir_frame1.html +++ /dev/null @@ -1,10 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>Subdir - Frame 1</title> - <script> - parent.msg[0] = "hello"; - </script> - </head> - <body></body> -</html> diff --git a/tests/auto/widgets/origins/resources/viewSource.html b/tests/auto/widgets/origins/resources/viewSource.html deleted file mode 100644 index 977074c74..000000000 --- a/tests/auto/widgets/origins/resources/viewSource.html +++ /dev/null @@ -1,9 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>viewSource</title> - </head> - <body> - <p>viewSource</p> - </body> -</html> diff --git a/tests/auto/widgets/origins/resources/websocket.html b/tests/auto/widgets/origins/resources/websocket.html deleted file mode 100644 index 31db66571..000000000 --- a/tests/auto/widgets/origins/resources/websocket.html +++ /dev/null @@ -1,23 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>WebSocket</title> - <script src="qrc:/qtwebchannel/qwebchannel.js"></script> - <script> - var result; - new QWebChannel(qt.webChannelTransport, channel => { - const ws = new WebSocket(channel.objects.echoServer.url); - ws.addEventListener("open", event => { - ws.send("ok"); - }); - ws.addEventListener("message", event => { - result = event.data; - }); - ws.addEventListener("close", event => { - result = event.code; - }); - }) - </script> - </head> - <body></body> -</html> diff --git a/tests/auto/widgets/origins/tst_origins.cpp b/tests/auto/widgets/origins/tst_origins.cpp deleted file mode 100644 index 02d5bfba3..000000000 --- a/tests/auto/widgets/origins/tst_origins.cpp +++ /dev/null @@ -1,829 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "../util.h" - -#include <QtCore/qfile.h> -#include <QtTest/QtTest> -#include <QtWebEngineCore/qwebengineurlrequestjob.h> -#include <QtWebEngineCore/qwebengineurlscheme.h> -#include <QtWebEngineCore/qwebengineurlschemehandler.h> -#include <QtWebEngineWidgets/qwebenginepage.h> -#include <QtWebEngineWidgets/qwebengineprofile.h> -#include <QtWebEngineWidgets/qwebenginesettings.h> -#if defined(WEBSOCKETS) -#include <QtWebSockets/qwebsocket.h> -#include <QtWebSockets/qwebsocketserver.h> -#include <QtWebChannel/qwebchannel.h> -#endif -#include <QtWidgets/qaction.h> - -#define QSL QStringLiteral -#define QBAL QByteArrayLiteral -#define THIS_DIR TESTS_SOURCE_DIR "origins/" - -void registerSchemes() -{ - { - QWebEngineUrlScheme scheme(QBAL("PathSyntax")); - QWebEngineUrlScheme::registerScheme(scheme); - } - - { - QWebEngineUrlScheme scheme(QBAL("PathSyntax-Secure")); - scheme.setFlags(QWebEngineUrlScheme::SecureScheme); - QWebEngineUrlScheme::registerScheme(scheme); - } - - { - QWebEngineUrlScheme scheme(QBAL("PathSyntax-Secure-ServiceWorkersAllowed")); - scheme.setFlags(QWebEngineUrlScheme::SecureScheme | QWebEngineUrlScheme::ServiceWorkersAllowed); - QWebEngineUrlScheme::registerScheme(scheme); - } - - { - QWebEngineUrlScheme scheme(QBAL("PathSyntax-Local")); - scheme.setFlags(QWebEngineUrlScheme::LocalScheme); - QWebEngineUrlScheme::registerScheme(scheme); - } - - { - QWebEngineUrlScheme scheme(QBAL("PathSyntax-LocalAccessAllowed")); - scheme.setFlags(QWebEngineUrlScheme::LocalAccessAllowed); - QWebEngineUrlScheme::registerScheme(scheme); - } - - { - QWebEngineUrlScheme scheme(QBAL("PathSyntax-NoAccessAllowed")); - scheme.setFlags(QWebEngineUrlScheme::NoAccessAllowed); - QWebEngineUrlScheme::registerScheme(scheme); - } - - { - QWebEngineUrlScheme scheme(QBAL("PathSyntax-ServiceWorkersAllowed")); - scheme.setFlags(QWebEngineUrlScheme::ServiceWorkersAllowed); - QWebEngineUrlScheme::registerScheme(scheme); - } - - { - QWebEngineUrlScheme scheme(QBAL("PathSyntax-ViewSourceAllowed")); - scheme.setFlags(QWebEngineUrlScheme::ViewSourceAllowed); - QWebEngineUrlScheme::registerScheme(scheme); - } - - { - QWebEngineUrlScheme scheme(QBAL("HostSyntax")); - scheme.setSyntax(QWebEngineUrlScheme::Syntax::Host); - QWebEngineUrlScheme::registerScheme(scheme); - } - - { - QWebEngineUrlScheme scheme(QBAL("HostSyntax-ContentSecurityPolicyIgnored")); - scheme.setSyntax(QWebEngineUrlScheme::Syntax::Host); - scheme.setFlags(QWebEngineUrlScheme::ContentSecurityPolicyIgnored); - QWebEngineUrlScheme::registerScheme(scheme); - } - - { - QWebEngineUrlScheme scheme(QBAL("HostAndPortSyntax")); - scheme.setSyntax(QWebEngineUrlScheme::Syntax::HostAndPort); - scheme.setDefaultPort(42); - QWebEngineUrlScheme::registerScheme(scheme); - } - - { - QWebEngineUrlScheme scheme(QBAL("HostPortAndUserInformationSyntax")); - scheme.setSyntax(QWebEngineUrlScheme::Syntax::HostPortAndUserInformation); - scheme.setDefaultPort(42); - QWebEngineUrlScheme::registerScheme(scheme); - } - - { - QWebEngineUrlScheme scheme(QBAL("redirect1")); - scheme.setFlags(QWebEngineUrlScheme::CorsEnabled); - QWebEngineUrlScheme::registerScheme(scheme); - } - - { - QWebEngineUrlScheme scheme(QBAL("redirect2")); - scheme.setFlags(QWebEngineUrlScheme::CorsEnabled); - QWebEngineUrlScheme::registerScheme(scheme); - } -} -Q_CONSTRUCTOR_FUNCTION(registerSchemes) - -class TstUrlSchemeHandler final : public QWebEngineUrlSchemeHandler { - Q_OBJECT - -public: - TstUrlSchemeHandler(QWebEngineProfile *profile) - { - profile->installUrlSchemeHandler(QBAL("tst"), this); - - profile->installUrlSchemeHandler(QBAL("PathSyntax"), this); - profile->installUrlSchemeHandler(QBAL("PathSyntax-Secure"), this); - profile->installUrlSchemeHandler(QBAL("PathSyntax-Secure-ServiceWorkersAllowed"), this); - profile->installUrlSchemeHandler(QBAL("PathSyntax-Local"), this); - profile->installUrlSchemeHandler(QBAL("PathSyntax-LocalAccessAllowed"), this); - profile->installUrlSchemeHandler(QBAL("PathSyntax-NoAccessAllowed"), this); - profile->installUrlSchemeHandler(QBAL("PathSyntax-ServiceWorkersAllowed"), this); - profile->installUrlSchemeHandler(QBAL("PathSyntax-ViewSourceAllowed"), this); - profile->installUrlSchemeHandler(QBAL("HostSyntax"), this); - profile->installUrlSchemeHandler(QBAL("HostSyntax-ContentSecurityPolicyIgnored"), this); - profile->installUrlSchemeHandler(QBAL("HostAndPortSyntax"), this); - profile->installUrlSchemeHandler(QBAL("HostPortAndUserInformationSyntax"), this); - profile->installUrlSchemeHandler(QBAL("redirect1"), this); - profile->installUrlSchemeHandler(QBAL("redirect2"), this); - } - - QVector<QUrl> &requests() { return m_requests; } - -private: - void requestStarted(QWebEngineUrlRequestJob *job) override - { - QUrl url = job->requestUrl(); - m_requests << url; - - if (url.scheme() == QBAL("redirect1")) { - url.setScheme(QBAL("redirect2")); - job->redirect(url); - return; - } - - QString pathPrefix = QSL(THIS_DIR); - QString pathSuffix = url.path(); - QFile *file = new QFile(pathPrefix + pathSuffix, job); - if (!file->open(QIODevice::ReadOnly)) { - job->fail(QWebEngineUrlRequestJob::RequestFailed); - return; - } - QByteArray mimeType = QBAL("text/html"); - if (pathSuffix.endsWith(QSL(".js"))) - mimeType = QBAL("application/javascript"); - else if (pathSuffix.endsWith(QSL(".css"))) - mimeType = QBAL("text/css"); - job->reply(mimeType, file); - } - - QVector<QUrl> m_requests; -}; - -class tst_Origins final : public QObject { - Q_OBJECT - -private Q_SLOTS: - void initTestCase(); - void cleanup(); - void cleanupTestCase(); - - void jsUrlCanon(); - void jsUrlRelative(); - void jsUrlOrigin(); - void subdirWithAccess(); - void subdirWithoutAccess(); - void mixedSchemes(); - void mixedSchemesWithCsp(); - void mixedXHR(); -#if defined(WEBSOCKETS) - void webSocket(); -#endif - void dedicatedWorker(); - void sharedWorker(); - void serviceWorker(); - void viewSource(); - void createObjectURL(); - void redirect(); - -private: - bool load(const QUrl &url) - { - QSignalSpy spy(m_page, &QWebEnginePage::loadFinished); - m_page->load(url); - return (!spy.empty() || spy.wait(20000)) - && spy.front().value(0).toBool(); - } - - QVariant eval(const QString &code) - { - return evaluateJavaScriptSync(m_page, code); - } - - QWebEngineProfile m_profile; - QWebEnginePage *m_page = nullptr; - TstUrlSchemeHandler *m_handler = nullptr; -}; - -void tst_Origins::initTestCase() -{ - QTest::ignoreMessage( - QtWarningMsg, - QRegularExpression("Please register the custom scheme 'tst'.*")); - - m_page = new QWebEnginePage(&m_profile, nullptr); - m_handler = new TstUrlSchemeHandler(&m_profile); -} - -void tst_Origins::cleanup() -{ - m_handler->requests().clear(); -} - -void tst_Origins::cleanupTestCase() -{ - delete m_handler; - delete m_page; -} - -// Test URL parsing and canonicalization in Blink. The implementation of this -// part is mostly shared between Blink and Chromium proper. -void tst_Origins::jsUrlCanon() -{ - QVERIFY(load(QSL("about:blank"))); - - // Standard schemes are biased towards the authority part. - QCOMPARE(eval(QSL("new URL(\"http:foo/bar\").href")), QVariant(QSL("http://foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"http:/foo/bar\").href")), QVariant(QSL("http://foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"http://foo/bar\").href")), QVariant(QSL("http://foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"http:///foo/bar\").href")), QVariant(QSL("http://foo/bar"))); - - // The file scheme is however a (particularly) special case. -#ifdef Q_OS_WIN - QCOMPARE(eval(QSL("new URL(\"file:foo/bar\").href")), QVariant(QSL("file://foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"file:/foo/bar\").href")), QVariant(QSL("file://foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"file://foo/bar\").href")), QVariant(QSL("file://foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"file:///foo/bar\").href")), QVariant(QSL("file:///foo/bar"))); -#else - QCOMPARE(eval(QSL("new URL(\"file:foo/bar\").href")), QVariant(QSL("file:///foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"file:/foo/bar\").href")), QVariant(QSL("file:///foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"file://foo/bar\").href")), QVariant(QSL("file://foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"file:///foo/bar\").href")), QVariant(QSL("file:///foo/bar"))); -#endif - - // The qrc scheme is a PathSyntax scheme, having only a path and nothing else. - QCOMPARE(eval(QSL("new URL(\"qrc:foo/bar\").href")), QVariant(QSL("qrc:foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"qrc:/foo/bar\").href")), QVariant(QSL("qrc:/foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"qrc://foo/bar\").href")), QVariant(QSL("qrc://foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"qrc:///foo/bar\").href")), QVariant(QSL("qrc:///foo/bar"))); - - // Same for unregistered schemes. - QCOMPARE(eval(QSL("new URL(\"tst:foo/bar\").href")), QVariant(QSL("tst:foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"tst:/foo/bar\").href")), QVariant(QSL("tst:/foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"tst://foo/bar\").href")), QVariant(QSL("tst://foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"tst:///foo/bar\").href")), QVariant(QSL("tst:///foo/bar"))); - - // A HostSyntax scheme is like http without the port & user information. - QCOMPARE(eval(QSL("new URL(\"HostSyntax:foo/bar\").href")), QVariant(QSL("hostsyntax://foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"HostSyntax:foo:42/bar\").href")), QVariant(QSL("hostsyntax://foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"HostSyntax:a:b@foo/bar\").href")), QVariant(QSL("hostsyntax://foo/bar"))); - - // A HostAndPortSyntax scheme is like http without the user information. - QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo/bar\").href")), - QVariant(QSL("hostandportsyntax://foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:41/bar\").href")), - QVariant(QSL("hostandportsyntax://foo:41/bar"))); - QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:42/bar\").href")), - QVariant(QSL("hostandportsyntax://foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:a:b@foo/bar\").href")), - QVariant(QSL("hostandportsyntax://foo/bar"))); - - // A HostPortAndUserInformationSyntax scheme is exactly like http. - QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo/bar\").href")), - QVariant(QSL("hostportanduserinformationsyntax://foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:41/bar\").href")), - QVariant(QSL("hostportanduserinformationsyntax://foo:41/bar"))); - QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:42/bar\").href")), - QVariant(QSL("hostportanduserinformationsyntax://foo/bar"))); - QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:a:b@foo/bar\").href")), - QVariant(QSL("hostportanduserinformationsyntax://a:b@foo/bar"))); -} - -// Test relative URL resolution. -void tst_Origins::jsUrlRelative() -{ - QVERIFY(load(QSL("about:blank"))); - - // Schemes with hosts, like http, work as expected. - QCOMPARE(eval(QSL("new URL('bar', 'http://foo').href")), QVariant(QSL("http://foo/bar"))); - QCOMPARE(eval(QSL("new URL('baz', 'http://foo/bar').href")), QVariant(QSL("http://foo/baz"))); - QCOMPARE(eval(QSL("new URL('baz', 'http://foo/bar/').href")), QVariant(QSL("http://foo/bar/baz"))); - QCOMPARE(eval(QSL("new URL('/baz', 'http://foo/bar/').href")), QVariant(QSL("http://foo/baz"))); - QCOMPARE(eval(QSL("new URL('./baz', 'http://foo/bar/').href")), QVariant(QSL("http://foo/bar/baz"))); - QCOMPARE(eval(QSL("new URL('../baz', 'http://foo/bar/').href")), QVariant(QSL("http://foo/baz"))); - QCOMPARE(eval(QSL("new URL('../../baz', 'http://foo/bar/').href")), QVariant(QSL("http://foo/baz"))); - QCOMPARE(eval(QSL("new URL('//baz', 'http://foo/bar/').href")), QVariant(QSL("http://baz/"))); - - // In the case of schemes without hosts, relative URLs only work if the URL - // starts with a single slash -- and canonicalization does not guarantee - // this. The following cases all fail with TypeErrors. - QCOMPARE(eval(QSL("new URL('bar', 'tst:foo').href")), QVariant()); - QCOMPARE(eval(QSL("new URL('baz', 'tst:foo/bar').href")), QVariant()); - QCOMPARE(eval(QSL("new URL('bar', 'tst://foo').href")), QVariant()); - QCOMPARE(eval(QSL("new URL('bar', 'tst:///foo').href")), QVariant()); - - // However, registered custom schemes have been patched to allow relative - // URLs even without an initial slash. - QCOMPARE(eval(QSL("new URL('bar', 'qrc:foo').href")), QVariant(QSL("qrc:bar"))); - QCOMPARE(eval(QSL("new URL('baz', 'qrc:foo/bar').href")), QVariant(QSL("qrc:foo/baz"))); - QCOMPARE(eval(QSL("new URL('bar', 'qrc://foo').href")), QVariant()); - QCOMPARE(eval(QSL("new URL('bar', 'qrc:///foo').href")), QVariant()); - - // With a slash it works the same as http except 'foo' is part of the path and not the host. - QCOMPARE(eval(QSL("new URL('bar', 'qrc:/foo').href")), QVariant(QSL("qrc:/bar"))); - QCOMPARE(eval(QSL("new URL('bar', 'qrc:/foo/').href")), QVariant(QSL("qrc:/foo/bar"))); - QCOMPARE(eval(QSL("new URL('baz', 'qrc:/foo/bar').href")), QVariant(QSL("qrc:/foo/baz"))); - QCOMPARE(eval(QSL("new URL('baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/foo/bar/baz"))); - QCOMPARE(eval(QSL("new URL('/baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/baz"))); - QCOMPARE(eval(QSL("new URL('./baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/foo/bar/baz"))); - QCOMPARE(eval(QSL("new URL('../baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/foo/baz"))); - QCOMPARE(eval(QSL("new URL('../../baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/baz"))); - QCOMPARE(eval(QSL("new URL('../../../baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc:/baz"))); - - // If the relative URL begins with >= 2 slashes, then the scheme is treated - // not as a Syntax::Path scheme but as a Syntax::HostPortAndUserInformation - // scheme. - QCOMPARE(eval(QSL("new URL('//baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc://baz/"))); - QCOMPARE(eval(QSL("new URL('///baz', 'qrc:/foo/bar/').href")), QVariant(QSL("qrc://baz/"))); -} - -// Test origin serialization in Blink, implemented by blink::KURL and -// blink::SecurityOrigin as opposed to GURL and url::Origin. -void tst_Origins::jsUrlOrigin() -{ - QVERIFY(load(QSL("about:blank"))); - - // For network protocols the origin string must include the domain and port. - QCOMPARE(eval(QSL("new URL(\"http://foo.com/page.html\").origin")), QVariant(QSL("http://foo.com"))); - QCOMPARE(eval(QSL("new URL(\"https://foo.com/page.html\").origin")), QVariant(QSL("https://foo.com"))); - - // Even though file URL can also have domains, these are not included in the - // origin string by Chromium. The standard does not specify a value here, - // but suggests 'null' (https://url.spec.whatwg.org/#origin). - QCOMPARE(eval(QSL("new URL(\"file:/etc/passwd\").origin")), QVariant(QSL("file://"))); - QCOMPARE(eval(QSL("new URL(\"file://foo.com/etc/passwd\").origin")), QVariant(QSL("file://"))); - - // Unregistered schemes behave like file. - QCOMPARE(eval(QSL("new URL(\"tst:/banana\").origin")), QVariant(QSL("tst://"))); - QCOMPARE(eval(QSL("new URL(\"tst://foo.com/banana\").origin")), QVariant(QSL("tst://"))); - - // The non-PathSyntax schemes should have hosts and potentially ports. - QCOMPARE(eval(QSL("new URL(\"HostSyntax:foo:41/bar\").origin")), - QVariant(QSL("hostsyntax://foo"))); - QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:41/bar\").origin")), - QVariant(QSL("hostandportsyntax://foo:41"))); - QCOMPARE(eval(QSL("new URL(\"HostAndPortSyntax:foo:42/bar\").origin")), - QVariant(QSL("hostandportsyntax://foo"))); - QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:41/bar\").origin")), - QVariant(QSL("hostportanduserinformationsyntax://foo:41"))); - QCOMPARE(eval(QSL("new URL(\"HostPortAndUserInformationSyntax:foo:42/bar\").origin")), - QVariant(QSL("hostportanduserinformationsyntax://foo"))); - - // A PathSyntax scheme should have a 'universal' origin. - QCOMPARE(eval(QSL("new URL(\"PathSyntax:foo\").origin")), QVariant(QSL("pathsyntax:"))); - QCOMPARE(eval(QSL("new URL(\"qrc:/crysis.css\").origin")), QVariant(QSL("qrc:"))); - QCOMPARE(eval(QSL("new URL(\"qrc://foo.com/crysis.css\").origin")), QVariant(QSL("qrc:"))); - - // The NoAccessAllowed flag forces opaque origins. - QCOMPARE(eval(QSL("new URL(\"PathSyntax-NoAccessAllowed:foo\").origin")), - QVariant(QSL("null"))); -} - -class ScopedAttribute { -public: - ScopedAttribute(QWebEngineSettings *settings, QWebEngineSettings::WebAttribute attribute, bool newValue) - : m_settings(settings) - , m_attribute(attribute) - , m_oldValue(m_settings->testAttribute(m_attribute)) - { - m_settings->setAttribute(m_attribute, newValue); - } - ~ScopedAttribute() - { - m_settings->setAttribute(m_attribute, m_oldValue); - } -private: - QWebEngineSettings *m_settings; - QWebEngineSettings::WebAttribute m_attribute; - bool m_oldValue; -}; - -// Test same-origin policy of file, qrc and custom schemes. -// -// Note the test case involves the main page trying to load an iframe from a -// file that resides in a parent directory. This is just a small detail to -// demonstrate the difference with Firefox where such access is not allowed. -void tst_Origins::subdirWithAccess() -{ - ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, true); - - QVERIFY(load(QSL("file:" THIS_DIR "resources/subdir/index.html"))); - QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); - QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); - - QVERIFY(load(QSL("qrc:/resources/subdir/index.html"))); - QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); - QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); - - QVERIFY(load(QSL("tst:/resources/subdir/index.html"))); - QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); - QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); -} - -// In this variation the LocalContentCanAccessFileUrls attribute is disabled. As -// a result all file URLs will be considered to have unique/opaque origins, that -// is, they are not the 'same origin as' any other origin. -// -// Note that this applies only to file URLs and not qrc or custom schemes. -// -// See also (in Blink): -// - the allow_file_access_from_file_urls option and -// - the blink::SecurityOrigin::BlockLocalAccessFromLocalOrigin() method. -void tst_Origins::subdirWithoutAccess() -{ - ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, false); - - QVERIFY(load(QSL("file:" THIS_DIR "resources/subdir/index.html"))); - QCOMPARE(eval(QSL("msg[0]")), QVariant()); - QCOMPARE(eval(QSL("msg[1]")), QVariant()); - - QVERIFY(load(QSL("qrc:/resources/subdir/index.html"))); - QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); - QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); - - QVERIFY(load(QSL("tst:/resources/subdir/index.html"))); - QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); - QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); -} - -// Load the main page over one scheme with an iframe over another scheme. -// -// For file and qrc schemes, the iframe should load but it should not be -// possible for scripts in different frames to interact. -// -// Additionally for unregistered custom schemes and custom schemes without -// LocalAccessAllowed it should not be possible to load an iframe over the -// file: scheme. -void tst_Origins::mixedSchemes() -{ - QVERIFY(load(QSL("file:" THIS_DIR "resources/mixedSchemes.html"))); - eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - - QVERIFY(load(QSL("qrc:/resources/mixedSchemes.html"))); - eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - - QVERIFY(load(QSL("tst:/resources/mixedSchemes.html"))); - eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); - eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - - QVERIFY(load(QSL("PathSyntax:/resources/mixedSchemes.html"))); - eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); - eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - - QVERIFY(load(QSL("PathSyntax-LocalAccessAllowed:/resources/mixedSchemes.html"))); - eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - - QVERIFY(load(QSL("PathSyntax-NoAccessAllowed:/resources/mixedSchemes.html"))); - eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); - eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - - QVERIFY(load(QSL("HostSyntax://a/resources/mixedSchemes.html"))); - eval(QSL("setIFrameUrl('HostSyntax://a/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - eval(QSL("setIFrameUrl('HostSyntax://b/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); -} - -// Like mixedSchemes but adds a Content-Security-Policy: frame-src 'none' header. -void tst_Origins::mixedSchemesWithCsp() -{ - QVERIFY(load(QSL("HostSyntax://a/resources/mixedSchemesWithCsp.html"))); - eval(QSL("setIFrameUrl('HostSyntax://a/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - eval(QSL("setIFrameUrl('HostSyntax://b/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - - QVERIFY(load(QSL("HostSyntax-ContentSecurityPolicyIgnored://a/resources/mixedSchemesWithCsp.html"))); - eval(QSL("setIFrameUrl('HostSyntax-ContentSecurityPolicyIgnored://a/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - eval(QSL("setIFrameUrl('HostSyntax-ContentSecurityPolicyIgnored://b/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); -} - -// Load the main page over one scheme, then make an XMLHttpRequest to a -// different scheme. -// -// XMLHttpRequests can only be made to http, https, data, and chrome. -void tst_Origins::mixedXHR() -{ - QVERIFY(load(QSL("file:" THIS_DIR "resources/mixedXHR.html"))); - eval(QSL("sendXHR('file:" THIS_DIR "resources/mixedXHR.txt')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); - eval(QSL("sendXHR('qrc:/resources/mixedXHR.txt')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("error"))); - eval(QSL("sendXHR('tst:/resources/mixedXHR.txt')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("error"))); - eval(QSL("sendXHR('data:,ok')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); - - QVERIFY(load(QSL("qrc:/resources/mixedXHR.html"))); - eval(QSL("sendXHR('file:" THIS_DIR "resources/mixedXHR.txt')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); - eval(QSL("sendXHR('qrc:/resources/mixedXHR.txt')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); - eval(QSL("sendXHR('tst:/resources/mixedXHR.txt')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("error"))); - eval(QSL("sendXHR('data:,ok')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); - - QVERIFY(load(QSL("tst:/resources/mixedXHR.html"))); - eval(QSL("sendXHR('file:" THIS_DIR "resources/mixedXHR.txt')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("error"))); - eval(QSL("sendXHR('qrc:/resources/mixedXHR.txt')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("error"))); - eval(QSL("sendXHR('tst:/resources/mixedXHR.txt')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); - eval(QSL("sendXHR('data:,ok')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); -} - -#if defined(WEBSOCKETS) -class EchoServer : public QObject { - Q_OBJECT - Q_PROPERTY(QUrl url READ url NOTIFY urlChanged) -public: - EchoServer() : webSocketServer(QSL("EchoServer"), QWebSocketServer::NonSecureMode) - { - connect(&webSocketServer, &QWebSocketServer::newConnection, this, &EchoServer::onNewConnection); - } - - bool listen() - { - if (webSocketServer.listen(QHostAddress::Any)) { - Q_EMIT urlChanged(); - return true; - } - return false; - } - - QUrl url() const - { - return webSocketServer.serverUrl(); - } - -Q_SIGNALS: - void urlChanged(); - -private: - void onNewConnection() - { - QWebSocket *socket = webSocketServer.nextPendingConnection(); - connect(socket, &QWebSocket::textMessageReceived, this, &EchoServer::onTextMessageReceived); - connect(socket, &QWebSocket::disconnected, socket, &QObject::deleteLater); - } - - void onTextMessageReceived(const QString &message) - { - QWebSocket *socket = qobject_cast<QWebSocket *>(sender()); - socket->sendTextMessage(message); - } - - QWebSocketServer webSocketServer; -}; - -// Try opening a WebSocket from pages loaded over various URL schemes. -void tst_Origins::webSocket() -{ - EchoServer echoServer; - QWebChannel channel; - channel.registerObject(QSL("echoServer"), &echoServer); - m_page->setWebChannel(&channel); - QVERIFY(echoServer.listen()); - - QVERIFY(load(QSL("file:" THIS_DIR "resources/websocket.html"))); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); - - QVERIFY(load(QSL("qrc:/resources/websocket.html"))); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); - - // Unregistered schemes can also open WebSockets (since Chromium 71) - QVERIFY(load(QSL("tst:/resources/websocket.html"))); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); - - // Even an insecure registered scheme can open WebSockets. - QVERIFY(load(QSL("PathSyntax:/resources/websocket.html"))); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); -} -#endif -// Create a (Dedicated)Worker. Since dedicated workers can only be accessed from -// one page, there is not much need for security restrictions. -void tst_Origins::dedicatedWorker() -{ - QVERIFY(load(QSL("file:" THIS_DIR "resources/dedicatedWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QCOMPARE(eval(QSL("result")), QVariant(42)); - - QVERIFY(load(QSL("qrc:/resources/dedicatedWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QCOMPARE(eval(QSL("result")), QVariant(42)); - - // Unregistered schemes can also create Workers (since Chromium 71) - QVERIFY(load(QSL("tst:/resources/dedicatedWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QCOMPARE(eval(QSL("result")), QVariant(42)); - - // Even an insecure registered scheme can create Workers. - QVERIFY(load(QSL("PathSyntax:/resources/dedicatedWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QCOMPARE(eval(QSL("result")), QVariant(42)); - - // But not if the NoAccessAllowed flag is set. - QVERIFY(load(QSL("PathSyntax-NoAccessAllowed:/resources/dedicatedWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QVERIFY(eval(QSL("error")).toString() - .contains(QSL("cannot be accessed from origin 'null'"))); -} - -// Create a SharedWorker. Shared workers can be accessed from multiple pages, -// and therefore the same-origin policy applies. -void tst_Origins::sharedWorker() -{ - { - ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, false); - QVERIFY(load(QSL("file:" THIS_DIR "resources/sharedWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QVERIFY(eval(QSL("error")).toString() - .contains(QSL("cannot be accessed from origin 'null'"))); - } - - { - ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, true); - QVERIFY(load(QSL("file:" THIS_DIR "resources/sharedWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QCOMPARE(eval(QSL("result")), QVariant(42)); - } - - QVERIFY(load(QSL("qrc:/resources/sharedWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QCOMPARE(eval(QSL("result")), QVariant(42)); - - // Unregistered schemes should not create SharedWorkers. - - QVERIFY(load(QSL("PathSyntax:/resources/sharedWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QCOMPARE(eval(QSL("result")), QVariant(42)); - - QVERIFY(load(QSL("PathSyntax-NoAccessAllowed:/resources/sharedWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QVERIFY(eval(QSL("error")).toString() - .contains(QSL("denied to origin 'null'"))); -} - -// Service workers have to be explicitly enabled for a scheme. -void tst_Origins::serviceWorker() -{ - QVERIFY(load(QSL("file:" THIS_DIR "resources/serviceWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QVERIFY(eval(QSL("error")).toString() - .contains(QSL("The URL protocol of the current origin ('file://') is not supported."))); - - QVERIFY(load(QSL("qrc:/resources/serviceWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QVERIFY(eval(QSL("error")).toString() - .contains(QSL("The URL protocol of the current origin ('qrc:') is not supported."))); - - QVERIFY(load(QSL("tst:/resources/serviceWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QVERIFY(eval(QSL("error")).toString() - .contains(QSL("Cannot read property 'register' of undefined"))); - - QVERIFY(load(QSL("PathSyntax:/resources/serviceWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QVERIFY(eval(QSL("error")).toString() - .contains(QSL("Cannot read property 'register' of undefined"))); - - QVERIFY(load(QSL("PathSyntax-Secure:/resources/serviceWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QVERIFY(eval(QSL("error")).toString() - .contains(QSL("The URL protocol of the current origin ('pathsyntax-secure:') is not supported."))); - - QVERIFY(load(QSL("PathSyntax-ServiceWorkersAllowed:/resources/serviceWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QVERIFY(eval(QSL("error")).toString() - .contains(QSL("Cannot read property 'register' of undefined"))); - - QVERIFY(load(QSL("PathSyntax-Secure-ServiceWorkersAllowed:/resources/serviceWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QCOMPARE(eval(QSL("error")), QVariant()); - - QVERIFY(load(QSL("PathSyntax-NoAccessAllowed:/resources/serviceWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QVERIFY(eval(QSL("error")).toString() - .contains(QSL("Cannot read property 'register' of undefined"))); -} - -// Support for view-source must be enabled explicitly. -void tst_Origins::viewSource() -{ - QVERIFY(load(QSL("view-source:file:" THIS_DIR "resources/viewSource.html"))); -#ifdef Q_OS_WIN - QCOMPARE(m_page->requestedUrl().toString(), QSL("file:///" THIS_DIR "resources/viewSource.html")); -#else - QCOMPARE(m_page->requestedUrl().toString(), QSL("file://" THIS_DIR "resources/viewSource.html")); -#endif - - QVERIFY(load(QSL("view-source:qrc:/resources/viewSource.html"))); - QCOMPARE(m_page->requestedUrl().toString(), QSL("qrc:/resources/viewSource.html")); - - QVERIFY(load(QSL("view-source:tst:/resources/viewSource.html"))); - QCOMPARE(m_page->requestedUrl().toString(), QSL("about:blank")); - - QVERIFY(load(QSL("view-source:PathSyntax:/resources/viewSource.html"))); - QCOMPARE(m_page->requestedUrl().toString(), QSL("about:blank")); - - QVERIFY(load(QSL("view-source:PathSyntax-ViewSourceAllowed:/resources/viewSource.html"))); - QCOMPARE(m_page->requestedUrl().toString(), QSL("pathsyntax-viewsourceallowed:/resources/viewSource.html")); -} - -void tst_Origins::createObjectURL() -{ - // Legal for registered custom schemes. - QVERIFY(load(QSL("qrc:/resources/createObjectURL.html"))); - QVERIFY(eval(QSL("result")).toString().startsWith(QSL("blob:qrc:"))); - - // Also legal for unregistered schemes (since Chromium 71) - QVERIFY(load(QSL("tst:/resources/createObjectURL.html"))); - QVERIFY(eval(QSL("result")).toString().startsWith(QSL("blob:tst:"))); -} - -void tst_Origins::redirect() -{ - QVERIFY(load(QSL("redirect1:/resources/redirect.html"))); - QTRY_COMPARE(m_handler->requests().size(), 7); - QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("redirect1:/resources/redirect.html"))); - QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("redirect2:/resources/redirect.html"))); - QCOMPARE(m_handler->requests()[2], QUrl(QStringLiteral("redirect1:/resources/redirect.css"))); - QCOMPARE(m_handler->requests()[3], QUrl(QStringLiteral("redirect2:/resources/redirect.css"))); - QCOMPARE(m_handler->requests()[4], QUrl(QStringLiteral("redirect1:/resources/Akronim-Regular.woff2"))); - QCOMPARE(m_handler->requests()[5], QUrl(QStringLiteral("redirect1:/resources/Akronim-Regular.woff2"))); - QCOMPARE(m_handler->requests()[6], QUrl(QStringLiteral("redirect2:/resources/Akronim-Regular.woff2"))); -} - -QTEST_MAIN(tst_Origins) -#include "tst_origins.moc" diff --git a/tests/auto/widgets/origins/tst_origins.qrc b/tests/auto/widgets/origins/tst_origins.qrc deleted file mode 100644 index fcf54aaea..000000000 --- a/tests/auto/widgets/origins/tst_origins.qrc +++ /dev/null @@ -1,22 +0,0 @@ -<!DOCTYPE RCC> -<RCC version="1.0"> -<qresource> - <file>resources/createObjectURL.html</file> - <file>resources/dedicatedWorker.html</file> - <file>resources/dedicatedWorker.js</file> - <file>resources/mixedSchemes.html</file> - <file>resources/mixedSchemesWithCsp.html</file> - <file>resources/mixedSchemes_frame.html</file> - <file>resources/mixedXHR.html</file> - <file>resources/mixedXHR.txt</file> - <file>resources/serviceWorker.html</file> - <file>resources/serviceWorker.js</file> - <file>resources/sharedWorker.html</file> - <file>resources/sharedWorker.js</file> - <file>resources/subdir/frame2.html</file> - <file>resources/subdir/index.html</file> - <file>resources/subdir_frame1.html</file> - <file>resources/viewSource.html</file> - <file>resources/websocket.html</file> -</qresource> -</RCC> diff --git a/tests/auto/widgets/printing/CMakeLists.txt b/tests/auto/widgets/printing/CMakeLists.txt new file mode 100644 index 000000000..baa3cf747 --- /dev/null +++ b/tests/auto/widgets/printing/CMakeLists.txt @@ -0,0 +1,34 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../util/util.cmake) + +find_package(PkgConfig) +if(PkgConfig_FOUND) + pkg_check_modules(POPPLER_CPP poppler-cpp IMPORTED_TARGET) +endif() + +qt_internal_add_test(tst_printing + SOURCES + tst_printing.cpp + LIBRARIES + Qt::CorePrivate + Qt::WebEngineWidgets + Qt::WebEngineCorePrivate + Test::Util +) + +qt_internal_extend_target(tst_printing + CONDITION POPPLER_CPP_FOUND AND QT_FEATURE_webengine_system_poppler + LIBRARIES + PkgConfig::POPPLER_CPP +) + +set(tst_printing_resource_files + "resources/basic_printing_page.html" +) + +qt_internal_add_resource(tst_printing "tst_printing" + PREFIX "/" + FILES ${tst_printing_resource_files} +) diff --git a/tests/auto/widgets/printing/printing.pro b/tests/auto/widgets/printing/printing.pro deleted file mode 100644 index 92f5d611c..000000000 --- a/tests/auto/widgets/printing/printing.pro +++ /dev/null @@ -1,10 +0,0 @@ -include($$QTWEBENGINE_OUT_ROOT/src/core/qtwebenginecore-config.pri) # workaround for QTBUG-68093 -QT_FOR_CONFIG += webenginecore-private - -include(../tests.pri) -QT *= core-private webenginecore-private - -qtConfig(webengine-poppler-cpp) { - CONFIG += link_pkgconfig - PKGCONFIG += poppler-cpp -} diff --git a/tests/auto/widgets/printing/tst_printing.cpp b/tests/auto/widgets/printing/tst_printing.cpp index 380fb65ff..605fb57b5 100644 --- a/tests/auto/widgets/printing/tst_printing.cpp +++ b/tests/auto/widgets/printing/tst_printing.cpp @@ -1,39 +1,16 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <QtWebEngineCore/private/qtwebenginecoreglobal_p.h> -#include <QWebEnginePage> +#include <QtWebEngineCore/qtwebenginecore-config.h> +#include <QWebEngineSettings> +#include <QWebEngineView> #include <QTemporaryDir> #include <QTest> #include <QSignalSpy> #include <util.h> -#if QT_CONFIG(webengine_poppler_cpp) +#if QT_CONFIG(webengine_system_poppler) #include <poppler-document.h> #include <poppler-page.h> #endif @@ -44,25 +21,27 @@ class tst_Printing : public QObject private slots: void printToPdfBasic(); void printRequest(); -#if QT_CONFIG(webengine_poppler_cpp) && defined(Q_OS_LINUX) && defined(__GLIBCXX__) +#if QT_CONFIG(webengine_system_poppler) void printToPdfPoppler(); + void printFromPdfViewer(); #endif + void interruptPrinting(); }; void tst_Printing::printToPdfBasic() { QTemporaryDir tempDir(QDir::tempPath() + "/tst_qwebengineview-XXXXXX"); QVERIFY(tempDir.isValid()); - QWebEnginePage page; - QSignalSpy spy(&page, &QWebEnginePage::loadFinished); - page.load(QUrl("qrc:///resources/basic_printing_page.html")); - QTRY_VERIFY(spy.count() == 1); + QWebEngineView view; + QSignalSpy spy(&view, &QWebEngineView::loadFinished); + view.load(QUrl("qrc:///resources/basic_printing_page.html")); + QTRY_VERIFY(spy.size() == 1); - QSignalSpy savePdfSpy(&page, &QWebEnginePage::pdfPrintingFinished); + QSignalSpy savePdfSpy(view.page(), &QWebEnginePage::pdfPrintingFinished); QPageLayout layout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(0.0, 0.0, 0.0, 0.0)); QString path = tempDir.path() + "/print_1_success.pdf"; - page.printToPdf(path, layout); - QTRY_VERIFY2(savePdfSpy.count() == 1, "Printing to PDF file failed without signal"); + view.page()->printToPdf(path, layout); + QTRY_VERIFY2(savePdfSpy.size() == 1, "Printing to PDF file failed without signal"); QList<QVariant> successArguments = savePdfSpy.takeFirst(); QVERIFY2(successArguments.at(0).toString() == path, "File path for first saved PDF does not match arguments"); @@ -73,56 +52,58 @@ void tst_Printing::printToPdfBasic() #else path = tempDir.path() + "/print_|2_failed.pdf"; #endif - page.printToPdf(path, QPageLayout()); - QTRY_VERIFY2(savePdfSpy.count() == 1, "Printing to PDF file failed without signal"); + view.page()->printToPdf(path, QPageLayout()); + QTRY_VERIFY2(savePdfSpy.size() == 1, "Printing to PDF file failed without signal"); QList<QVariant> failedArguments = savePdfSpy.takeFirst(); QVERIFY2(failedArguments.at(0).toString() == path, "File path for second saved PDF does not match arguments"); QVERIFY2(failedArguments.at(1).toBool() == false, "Printing to PDF file succeeded though it should fail"); CallbackSpy<QByteArray> successfulSpy; - page.printToPdf(successfulSpy.ref(), layout); - QVERIFY(successfulSpy.waitForResult().length() > 0); + view.page()->printToPdf(successfulSpy.ref(), layout); + QVERIFY(successfulSpy.waitForResult().size() > 0); CallbackSpy<QByteArray> failedInvalidLayoutSpy; - page.printToPdf(failedInvalidLayoutSpy.ref(), QPageLayout()); - QCOMPARE(failedInvalidLayoutSpy.waitForResult().length(), 0); + view.page()->printToPdf(failedInvalidLayoutSpy.ref(), QPageLayout()); + QCOMPARE(failedInvalidLayoutSpy.waitForResult().size(), 0); } void tst_Printing::printRequest() { - QWebEnginePage webPage; + QWebEngineView view; QPageLayout layout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(0.0, 0.0, 0.0, 0.0)); - QSignalSpy loadFinishedSpy(&webPage, &QWebEnginePage::loadFinished); - QSignalSpy printRequestedSpy(&webPage, &QWebEnginePage::printRequested); - QSignalSpy savePdfSpy(&webPage, &QWebEnginePage::pdfPrintingFinished); + QSignalSpy loadFinishedSpy(&view, &QWebEngineView::loadFinished); + QSignalSpy printRequestedSpy(&view, &QWebEngineView::printRequested); + QSignalSpy printRequestedSpy2(view.page(), &QWebEnginePage::printRequested); + QSignalSpy savePdfSpy(&view, &QWebEngineView::pdfPrintingFinished); CallbackSpy<QByteArray> resultSpy; - webPage.load(QUrl("qrc:///resources/basic_printing_page.html")); - QTRY_VERIFY(loadFinishedSpy.count() == 1); - webPage.runJavaScript("window.print()"); - QTRY_VERIFY(printRequestedSpy.count() == 1); + view.load(QUrl("qrc:///resources/basic_printing_page.html")); + QTRY_VERIFY(loadFinishedSpy.size() == 1); + view.page()->runJavaScript("window.print()"); + QTRY_VERIFY(printRequestedSpy.size() == 1); + QVERIFY(printRequestedSpy2.size() == 1); //check if printing still works - webPage.printToPdf(resultSpy.ref(), layout); + view.printToPdf(resultSpy.ref(), layout); const QByteArray data = resultSpy.waitForResult(); - QVERIFY(data.length() > 0); + QVERIFY(data.size() > 0); } -#if QT_CONFIG(webengine_poppler_cpp) && defined(Q_OS_LINUX) && defined(__GLIBCXX__) +#if QT_CONFIG(webengine_system_poppler) void tst_Printing::printToPdfPoppler() { // check if generated pdf is correct by searching for a know string on the page using namespace poppler; - QWebEnginePage webPage; + QWebEngineView view; QPageLayout layout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(0.0, 0.0, 0.0, 0.0)); - QSignalSpy spy(&webPage, &QWebEnginePage::loadFinished); - QSignalSpy savePdfSpy(&webPage, &QWebEnginePage::pdfPrintingFinished); + QSignalSpy spy(&view, &QWebEngineView::loadFinished); + QSignalSpy savePdfSpy(&view, &QWebEngineView::pdfPrintingFinished); CallbackSpy<QByteArray> resultSpy; - webPage.load(QUrl("qrc:///resources/basic_printing_page.html")); + view.load(QUrl("qrc:///resources/basic_printing_page.html")); QTRY_VERIFY(spy.count() == 1); - webPage.printToPdf(resultSpy.ref(), layout); + view.printToPdf(resultSpy.ref(), layout); const QByteArray data = resultSpy.waitForResult(); QVERIFY(data.length() > 0); @@ -137,8 +118,65 @@ void tst_Printing::printToPdfPoppler() QVERIFY2(pdfPage->search(ustring::from_latin1("Hello Paper World"), rect, page::search_from_top, case_sensitive ), "Could not find text"); } + +void tst_Printing::printFromPdfViewer() +{ + using namespace poppler; + + QWebEngineView view; + view.page()->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); + view.page()->settings()->setAttribute(QWebEngineSettings::PdfViewerEnabled, true); + + // Load a basic HTML + QSignalSpy spy(&view, &QWebEngineView::loadFinished); + view.load(QUrl("qrc:///resources/basic_printing_page.html")); + QTRY_COMPARE(spy.size(), 1); + + // Create a PDF + QTemporaryDir tempDir(QDir::tempPath() + "/tst_printing-XXXXXX"); + QVERIFY(tempDir.isValid()); + QString path = tempDir.path() + "/basic_page.pdf"; + QSignalSpy savePdfSpy(view.page(), &QWebEnginePage::pdfPrintingFinished); + view.page()->printToPdf(path); + QTRY_COMPARE(savePdfSpy.size(), 1); + + // Open the new file with the PDF viewer plugin + view.load(QUrl("file://" + path)); + QTRY_COMPARE(spy.size(), 2); + + // Print from the plugin + // loadFinished signal is not reliable when loading a PDF file, because it has multiple phases. + // Workaround: Try to print it a couple of times until the result matches the expected. + CallbackSpy<QByteArray> resultSpy; + bool ok = QTest::qWaitFor([&]() -> bool { + view.printToPdf(resultSpy.ref()); + QByteArray data = resultSpy.waitForResult(); + + // Check if the result contains text from the original basic HTML + // This catches all the typical issues: empty result or printing the WebUI without PDF content. + QScopedPointer<document> pdf(document::load_from_raw_data(data.constData(), data.length())); + QScopedPointer<page> pdfPage(pdf->create_page(0)); + rectf rect; + return pdfPage->search(ustring::from_latin1("Hello Paper World"), rect, page::search_from_top, + case_sensitive); + }, 10000); + QVERIFY(ok); +} #endif +void tst_Printing::interruptPrinting() +{ + QWebEngineView view; + QSignalSpy spy(&view, &QWebEngineView::loadFinished); + view.load(QUrl("qrc:///resources/basic_printing_page.html")); + QTRY_VERIFY(spy.size() == 1); + + QTemporaryDir tempDir(QDir::tempPath() + "/tst_qwebengineview-XXXXXX"); + QVERIFY(tempDir.isValid()); + view.page()->printToPdf(tempDir.path() + "/file.pdf"); + // Navigation stop interrupts print job, preferably do this without crash/assert + view.page()->triggerAction(QWebEnginePage::Stop); +} QTEST_MAIN(tst_Printing) #include "tst_printing.moc" diff --git a/tests/auto/widgets/printing/tst_printing.qrc b/tests/auto/widgets/printing/tst_printing.qrc deleted file mode 100644 index b1795ef8a..000000000 --- a/tests/auto/widgets/printing/tst_printing.qrc +++ /dev/null @@ -1,5 +0,0 @@ -<!DOCTYPE RCC><RCC version="1.0"> -<qresource> - <file>resources/basic_printing_page.html</file> -</qresource> -</RCC> diff --git a/tests/auto/widgets/proxy/CMakeLists.txt b/tests/auto/widgets/proxy/CMakeLists.txt new file mode 100644 index 000000000..95dc903ed --- /dev/null +++ b/tests/auto/widgets/proxy/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../httpserver/httpserver.cmake) + +qt_internal_add_test(tst_webengine_proxy + SOURCES + tst_proxy.cpp + LIBRARIES + Qt::WebEngineWidgets + Test::HttpServer +) diff --git a/tests/auto/widgets/proxy/proxy.pro b/tests/auto/widgets/proxy/proxy.pro deleted file mode 100644 index 802dfad05..000000000 --- a/tests/auto/widgets/proxy/proxy.pro +++ /dev/null @@ -1,9 +0,0 @@ -include(../tests.pri) -QT += core-private webengine webengine-private - -HEADERS += \ - proxy_server.h - -SOURCES += \ - proxy_server.cpp - diff --git a/tests/auto/widgets/proxy/proxy_server.cpp b/tests/auto/widgets/proxy/proxy_server.cpp deleted file mode 100644 index 55f014914..000000000 --- a/tests/auto/widgets/proxy/proxy_server.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "proxy_server.h" -#include <QDataStream> -#include <QTcpSocket> -#include <QDebug> - -ProxyServer::ProxyServer(QObject *parent) : QObject(parent) -{ - connect(&m_server, &QTcpServer::newConnection, this, &ProxyServer::handleNewConnection); -} - -void ProxyServer::setCredentials(const QByteArray &user, const QByteArray password) -{ - m_auth.append(user); - m_auth.append(QChar(':')); - m_auth.append(password); - m_auth = m_auth.toBase64(); -} - -bool ProxyServer::isListening() -{ - return m_server.isListening(); -} - -void ProxyServer::run() -{ - if (!m_server.listen(QHostAddress::LocalHost, 5555)) - qFatal("Could not start the test server"); -} - -void ProxyServer::handleNewConnection() -{ - // do one connection at the time - Q_ASSERT(m_data.isEmpty()); - QTcpSocket *socket = m_server.nextPendingConnection(); - Q_ASSERT(socket); - connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater); - connect(socket, &QAbstractSocket::readyRead, this, &ProxyServer::handleReadReady); -} - -void ProxyServer::handleReadReady() -{ - QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender()); - Q_ASSERT(socket); - - m_data.append(socket->readAll()); - - if (!m_data.endsWith("\r\n\r\n")) - return; - - if (!m_data.contains(QByteArrayLiteral("Proxy-Authorization: Basic"))) { - socket->write("HTTP/1.1 407 Proxy Authentication Required\nProxy-Authenticate: " - "Basic realm=\"Proxy requires authentication\"\r\n" - "content-length: 0\r\n" - "\r\n"); - return; - } - - if (m_data.contains(m_auth)) { - emit success(); - } - m_data.clear(); -} diff --git a/tests/auto/widgets/proxy/proxy_server.h b/tests/auto/widgets/proxy/proxy_server.h deleted file mode 100644 index cb7c30600..000000000 --- a/tests/auto/widgets/proxy/proxy_server.h +++ /dev/null @@ -1,59 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef PROXY_SERVER_H -#define PROXY_SERVER_H - -#include <QObject> -#include <QTcpServer> - -class ProxyServer : public QObject -{ - Q_OBJECT - -public: - explicit ProxyServer(QObject *parent = nullptr); - void setCredentials(const QByteArray &user, const QByteArray password); - bool isListening(); - -public slots: - void run(); - -private slots: - void handleNewConnection(); - void handleReadReady(); - -signals: - void success(); -private: - QByteArray m_data; - QTcpServer m_server; - QByteArray m_auth; -}; - -#endif // PROXY_SERVER_H diff --git a/tests/auto/widgets/proxy/tst_proxy.cpp b/tests/auto/widgets/proxy/tst_proxy.cpp index 5f5dec016..3dc72618c 100644 --- a/tests/auto/widgets/proxy/tst_proxy.cpp +++ b/tests/auto/widgets/proxy/tst_proxy.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "proxy_server.h" #include <QTest> @@ -32,6 +7,17 @@ #include <QNetworkProxy> #include <QWebEnginePage> #include <QWebEngineView> +#include <QWebEngineUrlRequestInterceptor> +#include <QWebEngineLoadingInfo> + +struct Interceptor : public QWebEngineUrlRequestInterceptor +{ + Interceptor(const QByteArray cookie):m_cookie(cookie){}; + void interceptRequest(QWebEngineUrlRequestInfo &info) override { + info.setHttpHeader(QByteArray("Cookie"), m_cookie); + }; + QByteArray m_cookie; +}; class tst_Proxy : public QObject { @@ -41,8 +27,11 @@ public: private slots: void proxyAuthentication(); + void forwardCookie(); + void invalidHostName(); }; + void tst_Proxy::proxyAuthentication() { QByteArray user(QByteArrayLiteral("test")); @@ -59,9 +48,42 @@ void tst_Proxy::proxyAuthentication() server.run(); QTRY_VERIFY2(server.isListening(), "Could not setup authentication server"); QWebEnginePage page; - QSignalSpy successSpy(&server, &ProxyServer::success); + QSignalSpy successSpy(&server, &ProxyServer::authenticationSuccess); + page.load(QUrl("http://www.qt.io")); + QTRY_VERIFY2(successSpy.size() > 0, "Could not get authentication token"); +} + +void tst_Proxy::forwardCookie() +{ + QNetworkProxy proxy; + proxy.setType(QNetworkProxy::HttpProxy); + proxy.setHostName("localhost"); + proxy.setPort(5555); + QNetworkProxy::setApplicationProxy(proxy); + ProxyServer server; + QByteArray cookie("foo=bar; sessionToken=123"); + server.setCookie(cookie); + server.run(); + QTRY_VERIFY2(server.isListening(), "Could not setup proxy server"); + Interceptor interceptor(cookie); + QWebEnginePage page; + page.setUrlRequestInterceptor(&interceptor); + QSignalSpy cookieSpy(&server, &ProxyServer::cookieMatch); + page.load(QUrl("http://www.qt.io")); + QTRY_VERIFY2(cookieSpy.size() > 0, "Could not get cookie"); +} + +// Crash test ( https://bugreports.qt.io/browse/QTBUG-113992 ) +void tst_Proxy::invalidHostName() +{ + QNetworkProxy proxy; + proxy.setType(QNetworkProxy::HttpProxy); + proxy.setHostName("999.0.0.0"); + QNetworkProxy::setApplicationProxy(proxy); + QWebEnginePage page; + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); page.load(QUrl("http://www.qt.io")); - QTRY_VERIFY2(successSpy.count() > 0, "Could not get authentication token"); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); } #include "tst_proxy.moc" diff --git a/tests/auto/widgets/proxypac/CMakeLists.txt b/tests/auto/widgets/proxypac/CMakeLists.txt new file mode 100644 index 000000000..f27160cb6 --- /dev/null +++ b/tests/auto/widgets/proxypac/CMakeLists.txt @@ -0,0 +1,64 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../httpserver/httpserver.cmake) + +qt_internal_add_test(tst_proxypac_file + SOURCES + tst_proxypac.cpp + LIBRARIES + Qt::WebEngineCore + Test::HttpServer +) + +if(WIN32) + get_filename_component(SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}" REALPATH) + set(fileEnvArg "--proxy-pac-url=\"file:///${SOURCE_DIR}/proxy.pac\"") +elseif(LINUX AND CMAKE_CROSSCOMPILING) + set(fileEnvArg "--single-process --no-sandbox --proxy-pac-url=\"file://${CMAKE_CURRENT_LIST_DIR}/proxy.pac\"") +else() + set(fileEnvArg "--proxy-pac-url=\"file://${CMAKE_CURRENT_LIST_DIR}/proxy.pac\"") +endif() + +set_tests_properties(tst_proxypac_file PROPERTIES + ENVIRONMENT QTWEBENGINE_CHROMIUM_FLAGS=${fileEnvArg} +) + +if(NOT (LINUX AND CMAKE_CROSSCOMPILING)) + set(fileEnvArg "--single-process ${fileEnvArg}") + + qt_internal_add_test(tst_proxypac_single_process + SOURCES + tst_proxypac.cpp + LIBRARIES + Qt::WebEngineCore + Test::HttpServer + ) + + set_tests_properties(tst_proxypac_single_process PROPERTIES + ENVIRONMENT QTWEBENGINE_CHROMIUM_FLAGS=${fileEnvArg} + ) +endif() + +qt_internal_add_test(tst_proxypac_qrc + SOURCES + tst_proxypac.cpp + LIBRARIES + Qt::WebEngineCore + Test::HttpServer +) + +if(LINUX AND CMAKE_CROSSCOMPILING) + set(qrcEnvArg "--single-process --no-sandbox --proxy-pac-url=\"qrc:///proxy.pac\"") +else() + set(qrcEnvArg "--proxy-pac-url=\"qrc:///proxy.pac\"") +endif() + +set_tests_properties(tst_proxypac_qrc PROPERTIES + ENVIRONMENT QTWEBENGINE_CHROMIUM_FLAGS=${qrcEnvArg} +) + +qt_internal_add_resource(tst_proxypac_qrc "proxypac" + PREFIX "/" + FILES "proxy.pac" +) diff --git a/tests/auto/widgets/proxypac/proxy.pac b/tests/auto/widgets/proxypac/proxy.pac index 1d29847b9..966c37ba5 100644 --- a/tests/auto/widgets/proxypac/proxy.pac +++ b/tests/auto/widgets/proxypac/proxy.pac @@ -2,6 +2,6 @@ function FindProxyForURL(url, host) { if (shExpMatch(host, "*.proxy1.com")) return "PROXY localhost:5551"; if (shExpMatch(host, "*.proxy2.com")) return "PROXY localhost:5552"; - return "PROXY proxy.url:8080"; + return "DIRECT"; } diff --git a/tests/auto/widgets/proxypac/proxypac.pro b/tests/auto/widgets/proxypac/proxypac.pro deleted file mode 100644 index 4dbcd9365..000000000 --- a/tests/auto/widgets/proxypac/proxypac.pro +++ /dev/null @@ -1,11 +0,0 @@ -include(../tests.pri) -QT += webengine -HEADERS += proxyserver.h -SOURCES += proxyserver.cpp - -proxy_pac.name = QTWEBENGINE_CHROMIUM_FLAGS -boot2qt:proxy_pac.value = "--single-process --no-sandbox --proxy-pac-url=file://$$PWD/proxy.pac" -else: proxy_pac.value = --proxy-pac-url="file://$$PWD/proxy.pac" - -QT_TOOL_ENV += proxy_pac - diff --git a/tests/auto/widgets/proxypac/proxyserver.cpp b/tests/auto/widgets/proxypac/proxyserver.cpp index 4d38c87c9..f7a859747 100644 --- a/tests/auto/widgets/proxypac/proxyserver.cpp +++ b/tests/auto/widgets/proxypac/proxyserver.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "proxyserver.h" #include <QDataStream> diff --git a/tests/auto/widgets/proxypac/proxyserver.h b/tests/auto/widgets/proxypac/proxyserver.h index ea68286a2..c95856da9 100644 --- a/tests/auto/widgets/proxypac/proxyserver.h +++ b/tests/auto/widgets/proxypac/proxyserver.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef PROXY_SERVER_H #define PROXY_SERVER_H diff --git a/tests/auto/widgets/proxypac/tst_proxypac.cpp b/tests/auto/widgets/proxypac/tst_proxypac.cpp index f9340341b..43ccbf028 100644 --- a/tests/auto/widgets/proxypac/tst_proxypac.cpp +++ b/tests/auto/widgets/proxypac/tst_proxypac.cpp @@ -1,40 +1,13 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "qtwebengineglobal.h" -#include "proxyserver.h" +#include "proxy_server.h" #include <QTest> #include <QSignalSpy> #include <QWebEngineProfile> #include <QWebEnginePage> #include <QNetworkProxy> - class tst_ProxyPac : public QObject { Q_OBJECT public: @@ -46,26 +19,39 @@ private slots: void tst_ProxyPac::proxypac() { + const QString fromEnv = qEnvironmentVariable("QTWEBENGINE_CHROMIUM_FLAGS"); + if (!fromEnv.contains("--proxy-pac-url")) + qFatal("--proxy-pac-url argument is not passed. Use ctest or set QTWEBENGINE_CHROMIUM_FLAGS"); + ProxyServer proxyServer1; + QSignalSpy proxySpy1(&proxyServer1, &ProxyServer::requestReceived); proxyServer1.setPort(5551); proxyServer1.run(); - QSignalSpy proxySpy1(&proxyServer1, &ProxyServer::requestReceived); ProxyServer proxyServer2; + QSignalSpy proxySpy2(&proxyServer2, &ProxyServer::requestReceived); proxyServer2.setPort(5552); proxyServer2.run(); - QSignalSpy proxySpy2(&proxyServer2, &ProxyServer::requestReceived); QTRY_VERIFY2(proxyServer1.isListening(), "Could not setup proxy server 1"); QTRY_VERIFY2(proxyServer2.isListening(), "Could not setup proxy server 2"); QWebEngineProfile profile; QWebEnginePage page(&profile); + + const bool v8_proxy_resolver_enabled = !fromEnv.contains("--single-process"); page.load(QUrl("http://test.proxy1.com")); - QTRY_COMPARE(proxySpy1.count() >= 1, true); - QVERIFY(proxySpy2.count() == 0); + QTRY_COMPARE(proxySpy1.size() >= 1, v8_proxy_resolver_enabled); + QVERIFY(proxySpy2.size() == 0); page.load(QUrl("http://test.proxy2.com")); - QTRY_COMPARE(proxySpy2.count() >= 1 , true); + QTRY_COMPARE(proxySpy2.size() >= 1, v8_proxy_resolver_enabled); + + // check for crash + QSignalSpy spyFinished(&page, &QWebEnginePage::loadFinished); + page.load(QUrl("https://contribute.qt-project.org")); + + QTRY_VERIFY_WITH_TIMEOUT(!spyFinished.isEmpty(), 200000); + } #include "tst_proxypac.moc" diff --git a/tests/auto/widgets/qtbug_110287/CMakeLists.txt b/tests/auto/widgets/qtbug_110287/CMakeLists.txt new file mode 100644 index 000000000..ac7926dc0 --- /dev/null +++ b/tests/auto/widgets/qtbug_110287/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_test(tst_qtbug_110287 + SOURCES + tst_qtbug_110287.cpp + LIBRARIES + Qt::Network + Qt::WebEngineWidgets +) +target_link_options(tst_qtbug_110287 PRIVATE "-Wl,--as-needed") diff --git a/tests/auto/widgets/qtbug_110287/tst_qtbug_110287.cpp b/tests/auto/widgets/qtbug_110287/tst_qtbug_110287.cpp new file mode 100644 index 000000000..9453ae9b8 --- /dev/null +++ b/tests/auto/widgets/qtbug_110287/tst_qtbug_110287.cpp @@ -0,0 +1,41 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QNetworkAccessManager> +#include <QNetworkRequest> +#include <QSignalSpy> +#include <QTest> +#include <QWebEngineView> + +class tst_qtbug_110287 : public QObject +{ + Q_OBJECT +public: + tst_qtbug_110287() { } + +private slots: + void getAddrInfo(); +}; + +void tst_qtbug_110287::getAddrInfo() +{ + QNetworkAccessManager nam; + QSignalSpy namSpy(&nam, &QNetworkAccessManager::finished); + + QString address("http://www.example.com"); + QScopedPointer<QNetworkReply> reply(nam.get(QNetworkRequest(address))); + + if (!namSpy.wait(25000) || reply->error() != QNetworkReply::NoError) + QSKIP("Couldn't load page from network, skipping test."); + + QWebEngineView view; + QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); + + // load() will trigger system DNS resolution that uses getaddrinfo() + view.load(QUrl(address)); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size() > 0, true, 30000); + QTRY_COMPARE(loadFinishedSpy[0][0].toBool(), true); +} + +#include "tst_qtbug_110287.moc" +QTEST_MAIN(tst_qtbug_110287) diff --git a/tests/auto/widgets/qwebenginedownloaditem/qwebenginedownloaditem.pro b/tests/auto/widgets/qwebenginedownloaditem/qwebenginedownloaditem.pro deleted file mode 100644 index 18a66c466..000000000 --- a/tests/auto/widgets/qwebenginedownloaditem/qwebenginedownloaditem.pro +++ /dev/null @@ -1,3 +0,0 @@ -include(../tests.pri) -include(../../shared/http.pri) -QT *= core-private diff --git a/tests/auto/widgets/qwebenginedownloadrequest/CMakeLists.txt b/tests/auto/widgets/qwebenginedownloadrequest/CMakeLists.txt new file mode 100644 index 000000000..5b76909b1 --- /dev/null +++ b/tests/auto/widgets/qwebenginedownloadrequest/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../httpserver/httpserver.cmake) +include(../../util/util.cmake) + +qt_internal_add_test(tst_qwebenginedownloadrequest + SOURCES + tst_qwebenginedownloadrequest.cpp + LIBRARIES + Qt::WebEngineWidgets + Test::HttpServer + Test::Util +) diff --git a/tests/auto/widgets/qwebenginedownloaditem/tst_qwebenginedownloaditem.cpp b/tests/auto/widgets/qwebenginedownloadrequest/tst_qwebenginedownloadrequest.cpp index 6dc7f03c1..c81a27b3a 100644 --- a/tests/auto/widgets/qwebenginedownloaditem/tst_qwebenginedownloaditem.cpp +++ b/tests/auto/widgets/qwebenginedownloadrequest/tst_qwebenginedownloadrequest.cpp @@ -1,30 +1,7 @@ -/**************************************************************************** -** -** 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:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <util.h> #include <QCoreApplication> #include <QSignalSpy> @@ -32,14 +9,14 @@ #include <QTemporaryDir> #include <QTest> #include <QRegularExpression> -#include <QWebEngineDownloadItem> +#include <QWebEngineDownloadRequest> #include <QWebEnginePage> #include <QWebEngineProfile> #include <QWebEngineSettings> #include <QWebEngineView> #include <httpserver.h> -class tst_QWebEngineDownloadItem : public QObject +class tst_QWebEngineDownloadRequest : public QObject { Q_OBJECT @@ -77,50 +54,40 @@ private Q_SLOTS: void downloadToDefaultLocation(); void downloadToNonExistentDir(); void downloadToReadOnlyDir(); - void downloadPathValidation(); + void downloadToDirectoryWithFileName_data(); void downloadToDirectoryWithFileName(); + void downloadDataUrls_data(); + void downloadDataUrls(); private: void saveLink(QPoint linkPos); void clickLink(QPoint linkPos); void simulateUserAction(QPoint linkPos, UserAction action); - QWebEngineDownloadItem::DownloadType expectedDownloadType( - UserAction userAction, - const QByteArray &contentDisposition = QByteArray()); - HttpServer *m_server; QWebEngineProfile *m_profile; QWebEnginePage *m_page; QWebEngineView *m_view; - QSet<QWebEngineDownloadItem *> m_requestedDownloads; - QSet<QWebEngineDownloadItem *> m_finishedDownloads; -}; - -class ScopedConnection { -public: - ScopedConnection(QMetaObject::Connection connection) : m_connection(std::move(connection)) {} - ~ScopedConnection() { QObject::disconnect(m_connection); } -private: - QMetaObject::Connection m_connection; + QSet<QWebEngineDownloadRequest *> m_requestedDownloads; + QSet<QWebEngineDownloadRequest *> m_finishedDownloads; }; -Q_DECLARE_METATYPE(tst_QWebEngineDownloadItem::UserAction) -Q_DECLARE_METATYPE(tst_QWebEngineDownloadItem::FileAction) +Q_DECLARE_METATYPE(tst_QWebEngineDownloadRequest::UserAction) +Q_DECLARE_METATYPE(tst_QWebEngineDownloadRequest::FileAction) -void tst_QWebEngineDownloadItem::initTestCase() +void tst_QWebEngineDownloadRequest::initTestCase() { m_server = new HttpServer(); m_profile = new QWebEngineProfile; m_profile->setHttpCacheType(QWebEngineProfile::NoCache); m_profile->settings()->setAttribute(QWebEngineSettings::AutoLoadIconsForPage, false); - connect(m_profile, &QWebEngineProfile::downloadRequested, [this](QWebEngineDownloadItem *item) { + connect(m_profile, &QWebEngineProfile::downloadRequested, [this](QWebEngineDownloadRequest *item) { m_requestedDownloads.insert(item); - connect(item, &QWebEngineDownloadItem::destroyed, [this, item](){ + connect(item, &QWebEngineDownloadRequest::destroyed, [this, item](){ m_requestedDownloads.remove(item); m_finishedDownloads.remove(item); }); - connect(item, &QWebEngineDownloadItem::finished, [this, item](){ + connect(item, &QWebEngineDownloadRequest::isFinishedChanged, [this, item](){ m_finishedDownloads.insert(item); }); }); @@ -131,24 +98,24 @@ void tst_QWebEngineDownloadItem::initTestCase() m_view->show(); } -void tst_QWebEngineDownloadItem::init() +void tst_QWebEngineDownloadRequest::init() { QVERIFY(m_server->start()); } -void tst_QWebEngineDownloadItem::cleanup() +void tst_QWebEngineDownloadRequest::cleanup() { - for (QWebEngineDownloadItem *item : m_finishedDownloads) { + for (QWebEngineDownloadRequest *item : m_finishedDownloads) { item->deleteLater(); } - QTRY_COMPARE(m_requestedDownloads.count(), 0); - QCOMPARE(m_finishedDownloads.count(), 0); + QTRY_COMPARE(m_requestedDownloads.size(), 0); + QCOMPARE(m_finishedDownloads.size(), 0); QVERIFY(m_server->stop()); // Set download path to default. m_profile->setDownloadPath(""); } -void tst_QWebEngineDownloadItem::cleanupTestCase() +void tst_QWebEngineDownloadRequest::cleanupTestCase() { delete m_view; delete m_page; @@ -156,24 +123,27 @@ void tst_QWebEngineDownloadItem::cleanupTestCase() delete m_server; } -void tst_QWebEngineDownloadItem::saveLink(QPoint linkPos) +void tst_QWebEngineDownloadRequest::saveLink(QPoint linkPos) { // Simulate right-clicking on link and choosing "save link as" from menu. QSignalSpy menuSpy(m_view, &QWebEngineView::customContextMenuRequested); m_view->setContextMenuPolicy(Qt::CustomContextMenu); - auto event1 = new QContextMenuEvent(QContextMenuEvent::Mouse, linkPos); - auto event2 = new QMouseEvent(QEvent::MouseButtonPress, linkPos, Qt::RightButton, {}, {}); - auto event3 = new QMouseEvent(QEvent::MouseButtonRelease, linkPos, Qt::RightButton, {}, {}); + auto event1 = + new QContextMenuEvent(QContextMenuEvent::Mouse, linkPos, m_view->mapToGlobal(linkPos)); + auto event2 = new QMouseEvent(QEvent::MouseButtonPress, linkPos, m_view->mapToGlobal(linkPos), + Qt::RightButton, {}, {}); + auto event3 = new QMouseEvent(QEvent::MouseButtonRelease, linkPos, m_view->mapToGlobal(linkPos), + Qt::RightButton, {}, {}); QTRY_VERIFY(m_view->focusWidget()); QWidget *renderWidget = m_view->focusWidget(); QCoreApplication::postEvent(renderWidget, event1); QCoreApplication::postEvent(renderWidget, event2); QCoreApplication::postEvent(renderWidget, event3); - QTRY_COMPARE(menuSpy.count(), 1); + QTRY_COMPARE(menuSpy.size(), 1); m_page->triggerAction(QWebEnginePage::DownloadLinkToDisk); } -void tst_QWebEngineDownloadItem::clickLink(QPoint linkPos) +void tst_QWebEngineDownloadRequest::clickLink(QPoint linkPos) { // Simulate left-clicking on link. QTRY_VERIFY(m_view->focusWidget()); @@ -181,7 +151,7 @@ void tst_QWebEngineDownloadItem::clickLink(QPoint linkPos) QTest::mouseClick(renderWidget, Qt::LeftButton, {}, linkPos); } -void tst_QWebEngineDownloadItem::simulateUserAction(QPoint linkPos, UserAction action) +void tst_QWebEngineDownloadRequest::simulateUserAction(QPoint linkPos, UserAction action) { switch (action) { case SaveLink: return saveLink(linkPos); @@ -189,17 +159,7 @@ void tst_QWebEngineDownloadItem::simulateUserAction(QPoint linkPos, UserAction a } } -QWebEngineDownloadItem::DownloadType tst_QWebEngineDownloadItem::expectedDownloadType( - UserAction userAction, const QByteArray &contentDisposition) -{ - if (userAction == SaveLink) - return QWebEngineDownloadItem::UserRequested; - if (contentDisposition == QByteArrayLiteral("attachment")) - return QWebEngineDownloadItem::Attachment; - return QWebEngineDownloadItem::DownloadAttribute; -} - -void tst_QWebEngineDownloadItem::downloadLink_data() +void tst_QWebEngineDownloadRequest::downloadLink_data() { QTest::addColumn<UserAction>("userAction"); QTest::addColumn<bool>("anchorHasDownloadAttribute"); @@ -210,7 +170,6 @@ void tst_QWebEngineDownloadItem::downloadLink_data() QTest::addColumn<QByteArray>("fileDisposition"); QTest::addColumn<bool>("fileHasReferer"); QTest::addColumn<FileAction>("fileAction"); - QTest::addColumn<QWebEngineDownloadItem::DownloadType>("downloadType"); // SaveLink should always trigger a download, even for empty files. QTest::newRow("save link to empty file") @@ -218,8 +177,8 @@ void tst_QWebEngineDownloadItem::downloadLink_data() /* anchorHasDownloadAttribute */ << false /* fileName */ << QByteArrayLiteral("foo.txt") /* fileContents */ << QByteArrayLiteral("") - /* fileMimeTypeDeclared */ << QByteArrayLiteral("") - /* fileMimeTypeDetected */ << QByteArrayLiteral("") + /* fileMimeTypeDeclared */ << QByteArrayLiteral("text/plain") + /* fileMimeTypeDetected */ << QByteArrayLiteral("text/plain") /* fileDisposition */ << QByteArrayLiteral("") /* fileHasReferer */ << true /* fileAction */ << FileIsDownloaded; @@ -305,7 +264,7 @@ void tst_QWebEngineDownloadItem::downloadLink_data() /* fileMimeTypeDeclared */ << QByteArrayLiteral("text/plain") /* fileMimeTypeDetected */ << QByteArrayLiteral("text/plain") /* fileDisposition */ << QByteArrayLiteral("") - /* fileHasReferer */ << false // crbug.com/455987 + /* fileHasReferer */ << true /* fileAction */ << FileIsDownloaded; // ... same with the content disposition header save for the download type. @@ -329,7 +288,7 @@ void tst_QWebEngineDownloadItem::downloadLink_data() /* fileMimeTypeDeclared */ << QByteArrayLiteral("text/plain") /* fileMimeTypeDetected */ << QByteArrayLiteral("text/plain") /* fileDisposition */ << QByteArrayLiteral("attachment") - /* fileHasReferer */ << false // crbug.com/455987 + /* fileHasReferer */ << true /* fileAction */ << FileIsDownloaded; // The file's extension has no effect. @@ -396,7 +355,7 @@ void tst_QWebEngineDownloadItem::downloadLink_data() /* fileAction */ << FileIsDownloaded; } -void tst_QWebEngineDownloadItem::downloadLink() +void tst_QWebEngineDownloadRequest::downloadLink() { QFETCH(UserAction, userAction); QFETCH(bool, anchorHasDownloadAttribute); @@ -437,8 +396,7 @@ void tst_QWebEngineDownloadItem::downloadLink() rr->setResponseBody(fileContents); rr->sendResponse(); } else { - rr->setResponseStatus(404); - rr->sendResponse(); + rr->sendResponse(404); } }); @@ -454,31 +412,29 @@ void tst_QWebEngineDownloadItem::downloadLink() QUrl downloadUrl = m_server->url(slashFileName); int acceptedCount = 0; int finishedCount = 0; - ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { - QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested); + ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadRequest *item) { + QCOMPARE(item->state(), QWebEngineDownloadRequest::DownloadRequested); QCOMPARE(item->isFinished(), false); - QCOMPARE(item->totalBytes(), -1); + QCOMPARE(item->totalBytes(), fileContents.size()); QCOMPARE(item->receivedBytes(), 0); - QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); - QCOMPARE(item->type(), expectedDownloadType(userAction, fileDisposition)); + QCOMPARE(item->interruptReason(), QWebEngineDownloadRequest::NoReason); QCOMPARE(item->isSavePageDownload(), false); QCOMPARE(item->mimeType(), QString(fileMimeTypeDetected)); QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), suggestedPath); - QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat); + QCOMPARE(item->savePageFormat(), QWebEngineDownloadRequest::UnknownSaveFormat); QCOMPARE(item->url(), downloadUrl); QCOMPARE(item->page(), m_page); - connect(item, &QWebEngineDownloadItem::finished, [&, item]() { - QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); + connect(item, &QWebEngineDownloadRequest::isFinishedChanged, [&, item]() { + QCOMPARE(item->state(), QWebEngineDownloadRequest::DownloadCompleted); QCOMPARE(item->isFinished(), true); QCOMPARE(item->totalBytes(), fileContents.size()); QCOMPARE(item->receivedBytes(), fileContents.size()); - QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); - QCOMPARE(item->type(), expectedDownloadType(userAction, fileDisposition)); + QCOMPARE(item->interruptReason(), QWebEngineDownloadRequest::NoReason); QCOMPARE(item->isSavePageDownload(), false); QCOMPARE(item->mimeType(), QString(fileMimeTypeDetected)); QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), downloadPath); - QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat); + QCOMPARE(item->savePageFormat(), QWebEngineDownloadRequest::UnknownSaveFormat); QCOMPARE(item->url(), downloadUrl); QCOMPARE(item->page(), m_page); @@ -497,7 +453,7 @@ void tst_QWebEngineDownloadItem::downloadLink() // attribute or not. QSignalSpy loadSpy(m_page, &QWebEnginePage::loadFinished); m_view->load(m_server->url()); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true); QCOMPARE(indexRequestCount, 1); @@ -505,7 +461,7 @@ void tst_QWebEngineDownloadItem::downloadLink() // If file is expected to be displayed and not downloaded then end test if (fileAction == FileIsDisplayed) { - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true); QCOMPARE(acceptedCount, 0); return; @@ -524,7 +480,7 @@ void tst_QWebEngineDownloadItem::downloadLink() QCOMPARE(file.readAll(), fileContents); } -void tst_QWebEngineDownloadItem::downloadTwoLinks_data() +void tst_QWebEngineDownloadRequest::downloadTwoLinks_data() { QTest::addColumn<UserAction>("action1"); QTest::addColumn<UserAction>("action2"); @@ -534,7 +490,7 @@ void tst_QWebEngineDownloadItem::downloadTwoLinks_data() QTest::newRow("Click+Click") << ClickLink << ClickLink; } -void tst_QWebEngineDownloadItem::downloadTwoLinks() +void tst_QWebEngineDownloadRequest::downloadTwoLinks() { QFETCH(UserAction, action1); QFETCH(UserAction, action2); @@ -558,9 +514,6 @@ void tst_QWebEngineDownloadItem::downloadTwoLinks() rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); rr->setResponseBody(QByteArrayLiteral("file2")); rr->sendResponse(); - } else { - rr->setResponseStatus(404); - rr->sendResponse(); } }); @@ -570,29 +523,23 @@ void tst_QWebEngineDownloadItem::downloadTwoLinks() QString standardDir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); int acceptedCount = 0; int finishedCount = 0; - ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { - QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested); + ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadRequest *item) { + QCOMPARE(item->state(), QWebEngineDownloadRequest::DownloadRequested); QCOMPARE(item->isFinished(), false); - QCOMPARE(item->totalBytes(), -1); + QCOMPARE(item->totalBytes(), 5); // strlen("fileN") QCOMPARE(item->receivedBytes(), 0); - QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); - QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat); + QCOMPARE(item->interruptReason(), QWebEngineDownloadRequest::NoReason); + QCOMPARE(item->savePageFormat(), QWebEngineDownloadRequest::UnknownSaveFormat); QCOMPARE(item->mimeType(), QStringLiteral("text/plain")); QString filePart = QChar('/') + item->url().fileName(); QString fileName = item->url().fileName(); QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), standardDir + filePart); // type() is broken due to race condition in DownloadManagerDelegateQt - if (action1 == ClickLink && action2 == ClickLink) { - if (filePart == QStringLiteral("/file1")) - QCOMPARE(item->type(), expectedDownloadType(action1)); - else if (filePart == QStringLiteral("/file2")) - QCOMPARE(item->type(), expectedDownloadType(action2, QByteArrayLiteral("attachment"))); - else + if (action1 == ClickLink && action2 == ClickLink && filePart != QStringLiteral("/file1") && filePart != QStringLiteral("/file2")) QFAIL(qPrintable("Unexpected file name: " + filePart)); - } - connect(item, &QWebEngineDownloadItem::finished, [&]() { + connect(item, &QWebEngineDownloadRequest::isFinishedChanged, [&]() { finishedCount++; }); item->setDownloadDirectory(tmpDir.path()); @@ -604,7 +551,7 @@ void tst_QWebEngineDownloadItem::downloadTwoLinks() QSignalSpy loadSpy(m_page, &QWebEnginePage::loadFinished); m_view->load(m_server->url()); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true); // Trigger downloads @@ -618,17 +565,20 @@ void tst_QWebEngineDownloadItem::downloadTwoLinks() QTRY_COMPARE(finishedCount, 2); } -void tst_QWebEngineDownloadItem::downloadPage_data() +void tst_QWebEngineDownloadRequest::downloadPage_data() { - QTest::addColumn<QWebEngineDownloadItem::SavePageFormat>("savePageFormat"); - QTest::newRow("SingleHtmlSaveFormat") << QWebEngineDownloadItem::SingleHtmlSaveFormat; - QTest::newRow("CompleteHtmlSaveFormat") << QWebEngineDownloadItem::CompleteHtmlSaveFormat; - QTest::newRow("MimeHtmlSaveFormat") << QWebEngineDownloadItem::MimeHtmlSaveFormat; + QTest::addColumn<bool>("saveWithPageAction"); + QTest::addColumn<QWebEngineDownloadRequest::SavePageFormat>("savePageFormat"); + QTest::newRow("SingleHtmlSaveFormat") << false << QWebEngineDownloadRequest::SingleHtmlSaveFormat; + QTest::newRow("CompleteHtmlSaveFormat") << false << QWebEngineDownloadRequest::CompleteHtmlSaveFormat; + QTest::newRow("MimeHtmlSaveFormat") << false << QWebEngineDownloadRequest::MimeHtmlSaveFormat; + QTest::newRow("SavePageAction") << true << QWebEngineDownloadRequest::MimeHtmlSaveFormat; } -void tst_QWebEngineDownloadItem::downloadPage() +void tst_QWebEngineDownloadRequest::downloadPage() { - QFETCH(QWebEngineDownloadItem::SavePageFormat, savePageFormat); + QFETCH(bool, saveWithPageAction); + QFETCH(QWebEngineDownloadRequest::SavePageFormat, savePageFormat); // Set up HTTP server int indexRequestCount = 0; @@ -638,42 +588,45 @@ void tst_QWebEngineDownloadItem::downloadPage() rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html")); rr->setResponseBody(QByteArrayLiteral("<html><body>Hello</body></html>")); rr->sendResponse(); - } else { - rr->setResponseStatus(404); - rr->sendResponse(); } }); // Set up profile and download handler QTemporaryDir tmpDir; QVERIFY(tmpDir.isValid()); - QString downloadPath = tmpDir.path() + QStringLiteral("/test.html"); + QString downloadFileName("test.html"), downloadPath = tmpDir.filePath(downloadFileName); QUrl downloadUrl = m_server->url("/"); int acceptedCount = 0; int finishedCount = 0; - ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { - QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadInProgress); + ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadRequest *item) { + QCOMPARE(item->state(), saveWithPageAction ? QWebEngineDownloadRequest::DownloadRequested : QWebEngineDownloadRequest::DownloadInProgress); QCOMPARE(item->isFinished(), false); QCOMPARE(item->totalBytes(), -1); QCOMPARE(item->receivedBytes(), 0); - QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); - QCOMPARE(item->type(), QWebEngineDownloadItem::SavePage); + QCOMPARE(item->interruptReason(), QWebEngineDownloadRequest::NoReason); QCOMPARE(item->isSavePageDownload(), true); // FIXME(juvaldma): why is mimeType always the same? QCOMPARE(item->mimeType(), QStringLiteral("application/x-mimearchive")); - QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), downloadPath); QCOMPARE(item->savePageFormat(), savePageFormat); QCOMPARE(item->url(), downloadUrl); QCOMPARE(item->page(), m_page); - // no need to call item->accept() - connect(item, &QWebEngineDownloadItem::finished, [&, item]() { - QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); + if (saveWithPageAction) { + QVERIFY(!item->downloadDirectory().isEmpty()); + QVERIFY(!item->downloadFileName().isEmpty()); + item->setDownloadDirectory(tmpDir.path()); + item->setDownloadFileName(downloadFileName); + item->accept(); + } // save with explicit path accepts download automatically + + QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), downloadPath); + + connect(item, &QWebEngineDownloadRequest::isFinishedChanged, [&, item]() { + QCOMPARE(item->state(), QWebEngineDownloadRequest::DownloadCompleted); QCOMPARE(item->isFinished(), true); QCOMPARE(item->totalBytes(), item->receivedBytes()); QVERIFY(item->receivedBytes() > 0); - QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); - QCOMPARE(item->type(), QWebEngineDownloadItem::SavePage); + QCOMPARE(item->interruptReason(), QWebEngineDownloadRequest::NoReason); QCOMPARE(item->isSavePageDownload(), true); QCOMPARE(item->mimeType(), QStringLiteral("application/x-mimearchive")); QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), downloadPath); @@ -690,19 +643,23 @@ void tst_QWebEngineDownloadItem::downloadPage() // Load some HTML QSignalSpy loadSpy(m_page, &QWebEnginePage::loadFinished); m_page->load(m_server->url()); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true); QCOMPARE(indexRequestCount, 1); // Save some HTML - m_page->save(downloadPath, savePageFormat); + if (saveWithPageAction) + m_page->triggerAction(QWebEnginePage::SavePage); + else + m_page->save(downloadPath, savePageFormat); + QTRY_COMPARE(acceptedCount, 1); QTRY_COMPARE(finishedCount, 1); QFile file(downloadPath); QVERIFY(file.exists()); } -void tst_QWebEngineDownloadItem::downloadViaSetUrl() +void tst_QWebEngineDownloadRequest::downloadViaSetUrl() { // Reproduce the scenario described in QTBUG-63388 by triggering downloads // of the same file multiple times via QWebEnginePage::setUrl @@ -717,15 +674,12 @@ void tst_QWebEngineDownloadItem::downloadViaSetUrl() rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); rr->setResponseBody(QByteArrayLiteral("redacted")); rr->sendResponse(); - } else { - rr->setResponseStatus(404); - rr->sendResponse(); } }); // Set up profile and download handler - QVector<QUrl> downloadUrls; - ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { + QList<QUrl> downloadUrls; + ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadRequest *item) { downloadUrls.append(item->url()); }); @@ -734,8 +688,8 @@ void tst_QWebEngineDownloadItem::downloadViaSetUrl() QSignalSpy urlSpy(m_page, &QWebEnginePage::urlChanged); const QUrl indexUrl = m_server->url(); m_page->setUrl(indexUrl); - QTRY_COMPARE(loadSpy.count(), 1); - QTRY_COMPARE(urlSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); + QTRY_COMPARE(urlSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true); QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), indexUrl); @@ -745,9 +699,9 @@ void tst_QWebEngineDownloadItem::downloadViaSetUrl() for (int i = 0; i != 3; ++i) { m_page->setUrl(fileUrl); QCOMPARE(m_page->url(), fileUrl); - QTRY_COMPARE(loadSpy.count(), 1); - QTRY_COMPARE(urlSpy.count(), 2); - QTRY_COMPARE(downloadUrls.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); + QTRY_COMPARE(urlSpy.size(), 2); + QTRY_COMPARE(downloadUrls.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0).toBool(), false); QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), fileUrl); QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), indexUrl); @@ -756,20 +710,19 @@ void tst_QWebEngineDownloadItem::downloadViaSetUrl() } } -void tst_QWebEngineDownloadItem::downloadFileNot1() +void tst_QWebEngineDownloadRequest::downloadFileNot1() { // Trigger file download via download() but don't accept(). ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { - rr->setResponseStatus(404); - rr->sendResponse(); + rr->sendResponse(404); }); - QPointer<QWebEngineDownloadItem> downloadItem; + QPointer<QWebEngineDownloadRequest> downloadItem; int downloadCount = 0; - ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { + ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadRequest *item) { QVERIFY(item); - QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested); + QCOMPARE(item->state(), QWebEngineDownloadRequest::DownloadRequested); downloadItem = item; downloadCount++; }); @@ -779,20 +732,19 @@ void tst_QWebEngineDownloadItem::downloadFileNot1() QVERIFY(!downloadItem); } -void tst_QWebEngineDownloadItem::downloadFileNot2() +void tst_QWebEngineDownloadRequest::downloadFileNot2() { // Trigger file download via download() but call cancel() instead of accept(). ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { - rr->setResponseStatus(404); - rr->sendResponse(); + rr->sendResponse(404); }); - QPointer<QWebEngineDownloadItem> downloadItem; + QPointer<QWebEngineDownloadRequest> downloadItem; int downloadCount = 0; - ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { + ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadRequest *item) { QVERIFY(item); - QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested); + QCOMPARE(item->state(), QWebEngineDownloadRequest::DownloadRequested); item->cancel(); downloadItem = item; downloadCount++; @@ -801,46 +753,44 @@ void tst_QWebEngineDownloadItem::downloadFileNot2() m_page->download(m_server->url(QByteArrayLiteral("/file"))); QTRY_COMPARE(downloadCount, 1); QVERIFY(downloadItem); - QCOMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCancelled); + QCOMPARE(downloadItem->state(), QWebEngineDownloadRequest::DownloadCancelled); } -void tst_QWebEngineDownloadItem::downloadDeleted() +void tst_QWebEngineDownloadRequest::downloadDeleted() { - QPointer<QWebEngineDownloadItem> downloadItem; - m_server->setExpectError(true); - int downloadCount = 0; - int finishedCount = 0; - ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { + QPointer<QWebEngineDownloadRequest> downloadItem; + int downloadCount = 0, finishedCount = 0; + + ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadRequest *item) { QVERIFY(item); - QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested); + QCOMPARE(item->state(), QWebEngineDownloadRequest::DownloadRequested); downloadItem = item; - connect(downloadItem, &QWebEngineDownloadItem::finished, [&]() { - finishedCount++; - }); + connect(downloadItem, &QWebEngineDownloadRequest::isFinishedChanged, [&]() { ++finishedCount; }); + ++downloadCount; + // accept and schedule deletion, and check if it still finishes item->accept(); - downloadCount++; + item->deleteLater(); + QVERIFY(downloadItem); }); m_page->download(m_server->url(QByteArrayLiteral("/file"))); QTRY_COMPARE(downloadCount, 1); - QVERIFY(downloadItem); - QCOMPARE(finishedCount, 0); - downloadItem->deleteLater(); QTRY_COMPARE(finishedCount, 1); + QTRY_VERIFY(!downloadItem); + QCOMPARE(downloadCount, 1); + QCOMPARE(finishedCount, 1); } -void tst_QWebEngineDownloadItem::downloadDeletedByProfile() +void tst_QWebEngineDownloadRequest::downloadDeletedByProfile() { - m_server->setExpectError(true); - QPointer<QWebEngineProfile> profile(new QWebEngineProfile); profile->setHttpCacheType(QWebEngineProfile::NoCache); profile->settings()->setAttribute(QWebEngineSettings::AutoLoadIconsForPage, false); bool downloadFinished = false; - QPointer<QWebEngineDownloadItem> downloadItem; - connect(profile, &QWebEngineProfile::downloadRequested, [&] (QWebEngineDownloadItem *item) { - connect(item, &QWebEngineDownloadItem::finished, [&] () { + QPointer<QWebEngineDownloadRequest> downloadItem; + connect(profile, &QWebEngineProfile::downloadRequested, [&] (QWebEngineDownloadRequest *item) { + connect(item, &QWebEngineDownloadRequest::isFinishedChanged, [&] () { downloadFinished = true; }); downloadItem = item; @@ -860,7 +810,7 @@ void tst_QWebEngineDownloadItem::downloadDeletedByProfile() QTRY_COMPARE(downloadItem.isNull(), true); } -void tst_QWebEngineDownloadItem::downloadUniqueFilename_data() +void tst_QWebEngineDownloadRequest::downloadUniqueFilename_data() { QTest::addColumn<QString>("baseName"); QTest::addColumn<QString>("extension"); @@ -869,7 +819,7 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilename_data() QTest::newRow("tar.gz") << QString("test(1.test)") << QString("tar.gz"); } -void tst_QWebEngineDownloadItem::downloadUniqueFilename() +void tst_QWebEngineDownloadRequest::downloadUniqueFilename() { QFETCH(QString, baseName); QFETCH(QString, extension); @@ -884,28 +834,27 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilename() // Set up HTTP server ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { - if (rr->requestMethod() == "GET" && rr->requestPath() == ("/" + fileName)) { + auto requestPath = QString::fromUtf8(rr->requestPath()); + if (rr->requestMethod() == "GET" && requestPath == ("/" + fileName)) { rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("application/octet-stream")); rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); rr->setResponseBody(QByteArrayLiteral("a")); rr->sendResponse(); } else { - rr->setResponseStatus(404); - rr->sendResponse(); + rr->sendResponse(404); } }); // Set up profile and download handler - ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { + ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadRequest *item) { suggestedFileName = item->suggestedFileName(); item->accept(); - connect(item, &QWebEngineDownloadItem::finished, [&, item]() { - QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); + connect(item, &QWebEngineDownloadRequest::isFinishedChanged, [&, item]() { + QCOMPARE(item->state(), QWebEngineDownloadRequest::DownloadCompleted); QCOMPARE(item->isFinished(), true); QCOMPARE(item->totalBytes(), item->receivedBytes()); QVERIFY(item->receivedBytes() > 0); - QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); - QCOMPARE(item->type(), QWebEngineDownloadItem::Attachment); + QCOMPARE(item->interruptReason(), QWebEngineDownloadRequest::NoReason); QCOMPARE(item->isSavePageDownload(), false); downloadedFilePath = QDir(item->downloadDirectory()).filePath(item->downloadFileName()); downloadFinished = true; @@ -927,7 +876,7 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilename() } } -void tst_QWebEngineDownloadItem::downloadUniqueFilenameWithTimestamp() +void tst_QWebEngineDownloadRequest::downloadUniqueFilenameWithTimestamp() { // Set up HTTP server QString baseName("test(1.test)"); @@ -942,27 +891,27 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilenameWithTimestamp() m_profile->setDownloadPath(tmpDir.path()); ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { - if (rr->requestMethod() == "GET" && rr->requestPath() == ("/" + fileName)) { + auto requestPath = QString::fromUtf8(rr->requestPath()); + if (rr->requestMethod() == "GET" && requestPath == ("/" + fileName)) { rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("application/octet-stream")); rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); rr->setResponseBody(QByteArrayLiteral("a")); rr->sendResponse(); } else { - rr->setResponseStatus(404); - rr->sendResponse(); + rr->sendResponse(404); } }); // Set up profile and download handler - ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { + ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadRequest *item) { suggestedFileName = item->suggestedFileName(); item->accept(); - connect(item, &QWebEngineDownloadItem::finished, [&, item]() { - QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); + connect(item, &QWebEngineDownloadRequest::isFinishedChanged, [&, item]() { + QCOMPARE(item->state(), QWebEngineDownloadRequest::DownloadCompleted); QCOMPARE(item->isFinished(), true); QCOMPARE(item->totalBytes(), item->receivedBytes()); QVERIFY(item->receivedBytes() > 0); - QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); + QCOMPARE(item->interruptReason(), QWebEngineDownloadRequest::NoReason); QCOMPARE(item->page(), m_page); downloadFinished = true; downloadedFilePath = QDir(item->downloadDirectory()).filePath(item->downloadFileName()); @@ -998,13 +947,15 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilenameWithTimestamp() QRegularExpressionMatch match = fileNameCheck.match(downloadedFilePath); QVERIFY(match.hasMatch()); // ISO 8601 Date and time in UTC - QRegExp timestamp("^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9])([0-5][0-9])([0-5][0-9])([.][0-9]+)?(Z|[+-](?:2[0-3]|[01][0-9])[0-5][0-9])?$"); - QVERIFY(timestamp.exactMatch(match.captured(1))); + QRegularExpression timestamp("^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[" + "12][0-9])T(2[0-3]|[01][0-9])([0-5][0-9])([0-5][0-9])([.][0-9]" + "+)?(Z|[+-](?:2[0-3]|[01][0-9])[0-5][0-9])?$"); + QVERIFY(timestamp.match(match.captured(1)).hasMatch()); QCOMPARE(suggestedFileName, fileName); } } -void tst_QWebEngineDownloadItem::downloadToDefaultLocation() +void tst_QWebEngineDownloadRequest::downloadToDefaultLocation() { QTemporaryDir tmpDir; QVERIFY(tmpDir.isValid()); @@ -1021,7 +972,7 @@ void tst_QWebEngineDownloadItem::downloadToDefaultLocation() QCOMPARE(m_profile->downloadPath(), QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); } -void tst_QWebEngineDownloadItem::downloadToNonExistentDir() +void tst_QWebEngineDownloadRequest::downloadToNonExistentDir() { QString baseName("test(1.test)"); QString extension("txt"); @@ -1036,27 +987,27 @@ void tst_QWebEngineDownloadItem::downloadToNonExistentDir() // Set up HTTP server ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { - if (rr->requestMethod() == "GET" && rr->requestPath() == ("/" + fileName)) { + auto requestPath = QString::fromUtf8(rr->requestPath()); + if (rr->requestMethod() == "GET" && requestPath == ("/" + fileName)) { rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("application/octet-stream")); rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); rr->setResponseBody(QByteArrayLiteral("a")); rr->sendResponse(); } else { - rr->setResponseStatus(404); - rr->sendResponse(); + rr->sendResponse(404); } }); // Set up profile and download handler - ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { + ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadRequest *item) { suggestedFileName = item->suggestedFileName(); item->accept(); - connect(item, &QWebEngineDownloadItem::finished, [&, item]() { - QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); + connect(item, &QWebEngineDownloadRequest::isFinishedChanged, [&, item]() { + QCOMPARE(item->state(), QWebEngineDownloadRequest::DownloadCompleted); QCOMPARE(item->isFinished(), true); QCOMPARE(item->totalBytes(), item->receivedBytes()); QVERIFY(item->receivedBytes() > 0); - QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); + QCOMPARE(item->interruptReason(), QWebEngineDownloadRequest::NoReason); QCOMPARE(item->page(), m_page); downloadFinished = true; downloadedFilePath = QDir(item->downloadDirectory()).filePath(item->downloadFileName()); @@ -1074,7 +1025,7 @@ void tst_QWebEngineDownloadItem::downloadToNonExistentDir() QCOMPARE(suggestedFileName, fileName); } -void tst_QWebEngineDownloadItem::downloadToReadOnlyDir() +void tst_QWebEngineDownloadRequest::downloadToReadOnlyDir() { #ifdef Q_OS_WIN QSKIP("Cannot change file permissions on Windows."); @@ -1093,23 +1044,23 @@ void tst_QWebEngineDownloadItem::downloadToReadOnlyDir() // Set up HTTP server ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { - if (rr->requestMethod() == "GET" && rr->requestPath() == ("/" + fileName)) { + auto requestPath = QString::fromUtf8(rr->requestPath()); + if (rr->requestMethod() == "GET" && requestPath == ("/" + fileName)) { rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("application/octet-stream")); rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); rr->setResponseBody(QByteArrayLiteral("a")); rr->sendResponse(); } else { - rr->setResponseStatus(404); - rr->sendResponse(); + rr->sendResponse(404); } }); - QPointer<QWebEngineDownloadItem> downloadItem; - ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { + QPointer<QWebEngineDownloadRequest> downloadItem; + ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadRequest *item) { suggestedFileName = item->suggestedFileName(); downloadItem = item; item->accept(); - connect(item, &QWebEngineDownloadItem::finished, [&, item]() { + connect(item, &QWebEngineDownloadRequest::isFinishedChanged, [&]() { downloadFinished = true; }); downloadAccepted = true; @@ -1123,9 +1074,9 @@ void tst_QWebEngineDownloadItem::downloadToReadOnlyDir() QTRY_VERIFY(downloadAccepted); QVERIFY(downloadItem); - QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadInterrupted); + QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadRequest::DownloadInterrupted); QCOMPARE(downloadItem->isFinished(), false); - QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::FileAccessDenied); + QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadRequest::FileAccessDenied); QVERIFY(!QFile(downloadedFilePath).exists()); QCOMPARE(suggestedFileName, fileName); @@ -1135,125 +1086,17 @@ void tst_QWebEngineDownloadItem::downloadToReadOnlyDir() QFile(m_profile->downloadPath()).setPermissions(QFileDevice::WriteOwner); } -void tst_QWebEngineDownloadItem::downloadPathValidation() +void tst_QWebEngineDownloadRequest::downloadToDirectoryWithFileName_data() { - const QString fileName = "test.txt"; - QString downloadPath; - QString originalDownloadPath; + QTest::addColumn<bool>("setDirectoryFirst"); - QTemporaryDir tmpDir; - QVERIFY(tmpDir.isValid()); - m_profile->setDownloadPath(tmpDir.path()); - - // Set up HTTP server - ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { - if (rr->requestMethod() == "GET" && rr->requestPath() == ("/" + fileName)) { - rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("application/octet-stream")); - rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); - rr->setResponseBody(QByteArrayLiteral("a")); - rr->sendResponse(); - } else { - rr->setResponseStatus(404); - rr->sendResponse(); - } - }); - - // Set up profile and download handler - QPointer<QWebEngineDownloadItem> downloadItem; - ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { - downloadItem = item; - originalDownloadPath = item->path(); - - item->setPath(downloadPath); - item->accept(); - - connect(item, &QWebEngineDownloadItem::stateChanged, [&, item](QWebEngineDownloadItem::DownloadState downloadState) { - if (downloadState == QWebEngineDownloadItem::DownloadInterrupted) { - item->cancel(); - } - }); - - connect(item, &QWebEngineDownloadItem::finished, [&, item]() { - QCOMPARE(item->isFinished(), true); - QCOMPARE(item->totalBytes(), item->receivedBytes()); - QVERIFY(item->receivedBytes() > 0); - QCOMPARE(item->page(), m_page); - }); - }); - - QString oldPath = QDir::currentPath(); - QDir::setCurrent(tmpDir.path()); - - // Set only the file name. - downloadItem.clear(); - originalDownloadPath = ""; - downloadPath = fileName; - m_page->setUrl(m_server->url("/" + fileName)); - QTRY_VERIFY(downloadItem); - QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCompleted); - QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::NoReason); - QCOMPARE(downloadItem->path(), fileName); - - // Set only the directory path. - downloadItem.clear(); - originalDownloadPath = ""; - downloadPath = tmpDir.path(); - m_page->setUrl(m_server->url("/" + fileName)); - QTRY_VERIFY(downloadItem); - QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCompleted); - QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::NoReason); - QCOMPARE(downloadItem->path(), originalDownloadPath); - - // Set only the directory path with separator. - downloadItem.clear(); - originalDownloadPath = ""; - downloadPath = tmpDir.path() + QDir::separator(); - m_page->setUrl(m_server->url("/" + fileName)); - QTRY_VERIFY(downloadItem); - QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCompleted); - QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::NoReason); - QCOMPARE(downloadItem->path(), originalDownloadPath); - - // Set only the directory with the current directory path without ending separator. - downloadItem.clear(); - originalDownloadPath = ""; - downloadPath = "."; - m_page->setUrl(m_server->url("/" + fileName)); - QTRY_VERIFY(downloadItem); - QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCompleted); - QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::NoReason); - QCOMPARE(downloadItem->path(), originalDownloadPath); - - // Set only the directory with the current directory path with ending separator. - downloadItem.clear(); - originalDownloadPath = ""; - downloadPath = "./"; - m_page->setUrl(m_server->url("/" + fileName)); - QTRY_VERIFY(downloadItem); - QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCompleted); - QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::NoReason); - QCOMPARE(downloadItem->path(), originalDownloadPath); - - downloadItem.clear(); - originalDownloadPath = ""; - downloadPath = "..."; - m_page->setUrl(m_server->url("/" + fileName)); - QTRY_VERIFY(downloadItem); -#if !defined(Q_OS_WIN) - QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCancelled); - QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::FileFailed); - QCOMPARE(downloadItem->path(), downloadPath); -#else - // Windows interprets the "..." path as a valid path. It will be the current path. - QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCompleted); - QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::NoReason); - QCOMPARE(downloadItem->path(), originalDownloadPath); -#endif // !defined(Q_OS_WIN) - QDir::setCurrent(oldPath); + QTest::newRow("setDirectoryFirst") << true; + QTest::newRow("setFileNameFirst") << false; } -void tst_QWebEngineDownloadItem::downloadToDirectoryWithFileName() +void tst_QWebEngineDownloadRequest::downloadToDirectoryWithFileName() { + QFETCH(bool, setDirectoryFirst); QString downloadDirectory; QString downloadFileName; QString downloadedFilePath; @@ -1269,39 +1112,51 @@ void tst_QWebEngineDownloadItem::downloadToDirectoryWithFileName() // Set up HTTP server ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { - if (rr->requestMethod() == "GET" && rr->requestPath() == ("/" + fileName)) { + auto requestPath = QString::fromUtf8(rr->requestPath()); + if (rr->requestMethod() == "GET" && requestPath == ("/" + fileName)) { rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("application/octet-stream")); rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); rr->setResponseBody(QByteArrayLiteral("a")); rr->sendResponse(); } else { - rr->setResponseStatus(404); - rr->sendResponse(); + rr->sendResponse(404); } }); // Set up profile and download handler - ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { - - if (!downloadDirectory.isEmpty()) { + ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadRequest *item) { + QSignalSpy fileNameSpy(item, &QWebEngineDownloadRequest::downloadFileNameChanged); + QSignalSpy directorySpy(item, &QWebEngineDownloadRequest::downloadDirectoryChanged); + bool isUniquifiedFileName = false; + if (!downloadDirectory.isEmpty() && setDirectoryFirst) { + const QString &originalFileName = item->downloadFileName(); item->setDownloadDirectory(downloadDirectory); QCOMPARE(item->downloadDirectory(), downloadDirectory); + QCOMPARE(directorySpy.size(), 1); + isUniquifiedFileName = (originalFileName != item->downloadFileName()); + QCOMPARE(fileNameSpy.size(), isUniquifiedFileName ? 1 : 0); } if (!downloadFileName.isEmpty()) { item->setDownloadFileName(downloadFileName); QCOMPARE(item->downloadFileName(), downloadFileName); + QCOMPARE(fileNameSpy.size(), isUniquifiedFileName ? 2 : 1); + } + + if (!downloadDirectory.isEmpty() && !setDirectoryFirst) { + item->setDownloadDirectory(downloadDirectory); + QCOMPARE(item->downloadDirectory(), downloadDirectory); + QCOMPARE(directorySpy.size(), 1); } - QCOMPARE(item->path(), QDir(item->downloadDirectory()).filePath(item->downloadFileName())); item->accept(); - connect(item, &QWebEngineDownloadItem::finished, [&, item]() { - QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); + connect(item, &QWebEngineDownloadRequest::isFinishedChanged, [&, item]() { + QCOMPARE(item->state(), QWebEngineDownloadRequest::DownloadCompleted); QCOMPARE(item->isFinished(), true); QCOMPARE(item->totalBytes(), item->receivedBytes()); QVERIFY(item->receivedBytes() > 0); - QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); + QCOMPARE(item->interruptReason(), QWebEngineDownloadRequest::NoReason); QCOMPARE(item->page(), m_page); downloadFinished = true; downloadedFilePath = QDir(item->downloadDirectory()).filePath(item->downloadFileName()); @@ -1339,7 +1194,7 @@ void tst_QWebEngineDownloadItem::downloadToDirectoryWithFileName() QCOMPARE(downloadedSuggestedFileName, fileName); // Download another file to the same directory and set file name by - // QWebEngineDownloadItem::setDownloadDirectory() and setDownloadFileName() to avoid uniquification. + // QWebEngineDownloadRequest::setDownloadDirectory() and setDownloadFileName() to avoid uniquification. downloadFinished = false; downloadDirectory = m_profile->downloadPath() + QDir::separator() + "test1" + QDir::separator(); downloadFileName = "test1.txt"; @@ -1360,7 +1215,7 @@ void tst_QWebEngineDownloadItem::downloadToDirectoryWithFileName() QCOMPARE(downloadedSuggestedFileName, fileName); // Download the same file to same directory and set file name by - // QWebEngineDownloadItem::setDownloadDirectory() and setDownloadFileName() to avoid uniquification. + // QWebEngineDownloadRequest::setDownloadDirectory() and setDownloadFileName() to avoid uniquification. downloadFinished = false; downloadDirectory = m_profile->downloadPath() + QDir::separator() + "test2" + QDir::separator(); downloadFileName = "test1.txt"; @@ -1382,5 +1237,50 @@ void tst_QWebEngineDownloadItem::downloadToDirectoryWithFileName() QCOMPARE(downloadedSuggestedFileName, fileName); } -QTEST_MAIN(tst_QWebEngineDownloadItem) -#include "tst_qwebenginedownloaditem.moc" +void tst_QWebEngineDownloadRequest::downloadDataUrls_data() +{ + QTest::addColumn<QByteArray>("htmlData"); + QTest::addColumn<QString>("expectedFileName"); + QTest::newRow("data url without slash") << QByteArrayLiteral("<html><head><meta charset=\"utf-8\"></head><body><a href=\"data:application/gzip;base64,dGVzdA==\">data URL without slash</a><br/></body></html>") << QStringLiteral("qwe_download.gz") ; + QTest::newRow("data url with slash") << QByteArrayLiteral("<html><head><meta charset=\"utf-8\"></head><body><a href=\"data:application/gzip;base64,dGVzcnI/dGVzdA==\">data URL with filename</a><br/></body></html>") << QStringLiteral("qwe_download.gz") ; + QTest::newRow("data url with download tag") << QByteArrayLiteral("<html><head><meta charset=\"utf-8\"></head><body><a href=\"data:application/gzip;base64,dGVzdA/IHRlc3Q=\" download=\"filename.gz\">data URL with filename</a><br/></body></html>") << QStringLiteral("filename.gz") ; + +} + +void tst_QWebEngineDownloadRequest::downloadDataUrls() +{ + QFETCH(QByteArray, htmlData); + QFETCH(QString, expectedFileName); + // Set up HTTP server + ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { + if (rr->requestMethod() == "GET" && rr->requestPath() == "/") { + rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html")); + rr->setResponseBody(htmlData); + rr->sendResponse(); + } + }); + + // Set up profile and download handler + QTemporaryDir tmpDir; + QVERIFY(tmpDir.isValid()); + m_profile->setDownloadPath(tmpDir.path()); + + int downloadRequestCount = 0; + ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadRequest *item) { + QCOMPARE(item->state(), QWebEngineDownloadRequest::DownloadRequested); + QCOMPARE(item->downloadFileName(), expectedFileName); + downloadRequestCount++; + }); + + QSignalSpy loadSpy(m_page, &QWebEnginePage::loadFinished); + m_view->load(m_server->url()); + QTRY_COMPARE(loadSpy.size(), 1); + QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true); + + // Trigger download + simulateUserAction(QPoint(10, 10), UserAction::ClickLink); + QTRY_COMPARE(downloadRequestCount, 1); +} + +QTEST_MAIN(tst_QWebEngineDownloadRequest) +#include "tst_qwebenginedownloadrequest.moc" diff --git a/tests/auto/widgets/qwebenginehistory/CMakeLists.txt b/tests/auto/widgets/qwebenginehistory/CMakeLists.txt new file mode 100644 index 000000000..e277a7326 --- /dev/null +++ b/tests/auto/widgets/qwebenginehistory/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../util/util.cmake) + +qt_internal_add_test(tst_qwebenginehistory + SOURCES + tst_qwebenginehistory.cpp + LIBRARIES + Qt::WebEngineWidgets + Test::Util +) + +set(tst_qwebenginehistory_resource_files + "resources/page1.html" + "resources/page2.html" + "resources/page3.html" + "resources/page4.html" + "resources/page5.html" + "resources/page6.html" +) + +qt_internal_add_resource(tst_qwebenginehistory "tst_qwebenginehistory" + PREFIX + "/" + FILES + ${tst_qwebenginehistory_resource_files} +) + diff --git a/tests/auto/widgets/qwebenginehistory/qwebenginehistory.pro b/tests/auto/widgets/qwebenginehistory/qwebenginehistory.pro deleted file mode 100644 index e99c7f493..000000000 --- a/tests/auto/widgets/qwebenginehistory/qwebenginehistory.pro +++ /dev/null @@ -1 +0,0 @@ -include(../tests.pri) diff --git a/tests/auto/widgets/qwebenginehistory/resources/page5.html b/tests/auto/widgets/qwebenginehistory/resources/page5.html index 859355279..cad6d964e 100644 --- a/tests/auto/widgets/qwebenginehistory/resources/page5.html +++ b/tests/auto/widgets/qwebenginehistory/resources/page5.html @@ -1 +1,2 @@ +<link rel="icon" href="qrc:/qt-project.org/qmessagebox/images/qtlogo-64.png"> <title>page5</title><body><h1>page5</h1></body> diff --git a/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp b/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp index 6209401cb..ad66e972c 100644 --- a/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp +++ b/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp @@ -20,7 +20,7 @@ #include <QtTest/QtTest> #include <QAction> -#include "../util.h" +#include <util.h> #include "qwebenginepage.h" #include "qwebengineview.h" #include "qwebenginehistory.h" @@ -39,7 +39,7 @@ protected : { loadFinishedSpy->clear(); page->load(QUrl("qrc:/resources/page" + QString::number(nr) + ".html")); - QTRY_COMPARE(loadFinishedSpy->count(), 1); + QTRY_COMPARE(loadFinishedSpy->size(), 1); loadFinishedSpy->clear(); } @@ -51,6 +51,7 @@ public Q_SLOTS: private Q_SLOTS: void title(); void lastVisited(); + void iconUrl(); void count(); void back(); void forward(); @@ -126,6 +127,11 @@ void tst_QWebEngineHistory::lastVisited() QVERIFY(qAbs(hist->itemAt(0).lastVisited().secsTo(QDateTime::currentDateTime())) < 60); } +void tst_QWebEngineHistory::iconUrl() +{ + QTRY_COMPARE(hist->currentItem().iconUrl(), QUrl("qrc:/qt-project.org/qmessagebox/images/qtlogo-64.png")); +} + /** * Check QWebEngineHistory::count() method */ @@ -144,8 +150,8 @@ void tst_QWebEngineHistory::back() for (int i = histsize;i > 1;i--) { QTRY_COMPARE(toPlainTextSync(page), QString("page") + QString::number(i)); hist->back(); - QTRY_COMPARE(loadFinishedSpy->count(), histsize-i+1); - QTRY_COMPARE(titleChangedSpy.count(), histsize-i+1); + QTRY_COMPARE(loadFinishedSpy->size(), histsize-i+1); + QTRY_COMPARE(titleChangedSpy.size(), histsize-i+1); } //try one more time (too many). crash test hist->back(); @@ -162,15 +168,15 @@ void tst_QWebEngineHistory::forward() while (hist->canGoBack()) { hist->back(); histBackCount++; - QTRY_COMPARE(loadFinishedSpy->count(), histBackCount); + QTRY_COMPARE(loadFinishedSpy->size(), histBackCount); } QSignalSpy titleChangedSpy(page, SIGNAL(titleChanged(const QString&))); for (int i = 1;i < histsize;i++) { QTRY_COMPARE(toPlainTextSync(page), QString("page") + QString::number(i)); hist->forward(); - QTRY_COMPARE(loadFinishedSpy->count(), i+histBackCount); - QTRY_COMPARE(titleChangedSpy.count(), i); + QTRY_COMPARE(loadFinishedSpy->size(), i+histBackCount); + QTRY_COMPARE(titleChangedSpy.size(), i); } //try one more time (too many). crash test hist->forward(); @@ -199,15 +205,15 @@ void tst_QWebEngineHistory::goToItem() QWebEngineHistoryItem current = hist->currentItem(); hist->back(); - QTRY_COMPARE(loadFinishedSpy->count(), 1); + QTRY_COMPARE(loadFinishedSpy->size(), 1); hist->back(); - QTRY_COMPARE(loadFinishedSpy->count(), 2); + QTRY_COMPARE(loadFinishedSpy->size(), 2); QVERIFY(hist->currentItem().title() != current.title()); hist->goToItem(current); - QTRY_COMPARE(loadFinishedSpy->count(), 2); + QTRY_COMPARE(loadFinishedSpy->size(), 2); QTRY_COMPARE(hist->currentItem().title(), current.title()); } @@ -219,7 +225,7 @@ void tst_QWebEngineHistory::items() { QList<QWebEngineHistoryItem> items = hist->items(); //check count - QTRY_COMPARE(histsize, items.count()); + QTRY_COMPARE(histsize, items.size()); //check order for (int i = 1;i <= histsize;i++) { @@ -230,10 +236,10 @@ void tst_QWebEngineHistory::items() void tst_QWebEngineHistory::backForwardItems() { hist->back(); - QTRY_COMPARE(loadFinishedSpy->count(), 1); + QTRY_COMPARE(loadFinishedSpy->size(), 1); hist->back(); - QTRY_COMPARE(loadFinishedSpy->count(), 2); + QTRY_COMPARE(loadFinishedSpy->size(), 2); QTRY_COMPARE(hist->items().size(), 5); QTRY_COMPARE(hist->backItems(100).size(), 2); @@ -291,9 +297,9 @@ void tst_QWebEngineHistory::serialize_2() hist->back(); QTRY_VERIFY(evaluateJavaScriptSync(page, "location.hash").toString().isEmpty()); hist->back(); - QTRY_COMPARE(loadFinishedSpy->count(), 1); + QTRY_COMPARE(loadFinishedSpy->size(), 1); hist->back(); - QTRY_COMPARE(loadFinishedSpy->count(), 2); + QTRY_COMPARE(loadFinishedSpy->size(), 2); //check if current index was changed (make sure that it is not last item) QVERIFY(hist->currentItemIndex() != initialCurrentIndex); //save current index @@ -304,17 +310,18 @@ void tst_QWebEngineHistory::serialize_2() load >> *hist; QVERIFY(load.status() == QDataStream::Ok); // Restoring the history will trigger a load. - QTRY_COMPARE(loadFinishedSpy->count(), 3); + QTRY_COMPARE(loadFinishedSpy->size(), 3); //check current index QTRY_COMPARE(hist->currentItemIndex(), oldCurrentIndex); hist->forward(); - QTRY_COMPARE(loadFinishedSpy->count(), 4); + QTRY_COMPARE(loadFinishedSpy->size(), 4); hist->forward(); - QTRY_COMPARE(loadFinishedSpy->count(), 5); + QTRY_COMPARE(loadFinishedSpy->size(), 5); hist->forward(); - QTRY_COMPARE(loadFinishedSpy->count(), 6); + // In-page navigation, the last url was the page5.html + QTRY_COMPARE(loadFinishedSpy->size(), 5); QTRY_COMPARE(hist->currentItemIndex(), initialCurrentIndex); } @@ -336,6 +343,7 @@ void tst_QWebEngineHistory::serialize_3() QDateTime lastVisited(a.lastVisited()); QUrl originalUrl(a.originalUrl()); QUrl url(a.url()); + QUrl iconUrl(a.iconUrl()); save << *hist; QVERIFY(save.status() == QDataStream::Ok); @@ -351,6 +359,7 @@ void tst_QWebEngineHistory::serialize_3() QTRY_COMPARE(b.lastVisited(), lastVisited); QTRY_COMPARE(b.originalUrl(), originalUrl); QTRY_COMPARE(b.url(), url); + QTRY_COMPARE(b.iconUrl(), iconUrl); //Check if all data was read QVERIFY(load.atEnd()); @@ -420,7 +429,7 @@ void tst_QWebEngineHistory::saveAndRestore_crash_4() QSignalSpy loadFinishedSpy2(page2.data(), SIGNAL(loadFinished(bool))); QDataStream load(&buffer, QIODevice::ReadOnly); load >> *page2->history(); - QTRY_COMPARE(loadFinishedSpy2.count(), 1); + QTRY_COMPARE(loadFinishedSpy2.size(), 1); } void tst_QWebEngineHistory::saveAndRestore_InternalPage() @@ -459,7 +468,7 @@ void tst_QWebEngineHistory::popPushState() QWebEnginePage page; QSignalSpy spyLoadFinished(&page, SIGNAL(loadFinished(bool))); page.setHtml("<html><body>long live Qt!</body></html>"); - QTRY_COMPARE(spyLoadFinished.count(), 1); + QTRY_COMPARE(spyLoadFinished.size(), 1); evaluateJavaScriptSync(&page, script); } @@ -478,9 +487,9 @@ void tst_QWebEngineHistory::clear() QWebEnginePage page2(this); QWebEngineHistory* hist2 = page2.history(); - QVERIFY(hist2->count() == 0); + QCOMPARE(hist2->count(), 0); hist2->clear(); - QVERIFY(hist2->count() == 0); // Do not change anything. + QCOMPARE(hist2->count(), 0); // Do not change anything. } void tst_QWebEngineHistory::historyItemFromDeletedPage() diff --git a/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.qrc b/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.qrc deleted file mode 100644 index cdfe575a0..000000000 --- a/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.qrc +++ /dev/null @@ -1,10 +0,0 @@ -<!DOCTYPE RCC><RCC version="1.0"> -<qresource> - <file>resources/page1.html</file> - <file>resources/page2.html</file> - <file>resources/page3.html</file> - <file>resources/page4.html</file> - <file>resources/page5.html</file> - <file>resources/page6.html</file> -</qresource> -</RCC> diff --git a/tests/auto/widgets/qwebenginepage/BLACKLIST b/tests/auto/widgets/qwebenginepage/BLACKLIST index e6fad8b20..52def48d1 100644 --- a/tests/auto/widgets/qwebenginepage/BLACKLIST +++ b/tests/auto/widgets/qwebenginepage/BLACKLIST @@ -3,10 +3,13 @@ osx [mouseMovementProperties] windows +macos # Can't move cursor (QTBUG-76312) -[runJavaScriptFromSlot] -osx -linux +[comboBoxPopupPositionAfterMove] +macos -[fullScreenRequested] -windows +[comboBoxPopupPositionAfterChildMove] +macos + +[backgroundColor] +macos diff --git a/tests/auto/widgets/qwebenginepage/CMakeLists.txt b/tests/auto/widgets/qwebenginepage/CMakeLists.txt new file mode 100644 index 000000000..f63d6211c --- /dev/null +++ b/tests/auto/widgets/qwebenginepage/CMakeLists.txt @@ -0,0 +1,68 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../httpserver/httpserver.cmake) +include(../../util/util.cmake) + +qt_internal_add_test(tst_qwebenginepage + SOURCES + tst_qwebenginepage.cpp + LIBRARIES + Qt::CorePrivate + Qt::NetworkPrivate + Qt::WebEngineCorePrivate + Qt::WebEngineWidgets + Test::HttpServer + Test::Util +) + +get_target_property(sharedData Test::HttpServer SHARED_DATA) + +set(tst_qwebenginepage_resource_files + "resources/redirect.html" + "resources/bar.txt" + "resources/content.html" + "resources/dynamicFrame.html" + "resources/foo.txt" + "resources/fontaccess.html" + "resources/frame_a.html" + "resources/frame_c.html" + "resources/framedindex.html" + "resources/fullscreen.html" + "resources/iframe.html" + "resources/iframe2.html" + "resources/iframe3.html" + "resources/image.png" + "resources/index.html" + "resources/lifecycle.html" + "resources/pasteimage.html" + "resources/path with spaces.txt" + "resources/reload.html" + "resources/script.html" + "resources/style.css" + "resources/test1.html" + "resources/test2.html" + "resources/testiframe.html" + "resources/testiframe2.html" + "resources/user.css" +) + +qt_internal_add_resource(tst_qwebenginepage "tst_qwebenginepage" + PREFIX + "/" + FILES + ${tst_qwebenginepage_resource_files} +) +set_source_files_properties("${sharedData}/notification.html" + PROPERTIES QT_RESOURCE_ALIAS "notification.html" +) +set(tst_qwebenginepage1_resource_files + "${sharedData}/notification.html" +) + +qt_internal_add_resource(tst_qwebenginepage "tst_qwebenginepage1" + PREFIX + "/shared" + FILES + ${tst_qwebenginepage1_resource_files} +) diff --git a/tests/auto/widgets/qwebenginepage/qwebenginepage.pro b/tests/auto/widgets/qwebenginepage/qwebenginepage.pro deleted file mode 100644 index 18a66c466..000000000 --- a/tests/auto/widgets/qwebenginepage/qwebenginepage.pro +++ /dev/null @@ -1,3 +0,0 @@ -include(../tests.pri) -include(../../shared/http.pri) -QT *= core-private diff --git a/tests/auto/widgets/qwebenginepage/resources/fontaccess.html b/tests/auto/widgets/qwebenginepage/resources/fontaccess.html new file mode 100644 index 000000000..1a0fe8af9 --- /dev/null +++ b/tests/auto/widgets/qwebenginepage/resources/fontaccess.html @@ -0,0 +1,14 @@ +<html> +<body onkeypress='onKeyPress()'> +<a>This is test content</a> +<script> +var done = false; +var fonts; +var activated = false; +function onKeyPress() { + activated = true; + window.queryLocalFonts().then(f => { fonts = f; done = true; }); +} +</script> +</body> +</html> diff --git a/tests/auto/widgets/resources/image2.png b/tests/auto/widgets/qwebenginepage/resources/image2.png Binary files differindex 8d703640c..8d703640c 100644 --- a/tests/auto/widgets/resources/image2.png +++ b/tests/auto/widgets/qwebenginepage/resources/image2.png diff --git a/tests/auto/widgets/qwebenginepage/resources/redirect.html b/tests/auto/widgets/qwebenginepage/resources/redirect.html new file mode 100644 index 000000000..db06d73a7 --- /dev/null +++ b/tests/auto/widgets/qwebenginepage/resources/redirect.html @@ -0,0 +1,8 @@ +<html> +<body> +<script> +function doRedirect() { location.replace('qrc:///resources/content.html') } +document.addEventListener("DOMContentLoaded", doRedirect) +</script> +</body> +</html> diff --git a/tests/auto/widgets/qwebenginepage/resources/reload.html b/tests/auto/widgets/qwebenginepage/resources/reload.html index d9c33dfcd..062d06807 100644 --- a/tests/auto/widgets/qwebenginepage/resources/reload.html +++ b/tests/auto/widgets/qwebenginepage/resources/reload.html @@ -1,6 +1,6 @@ <html> <head> -<meta http-equiv="refresh" content="2"> +<meta http-equiv="refresh" content="2;url=qrc:///resources/content.html"> </head> <body> This is test content diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp index dbb15ba10..f6eac2880 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 2016 The Qt Company Ltd. + Copyright (C) 2023 The Qt Company Ltd. Copyright (C) 2009 Girish Ramakrishnan <girish@forwardbias.in> Copyright (C) 2010 Holger Hans Peter Freyther @@ -19,8 +19,10 @@ Boston, MA 02110-1301, USA. */ -#include "../util.h" +#include <widgetutil.h> +#include <QtNetwork/private/qtnetworkglobal_p.h> #include <QtWebEngineCore/qtwebenginecore-config.h> +#include <QtWebEngineCore/private/qtwebenginecoreglobal_p.h> #include <QByteArray> #include <QClipboard> #include <QDir> @@ -31,11 +33,13 @@ #include <QMenu> #include <QMimeDatabase> #include <QNetworkProxy> -#include <QOpenGLWidget> #include <QPaintEngine> #include <QPushButton> #include <QScreen> -#include <QStateMachine> +#include <QWheelEvent> +#if defined(QT_STATEMACHINE_LIB) +# include <QStateMachine> +#endif #include <QtGui/QClipboard> #include <QtTest/QtTest> #include <QTextCharFormat> @@ -46,10 +50,15 @@ #include <qnetworkcookiejar.h> #include <qnetworkreply.h> #include <qnetworkrequest.h> -#include <qwebenginedownloaditem.h> +#include <qwebengineclienthints.h> +#include <qwebenginedownloadrequest.h> +#include <qwebenginedesktopmediarequest.h> +#include <qwebenginefilesystemaccessrequest.h> #include <qwebenginefindtextresult.h> #include <qwebenginefullscreenrequest.h> #include <qwebenginehistory.h> +#include <qwebenginenavigationrequest.h> +#include <qwebenginenewwindowrequest.h> #include <qwebenginenotification.h> #include <qwebenginepage.h> #include <qwebengineprofile.h> @@ -58,14 +67,21 @@ #include <qwebenginescript.h> #include <qwebenginescriptcollection.h> #include <qwebenginesettings.h> +#include <qwebengineurlrequestinterceptor.h> +#include <qwebengineurlrequestjob.h> +#include <qwebengineurlscheme.h> +#include <qwebengineurlschemehandler.h> #include <qwebengineview.h> #include <qimagewriter.h> +#include <QColorSpace> +#include <QQuickRenderControl> +#include <QQuickWindow> static void removeRecursive(const QString& dirname) { QDir dir(dirname); QFileInfoList entries(dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)); - for (int i = 0; i < entries.count(); ++i) + for (int i = 0; i < entries.size(); ++i) if (entries[i].isDir()) removeRecursive(entries[i].filePath()); else @@ -73,6 +89,13 @@ static void removeRecursive(const QString& dirname) QDir().rmdir(dirname); } +struct TestBasePage : QWebEnginePage +{ + explicit TestBasePage(QWebEngineProfile *profile, QObject *parent = nullptr) : QWebEnginePage(profile, parent) { } + explicit TestBasePage(QObject *parent = nullptr) : QWebEnginePage(parent) { } + QSignalSpy loadSpy { this, &QWebEnginePage::loadFinished }; +}; + class tst_QWebEnginePage : public QObject { Q_OBJECT @@ -81,8 +104,6 @@ public: tst_QWebEnginePage(); virtual ~tst_QWebEnginePage(); - bool eventFilter(QObject *watched, QEvent *event); - public Q_SLOTS: void init(); void cleanup(); @@ -91,12 +112,18 @@ public Q_SLOTS: private Q_SLOTS: void initTestCase(); void cleanupTestCase(); + void comboBoxPopupPositionAfterMove_data(); void comboBoxPopupPositionAfterMove(); + void comboBoxPopupPositionAfterChildMove_data(); void comboBoxPopupPositionAfterChildMove(); void acceptNavigationRequest(); + void acceptNavigationRequestWithFormData(); void acceptNavigationRequestNavigationType(); + void acceptNavigationRequestRelativeToNothing(); +#ifndef Q_OS_MACOS void geolocationRequestJS_data(); void geolocationRequestJS(); +#endif void loadFinished(); void actionStates(); void pasteImage(); @@ -131,11 +158,13 @@ private Q_SLOTS: void findTextCalledOnMatch(); void findTextActiveMatchOrdinal(); void deleteQWebEngineViewTwice(); +#if defined(QT_STATEMACHINE_LIB) void loadSignalsOrder_data(); void loadSignalsOrder(); +#endif void openWindowDefaultSize(); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS void macCopyUnicodeToClipboard(); #endif @@ -143,7 +172,8 @@ private Q_SLOTS: void runJavaScriptDisabled(); void runJavaScriptFromSlot(); void fullScreenRequested(); - void quotaRequested(); + void requestQuota_data(); + void requestQuota(); // Tests from tst_QWebEngineFrame @@ -158,6 +188,7 @@ private Q_SLOTS: void setHtmlWithStylesheetResource(); void setHtmlWithBaseURL(); void setHtmlWithJSAlert(); + void setHtmlWithModuleImport(); void baseUrl_data(); void baseUrl(); void scrollPosition(); @@ -174,7 +205,6 @@ private Q_SLOTS: void setUrlUsingStateObject(); void setUrlThenLoads_data(); void setUrlThenLoads(); - void loadFinishedAfterNotFoundError(); void loadInSignalHandlers_data(); void loadInSignalHandlers(); void loadFromQrc(); @@ -197,13 +227,21 @@ private Q_SLOTS: void dataURLFragment(); void devTools(); void openLinkInDifferentProfile(); + void openLinkInNewPage_data(); + void openLinkInNewPage(); void triggerActionWithoutMenu(); void dynamicFrame(); - void notificationRequest_data(); - void notificationRequest(); + void notificationPermission_data(); + void notificationPermission(); void sendNotification(); + void clipboardReadWritePermissionInitialState_data(); + void clipboardReadWritePermissionInitialState(); + void clipboardReadWritePermission_data(); + void clipboardReadWritePermission(); void contentsSize(); + void localFontAccessPermission_data(); + void localFontAccessPermission(); void setLifecycleState(); void setVisible(); @@ -224,20 +262,64 @@ private Q_SLOTS: void editActionsWithoutSelection(); void customUserAgentInNewTab(); + void openNewTabInDifferentProfile_data(); + void openNewTabInDifferentProfile(); + void renderProcessCrashed(); + void renderProcessPid(); + void backgroundColor(); + void audioMuted(); + void closeContents(); + void isSafeRedirect_data(); + void isSafeRedirect(); + + void testChooseFilesParameters_data(); + void testChooseFilesParameters(); + void fileSystemAccessDialog(); + + void localToRemoteNavigation(); + void clientHints_data(); + void clientHints(); + void childFrameInput(); + void openLinkInNewPageWithWebWindowType_data(); + void openLinkInNewPageWithWebWindowType(); + void keepInterceptorAfterNewWindowRequested(); + void chooseDesktopMedia(); private: - static QPoint elementCenter(QWebEnginePage *page, const QString &id); + static bool isFalseJavaScriptResult(QWebEnginePage *page, const QString &javaScript); + static bool isTrueJavaScriptResult(QWebEnginePage *page, const QString &javaScript); + static bool isEmptyListJavaScriptResult(QWebEnginePage *page, const QString &javaScript); QWebEngineView* m_view; QWebEnginePage* m_page; - QWebEngineView* m_inputFieldsTestView; - int m_inputFieldTestPaintCount; + QScopedPointer<QPointingDevice> s_touchDevice = + QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); + QString tmpDirPath() const { static QString tmpd = QDir::tempPath() + "/tst_qwebenginepage-" + QDateTime::currentDateTime().toString(QLatin1String("yyyyMMddhhmmss")); return tmpd; } + + void makeClick(const QPointer<QWindow> window, bool withTouch = false, + const QPoint &p = QPoint()) + { + QVERIFY2(window, "window is gone"); + if (!withTouch) { + QTest::mouseClick(window, Qt::LeftButton, Qt::KeyboardModifiers(), p); + } else { + QTest::touchEvent(window, s_touchDevice.get()).press(1, p); + QTest::touchEvent(window, s_touchDevice.get()).release(1, p); + } + }; + + void makeScroll(QWidget *target, QPointF pos, QPoint globalPos, QPoint angleDelta) + { + QWheelEvent ev(pos, globalPos, QPoint(0, 0), angleDelta, Qt::NoButton, Qt::NoModifier, + Qt::NoScrollPhase, false); + QGuiApplication::sendEvent(target, &ev); + } }; tst_QWebEnginePage::tst_QWebEnginePage() @@ -248,16 +330,6 @@ tst_QWebEnginePage::~tst_QWebEnginePage() { } -bool tst_QWebEnginePage::eventFilter(QObject* watched, QEvent* event) -{ - // used on the inputFieldFocus test - if (watched == m_inputFieldsTestView) { - if (event->type() == QEvent::Paint) - m_inputFieldTestPaintCount++; - } - return QObject::eventFilter(watched, event); -} - void tst_QWebEnginePage::init() { m_view = new QWebEngineView(); @@ -289,6 +361,18 @@ void tst_QWebEnginePage::initTestCase() #endif searchPath += QStringLiteral("/../../../plugins"); QCoreApplication::addLibraryPath(searchPath); + + QWebEngineUrlScheme echo("echo"); + echo.setSyntax(QWebEngineUrlScheme::Syntax::Path); + QWebEngineUrlScheme::registerScheme(echo); + + QWebEngineUrlScheme local("local"); + local.setFlags(QWebEngineUrlScheme::LocalScheme); + QWebEngineUrlScheme::registerScheme(local); + + QWebEngineUrlScheme remote("remote"); + remote.setFlags(QWebEngineUrlScheme::CorsEnabled); + QWebEngineUrlScheme::registerScheme(remote); } void tst_QWebEnginePage::cleanupTestCase() @@ -296,6 +380,23 @@ void tst_QWebEnginePage::cleanupTestCase() cleanupFiles(); // Be nice } +class EchoingUrlSchemeHandler : public QWebEngineUrlSchemeHandler +{ +public: + EchoingUrlSchemeHandler(QObject *parent = nullptr) + : QWebEngineUrlSchemeHandler(parent) + { + } + ~EchoingUrlSchemeHandler() = default; + + void requestStarted(QWebEngineUrlRequestJob *job) override + { + QBuffer *buffer = new QBuffer(job); + buffer->setData(job->requestUrl().toString(QUrl::RemoveScheme).toUtf8()); + job->reply("text/plain;charset=utf-8", buffer); + } +}; + class NavigationRequestOverride : public QWebEnginePage { public: @@ -303,7 +404,7 @@ public: bool m_acceptNavigationRequest; protected: - virtual bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) + bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) override { Q_UNUSED(url); Q_UNUSED(isMainFrame); @@ -316,24 +417,26 @@ protected: void tst_QWebEnginePage::acceptNavigationRequest() { QWebEngineProfile profile; + profile.installUrlSchemeHandler("echo", new EchoingUrlSchemeHandler(&profile)); NavigationRequestOverride page(&profile, false); QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); - page.setHtml(QString("<html><body><form name='tstform' action='data:text/html,foo'method='get'>" - "<input type='text'><input type='submit'></form></body></html>"), QUrl()); - QTRY_COMPARE(loadSpy.count(), 1); + page.setHtml(QString("<html><body><form name='tstform' action='foo' method='get'>" + "<input type='text'><input type='submit'></form></body></html>"), + QUrl("echo:/")); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); evaluateJavaScriptSync(&page, "tstform.submit();"); - QTRY_COMPARE(loadSpy.count(), 2); + QTRY_COMPARE(loadSpy.size(), 2); // Content hasn't changed so the form submit will still work page.m_acceptNavigationRequest = true; evaluateJavaScriptSync(&page, "tstform.submit();"); - QTRY_COMPARE(loadSpy.count(), 3); + QTRY_COMPARE(loadSpy.size(), 3); // Now the content has changed - QCOMPARE(toPlainTextSync(&page), QString("foo?")); + QCOMPARE(toPlainTextSync(&page), QString("/foo?")); } class JSTestPage : public QWebEnginePage @@ -366,6 +469,7 @@ private: bool m_allowGeolocation; }; +#ifndef Q_OS_MACOS void tst_QWebEnginePage::geolocationRequestJS_data() { QTest::addColumn<bool>("allowed"); @@ -380,7 +484,8 @@ void tst_QWebEnginePage::geolocationRequestJS() QFETCH(int, errorCode); QWebEngineView view; JSTestPage *newPage = new JSTestPage(&view); - newPage->setView(&view); + view.setPage(newPage); + newPage->profile()->setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions); newPage->setGeolocationPermission(allowed); connect(newPage, SIGNAL(featurePermissionRequested(const QUrl&, QWebEnginePage::Feature)), @@ -388,7 +493,7 @@ void tst_QWebEnginePage::geolocationRequestJS() QSignalSpy spyLoadFinished(newPage, SIGNAL(loadFinished(bool))); newPage->setHtml(QString("<html><body>test</body></html>"), QUrl("qrc://secure/origin")); - QTRY_COMPARE(spyLoadFinished.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(spyLoadFinished.size(), 1, 20000); // Geolocation is only enabled for visible WebContents. view.show(); @@ -405,6 +510,7 @@ void tst_QWebEnginePage::geolocationRequestJS() QEXPECT_FAIL("", "No location service available.", Continue); QCOMPARE(result, errorCode); } +#endif void tst_QWebEnginePage::loadFinished() { @@ -415,19 +521,19 @@ void tst_QWebEnginePage::loadFinished() page.load(QUrl("data:text/html,<frameset cols=\"25%,75%\"><frame src=\"data:text/html," "<head><meta http-equiv='refresh' content='1'></head>foo \">" "<frame src=\"data:text/html,bar\"></frameset>")); - QTRY_COMPARE(spyLoadFinished.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(spyLoadFinished.size(), 1, 20000); QEXPECT_FAIL("", "Behavior change: Load signals are emitted only for the main frame in QtWebEngine.", Continue); - QTRY_VERIFY_WITH_TIMEOUT(spyLoadStarted.count() > 1, 100); + QTRY_VERIFY_WITH_TIMEOUT(spyLoadStarted.size() > 1, 100); QEXPECT_FAIL("", "Behavior change: Load signals are emitted only for the main frame in QtWebEngine.", Continue); - QTRY_VERIFY_WITH_TIMEOUT(spyLoadFinished.count() > 1, 100); + QTRY_VERIFY_WITH_TIMEOUT(spyLoadFinished.size() > 1, 100); spyLoadFinished.clear(); page.load(QUrl("data:text/html,<frameset cols=\"25%,75%\"><frame src=\"data:text/html," "foo \"><frame src=\"data:text/html,bar\"></frameset>")); - QTRY_COMPARE(spyLoadFinished.count(), 1); - QCOMPARE(spyLoadFinished.count(), 1); + QTRY_COMPARE(spyLoadFinished.size(), 1); + QCOMPARE(spyLoadFinished.size(), 1); } void tst_QWebEnginePage::actionStates() @@ -475,15 +581,16 @@ void tst_QWebEnginePage::pasteImage() QClipboard *clipboard = QGuiApplication::clipboard(); clipboard->setImage(origImage); QWebEnginePage *page = m_view->page(); - page->load(QUrl("qrc:///resources/pasteimage.html")); QSignalSpy spyFinished(m_view, &QWebEngineView::loadFinished); - QVERIFY(spyFinished.wait()); + page->load(QUrl("qrc:///resources/pasteimage.html")); + QTRY_VERIFY_WITH_TIMEOUT(!spyFinished.isEmpty(), 20000); page->triggerAction(QWebEnginePage::Paste); QTRY_VERIFY(evaluateJavaScriptSync(page, "window.myImageDataURL ? window.myImageDataURL.length : 0").toInt() > 0); QByteArray data = evaluateJavaScriptSync(page, "window.myImageDataURL").toByteArray(); data.remove(0, data.indexOf(";base64,") + 8); QImage image = QImage::fromData(QByteArray::fromBase64(data), "PNG"); + image.setColorSpace(origImage.colorSpace()); if (image.format() == QImage::Format_RGB32) image.reinterpretAsFormat(QImage::Format_ARGB32); QCOMPARE(image, origImage); @@ -494,7 +601,7 @@ class ConsolePage : public QWebEnginePage public: ConsolePage(QObject* parent = 0) : QWebEnginePage(parent) {} - virtual void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString& message, int lineNumber, const QString& sourceID) + void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString& message, int lineNumber, const QString& sourceID) override { levels.append(level); messages.append(message); @@ -513,42 +620,29 @@ void tst_QWebEnginePage::consoleOutput() ConsolePage page; // We don't care about the result but want this to be synchronous evaluateJavaScriptSync(&page, "this is not valid JavaScript"); - QCOMPARE(page.messages.count(), 1); + QCOMPARE(page.messages.size(), 1); QCOMPARE(page.lineNumbers.at(0), 1); } class TestPage : public QWebEnginePage { Q_OBJECT public: - TestPage(QObject* parent = 0) : QWebEnginePage(parent) + TestPage(QObject *parent = nullptr, QWebEngineProfile *profile = nullptr) : QWebEnginePage(profile, parent) { - connect(this, SIGNAL(geometryChangeRequested(QRect)), this, SLOT(slotGeometryChangeRequested(QRect))); + connect(this, &QWebEnginePage::geometryChangeRequested, this, &TestPage::slotGeometryChangeRequested); + connect(this, &QWebEnginePage::navigationRequested, this, &TestPage::slotNavigationRequested); + connect(this, &QWebEnginePage::newWindowRequested, this, &TestPage::slotNewWindowRequested); } struct Navigation { - NavigationType type; + QWebEngineNavigationRequest::NavigationType type; QUrl url; bool isMainFrame; + bool hasFormData; }; QList<Navigation> navigations; - virtual bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) - { - Navigation n; - n.url = url; - n.type = type; - n.isMainFrame = isMainFrame; - navigations.append(n); - return true; - } - QList<TestPage*> createdWindows; - virtual QWebEnginePage* createWindow(WebWindowType) { - TestPage* page = new TestPage(this); - createdWindows.append(page); - emit windowCreated(); - return page; - } QRect requestedGeometry; @@ -556,77 +650,182 @@ signals: void windowCreated(); private Q_SLOTS: - void slotGeometryChangeRequested(const QRect& geom) { + void slotNavigationRequested(QWebEngineNavigationRequest &request) + { + Navigation n; + n.url = request.url(); + n.type = request.navigationType(); + n.isMainFrame = request.isMainFrame(); + n.hasFormData = request.hasFormData(); + navigations.append(n); + request.accept(); + } + void slotNewWindowRequested(QWebEngineNewWindowRequest &request) + { + TestPage *page = new TestPage(this, profile()); + createdWindows.append(page); + emit windowCreated(); + request.openIn(page); + } + + void slotGeometryChangeRequested(const QRect &geom) + { requestedGeometry = geom; } }; -void tst_QWebEnginePage::acceptNavigationRequestNavigationType() +void tst_QWebEnginePage::acceptNavigationRequestWithFormData() { + QWebEngineProfile profile; + profile.installUrlSchemeHandler("echo", new EchoingUrlSchemeHandler(&profile)); + TestPage page(nullptr, &profile); + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + + page.setHtml(QString("<html><body><form name='tstform' action='foo' method='post'>" + "<input type='text'><input type='submit'></form></body></html>"), + QUrl("echo:/")); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); + QCOMPARE(page.navigations[0].type, QWebEngineNavigationRequest::TypedNavigation); + QVERIFY(!page.navigations[0].hasFormData); + + evaluateJavaScriptSync(&page, "tstform.submit();"); + QTRY_COMPARE(loadSpy.size(), 2); + QCOMPARE(page.navigations[1].type, QWebEngineNavigationRequest::FormSubmittedNavigation); + QVERIFY(page.navigations[1].hasFormData); + + page.triggerAction(QWebEnginePage::Reload); + QTRY_COMPARE(loadSpy.size(), 3); + QCOMPARE(page.navigations[2].type, QWebEngineNavigationRequest::ReloadNavigation); + QVERIFY(page.navigations[2].hasFormData); +} +void tst_QWebEnginePage::acceptNavigationRequestNavigationType() +{ TestPage page; QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); page.load(QUrl("qrc:///resources/script.html")); - QTRY_COMPARE(loadSpy.count(), 1); - QTRY_COMPARE(page.navigations.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); + QTRY_COMPARE(page.navigations.size(), 1); page.load(QUrl("qrc:///resources/content.html")); - QTRY_COMPARE(loadSpy.count(), 2); - QTRY_COMPARE(page.navigations.count(), 2); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 2, 20000); + QTRY_COMPARE(page.navigations.size(), 2); page.triggerAction(QWebEnginePage::Stop); QVERIFY(page.history()->canGoBack()); page.triggerAction(QWebEnginePage::Back); - QTRY_COMPARE(loadSpy.count(), 3); - QTRY_COMPARE(page.navigations.count(), 3); + QTRY_COMPARE(loadSpy.size(), 3); + QTRY_COMPARE(page.navigations.size(), 3); page.triggerAction(QWebEnginePage::Reload); - QTRY_COMPARE(loadSpy.count(), 4); - QTRY_COMPARE(page.navigations.count(), 4); + QTRY_COMPARE(loadSpy.size(), 4); + QTRY_COMPARE(page.navigations.size(), 4); + QList<QWebEngineNavigationRequest::NavigationType> expectedList; + expectedList << QWebEngineNavigationRequest::TypedNavigation + << QWebEngineNavigationRequest::TypedNavigation + << QWebEngineNavigationRequest::BackForwardNavigation + << QWebEngineNavigationRequest::ReloadNavigation; + + // client side redirect page.load(QUrl("qrc:///resources/reload.html")); - QTRY_COMPARE(loadSpy.count(), 6); - QTRY_COMPARE(page.navigations.count(), 6); - - QList<QWebEnginePage::NavigationType> expectedList; - expectedList << QWebEnginePage::NavigationTypeTyped - << QWebEnginePage::NavigationTypeTyped - << QWebEnginePage::NavigationTypeBackForward - << QWebEnginePage::NavigationTypeReload - << QWebEnginePage::NavigationTypeTyped - << QWebEnginePage::NavigationTypeRedirect; - QVERIFY(expectedList.count() == page.navigations.count()); - for (int i = 0; i < expectedList.count(); ++i) { + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 6, 20000); + QTRY_COMPARE(page.navigations.size(), 6); + expectedList += { QWebEngineNavigationRequest::TypedNavigation, QWebEngineNavigationRequest::RedirectNavigation }; + + + page.load(QUrl("qrc:///resources/redirect.html")); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 7, 20000); + QTRY_COMPARE(page.navigations.size(), 8); + expectedList += { QWebEngineNavigationRequest::TypedNavigation, QWebEngineNavigationRequest::RedirectNavigation }; + + // server side redirect + HttpServer server; + server.setResourceDirs({ ":/resources" }); + connect(&server, &HttpServer::newRequest, &server, [&] (HttpReqRep *r) { + if (r->requestMethod() == "GET") { + if (r->requestPath() == "/redirect1.html") { + r->setResponseHeader("Location", server.url("/redirect2.html").toEncoded()); + r->setResponseBody("<html><body>Redirect1</body></html>"); + r->sendResponse(307); // Internal server redirect + } else if (r->requestPath() == "/redirect2.html") { + r->setResponseHeader("Location", server.url("/content.html").toEncoded()); + r->setResponseBody("<html><body>Redirect2</body></html>"); + r->sendResponse(301); // Moved permanently + } + } + }); + QVERIFY(server.start()); + page.load(QUrl(server.url("/redirect1.html"))); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 8, 20000); + expectedList += { + QWebEngineNavigationRequest::TypedNavigation, + QWebEngineNavigationRequest::RedirectNavigation, + QWebEngineNavigationRequest::RedirectNavigation + }; + + for (int i = 0; i < expectedList.size(); ++i) { + QTRY_VERIFY(i < page.navigations.size()); QCOMPARE(page.navigations[i].type, expectedList[i]); } + QVERIFY(expectedList.size() == page.navigations.size()); } -void tst_QWebEnginePage::popupFormSubmission() +// Relative url without base url. +// +// See also: QTBUG-48435 +void tst_QWebEnginePage::acceptNavigationRequestRelativeToNothing() { TestPage page; + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + + page.setHtml(QString("<html><body><a id='link' href='S0'>limited time offer</a></body></html>"), + /* baseUrl: */ QUrl()); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); + page.runJavaScript(QStringLiteral("document.getElementById(\"link\").click()")); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 2, 20000); + page.setHtml(QString("<html><body><a id='link' href='S0'>limited time offer</a></body></html>"), + /* baseUrl: */ QString("qrc:/")); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 3, 20000); + page.runJavaScript(QStringLiteral("document.getElementById(\"link\").click()")); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 4, 20000); + + // The two setHtml and the second click are counted, while the + // first click is ignored due to the empty base url. + QCOMPARE(page.navigations.size(), 3); + QCOMPARE(page.navigations[0].type, QWebEngineNavigationRequest::TypedNavigation); + QCOMPARE(page.navigations[1].type, QWebEngineNavigationRequest::TypedNavigation); + QCOMPARE(page.navigations[2].type, QWebEngineNavigationRequest::LinkClickedNavigation); + QCOMPARE(page.navigations[2].url, QUrl(QString("qrc:/S0"))); +} + +void tst_QWebEnginePage::popupFormSubmission() +{ + QWebEngineProfile profile; + profile.installUrlSchemeHandler("echo", new EchoingUrlSchemeHandler(&profile)); + TestPage page(nullptr, &profile); QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); QSignalSpy windowCreatedSpy(&page, SIGNAL(windowCreated())); page.settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true); page.setHtml("<form name='form1' method=get action='' target='myNewWin'>" " <input type='hidden' name='foo' value='bar'>" - "</form>"); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + "</form>", QUrl("echo:")); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 20000); page.runJavaScript("window.open('', 'myNewWin', 'width=500,height=300,toolbar=0');"); evaluateJavaScriptSync(&page, "document.form1.submit();"); - QTRY_COMPARE(windowCreatedSpy.count(), 1); + QTRY_COMPARE(windowCreatedSpy.size(), 1); // The number of popup created should be one. QVERIFY(page.createdWindows.size() == 1); QTRY_VERIFY(!page.createdWindows[0]->url().isEmpty()); - QString url = page.createdWindows[0]->url().toString(); // Check if the form submission was OK. - QVERIFY(url.contains("?foo=bar")); + QTRY_VERIFY(page.createdWindows[0]->url().toString().contains("?foo=bar")); } class TestNetworkManager : public QNetworkAccessManager @@ -638,7 +837,8 @@ public: QList<QNetworkRequest> requests; protected: - virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest &request, QIODevice* outgoingData) { + QNetworkReply* createRequest(Operation op, const QNetworkRequest &request, QIODevice* outgoingData) override + { requests.append(request); requestedUrls.append(request.url()); return QNetworkAccessManager::createRequest(op, request, outgoingData); @@ -666,16 +866,16 @@ void tst_QWebEnginePage::multipleProfilesAndLocalStorage() page1.setHtml(QString("<html><body> </body></html>"), QUrl("http://wwww.example.com")); page2.setHtml(QString("<html><body> </body></html>"), QUrl("http://wwww.example.com")); - QTRY_COMPARE(loadSpy1.count(), 1); - QTRY_COMPARE(loadSpy2.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy1.size(), 1, 20000); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy2.size(), 1, 20000); evaluateJavaScriptSync(&page1, "localStorage.setItem('test', 'value1');"); evaluateJavaScriptSync(&page2, "localStorage.setItem('test', 'value2');"); page1.setHtml(QString("<html><body> </body></html>"), QUrl("http://wwww.example.com")); page2.setHtml(QString("<html><body> </body></html>"), QUrl("http://wwww.example.com")); - QTRY_COMPARE(loadSpy1.count(), 2); - QTRY_COMPARE(loadSpy2.count(), 2); + QTRY_COMPARE(loadSpy1.size(), 2); + QTRY_COMPARE(loadSpy2.size(), 2); QVariant s1 = evaluateJavaScriptSync(&page1, "localStorage.getItem('test')"); QCOMPARE(s1.toString(), QString("value1")); @@ -695,7 +895,7 @@ public: CursorTrackedPage(QWidget *parent = 0): QWebEnginePage(parent) { } - QString selectedText() { + QString jsSelectedText() { return evaluateJavaScriptSync(this, "window.getSelection().toString()").toString(); } @@ -711,62 +911,91 @@ public: int isSelectionCollapsed() { return evaluateJavaScriptSync(this, "window.getSelection().getRangeAt(0).collapsed").toBool(); } - bool hasSelection() - { - return !selectedText().isEmpty(); - } }; void tst_QWebEnginePage::textSelection() { - QWebEngineView view; - CursorTrackedPage *page = new CursorTrackedPage(&view); - QString content("<html><body><p id=one>The quick brown fox</p>" \ + CursorTrackedPage page; + + QString textToSelect("The quick brown fox"); + QString content = QString("<html><body><p id=one>%1</p>" \ "<p id=two>jumps over the lazy dog</p>" \ - "<p>May the source<br/>be with you!</p></body></html>"); - page->setView(&view); - QSignalSpy loadSpy(&view, SIGNAL(loadFinished(bool))); - page->setHtml(content); - QTRY_COMPARE(loadSpy.count(), 1); + "<p>May the source<br/>be with you!</p></body></html>").arg(textToSelect); + + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + page.setHtml(content); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); // these actions must exist - QVERIFY(page->action(QWebEnginePage::SelectAll) != 0); + QVERIFY(page.action(QWebEnginePage::SelectAll) != 0); // ..but SelectAll is disabled because the page has no focus due to disabled FocusOnNavigationEnabled. - QCOMPARE(page->action(QWebEnginePage::SelectAll)->isEnabled(), false); + QCOMPARE(page.action(QWebEnginePage::SelectAll)->isEnabled(), false); // Verify hasSelection returns false since there is no selection yet... - QCOMPARE(page->hasSelection(), false); + QVERIFY(!page.hasSelection()); + QVERIFY(page.jsSelectedText().isEmpty()); // this will select the first paragraph QString selectScript = "var range = document.createRange(); " \ "var node = document.getElementById(\"one\"); " \ "range.selectNode(node); " \ "getSelection().addRange(range);"; - evaluateJavaScriptSync(page, selectScript); - QCOMPARE(page->selectedText().trimmed(), QString::fromLatin1("The quick brown fox")); + evaluateJavaScriptSync(&page, selectScript); + // Make sure hasSelection returns true, since there is selected text now... - QCOMPARE(page->hasSelection(), true); + QTRY_VERIFY(page.hasSelection()); + QCOMPARE(page.selectedText().trimmed(), textToSelect); + + QCOMPARE(page.jsSelectedText().trimmed(), textToSelect); + + // navigate away and check that selection is cleared + page.load(QUrl("about:blank")); + QTRY_COMPARE(loadSpy.size(), 2); + + QVERIFY(!page.hasSelection()); + QVERIFY(page.selectedText().isEmpty()); + + QVERIFY(page.jsSelectedText().isEmpty()); } void tst_QWebEnginePage::backActionUpdate() { QWebEngineView view; + view.resize(640, 480); + view.show(); + QWebEnginePage *page = view.page(); + QSignalSpy loadSpy(page, &QWebEnginePage::loadFinished); QAction *action = page->action(QWebEnginePage::Back); QVERIFY(!action->isEnabled()); - QSignalSpy loadSpy(page, SIGNAL(loadFinished(bool))); - QUrl url = QUrl("qrc:///resources/framedindex.html"); - page->load(url); - QTRY_COMPARE(loadSpy.count(), 1); + + page->load(QUrl("qrc:///resources/framedindex.html")); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); QVERIFY(!action->isEnabled()); - QTest::mouseClick(&view, Qt::LeftButton, 0, QPoint(10, 10)); - QEXPECT_FAIL("", "Behavior change: Load signals are emitted only for the main frame in QtWebEngine.", Continue); - QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 2, 100); - QEXPECT_FAIL("", "FIXME: Mouse events aren't passed from the QWebEngineView down to the RWHVQtDelegateWidget", Continue); - QVERIFY(action->isEnabled()); + auto firstAnchorCenterInFrame = [](QWebEnginePage *page, const QString &frameName) { + QVariantList rectList = evaluateJavaScriptSync(page, + "(function(){" + "var frame = document.getElementsByName('" + frameName + "')[0];" + "var anchor = frame.contentDocument.getElementsByTagName('a')[0];" + "var rect = anchor.getBoundingClientRect();" + "return [(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2];" + "})()").toList(); + + if (rectList.size() != 2) { + qWarning("firstAnchorCenterInFrame failed."); + return QPoint(); + } + + return QPoint(rectList.at(0).toInt(), rectList.at(1).toInt()); + }; + + QVERIFY(evaluateJavaScriptSync(page, "document.getElementsByName('frame_b')[0].contentDocument == undefined").toBool()); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, firstAnchorCenterInFrame(page, "frame_c")); + QTRY_VERIFY(evaluateJavaScriptSync(page, "document.getElementsByName('frame_b')[0].contentDocument != undefined").toBool()); + QTRY_VERIFY(action->isEnabled()); } void tst_QWebEnginePage::localStorageVisibility() @@ -782,24 +1011,28 @@ void tst_QWebEnginePage::localStorageVisibility() QSignalSpy loadSpy2(&webPage2, &QWebEnginePage::loadFinished); webPage1.setHtml(QString("<html><body>test</body></html>"), QUrl("http://www.example.com/")); webPage2.setHtml(QString("<html><body>test</body></html>"), QUrl("http://www.example.com/")); - QTRY_COMPARE(loadSpy1.count(), 1); - QTRY_COMPARE(loadSpy2.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy1.size(), 1, 20000); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy2.size(), 1, 20000); // The attribute determines the visibility of the window.localStorage object. QVERIFY(evaluateJavaScriptSync(&webPage1, QString("(window.localStorage != undefined)")).toBool()); QVERIFY(!evaluateJavaScriptSync(&webPage2, QString("(window.localStorage != undefined)")).toBool()); - // Switching the feature off does not actively remove the object from webPage1. + // Toggle local setting for every page and... webPage1.settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, false); webPage2.settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, true); - QVERIFY(evaluateJavaScriptSync(&webPage1, QString("(window.localStorage != undefined)")).toBool()); - QVERIFY(evaluateJavaScriptSync(&webPage2, QString("(window.localStorage != undefined)")).toBool()); + // TODO: note this setting is flaky, consider settings().commit() + // ...first check second page (for storage to appear) as applying settings is batched and done asynchronously + QTRY_VERIFY(evaluateJavaScriptSync(&webPage2, QString("(window.localStorage != undefined)")).toBool()); + // Switching the feature off does not actively remove the object from webPage1. +// FIXME: 94-based: now it does +// QVERIFY(evaluateJavaScriptSync(&webPage1, QString("(window.localStorage != undefined)")).toBool()); // The object disappears only after reloading. webPage1.triggerAction(QWebEnginePage::Reload); webPage2.triggerAction(QWebEnginePage::Reload); - QTRY_COMPARE(loadSpy1.count(), 2); - QTRY_COMPARE(loadSpy2.count(), 2); + QTRY_COMPARE(loadSpy1.size(), 2); + QTRY_COMPARE(loadSpy2.size(), 2); QVERIFY(!evaluateJavaScriptSync(&webPage1, QString("(window.localStorage != undefined)")).toBool()); QVERIFY(evaluateJavaScriptSync(&webPage2, QString("(window.localStorage != undefined)")).toBool()); } @@ -873,7 +1106,7 @@ public: JSPromptPage() {} - bool javaScriptPrompt(const QUrl &securityOrigin, const QString& msg, const QString& defaultValue, QString* result) + bool javaScriptPrompt(const QUrl &securityOrigin, const QString& msg, const QString& defaultValue, QString* result) override { if (msg == QLatin1String("test1")) { *result = QString(); @@ -900,7 +1133,7 @@ void tst_QWebEnginePage::testJSPrompt() bool res; QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); page.setHtml(QStringLiteral("<html><body></body></html>")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); // OK + QString() res = evaluateJavaScriptSync(&page, @@ -934,7 +1167,7 @@ void tst_QWebEnginePage::findText() // Showing is required, otherwise all find operations fail. m_view->show(); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); // Select whole page contents. QTRY_VERIFY(m_view->page()->action(QWebEnginePage::SelectAll)->isEnabled()); @@ -944,22 +1177,22 @@ void tst_QWebEnginePage::findText() // Invoking a stopFinding operation will not change or clear the currently selected text, // if nothing was found beforehand. { - CallbackSpy<bool> callbackSpy; + CallbackSpy<QWebEngineFindTextResult> callbackSpy; QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); - m_view->findText("", 0, callbackSpy.ref()); + m_view->findText("", {}, callbackSpy.ref()); QVERIFY(callbackSpy.wasCalled()); - QCOMPARE(signalSpy.count(), 1); + QCOMPARE(signalSpy.size(), 1); QTRY_COMPARE(m_view->selectedText(), QString("foo bar")); } // Invoking a startFinding operation with text that won't be found, will clear the current // selection. { - CallbackSpy<bool> callbackSpy; + CallbackSpy<QWebEngineFindTextResult> callbackSpy; QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); - m_view->findText("Will not be found", 0, callbackSpy.ref()); - QCOMPARE(callbackSpy.waitForResult(), false); - QTRY_COMPARE(signalSpy.count(), 1); + m_view->findText("Will not be found", {}, callbackSpy.ref()); + QCOMPARE(callbackSpy.waitForResult().numberOfMatches(), 0); + QTRY_COMPARE(signalSpy.size(), 1); auto result = signalSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 0); QTRY_VERIFY(m_view->selectedText().isEmpty()); @@ -972,34 +1205,47 @@ void tst_QWebEnginePage::findText() // Invoking a startFinding operation with text that will be found, will clear the current // selection as well. { - CallbackSpy<bool> callbackSpy; + CallbackSpy<QWebEngineFindTextResult> callbackSpy; QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); - m_view->findText("foo", 0, callbackSpy.ref()); - QVERIFY(callbackSpy.waitForResult()); - QTRY_COMPARE(signalSpy.count(), 1); + m_view->findText("foo", {}, callbackSpy.ref()); + QVERIFY(callbackSpy.waitForResult().numberOfMatches() > 0); + QTRY_COMPARE(signalSpy.size(), 1); 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> callbackSpy; + CallbackSpy<QWebEngineFindTextResult> callbackSpy; QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); - m_view->findText("", 0, callbackSpy.ref()); + m_view->findText("", {}, callbackSpy.ref()); QTRY_VERIFY(callbackSpy.wasCalled()); - QTRY_COMPARE(signalSpy.count(), 1); + QTRY_COMPARE(signalSpy.size(), 1); QTRY_COMPARE(m_view->selectedText(), QString("foo")); } + + // Invoking startFinding operation for the same text twice. Without any wait, the second one + // should interrupt the first one. + { + QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); + m_view->findText("foo", {}); + m_view->findText("foo", {}); + QTRY_COMPARE(signalSpy.size(), 2); + QTRY_VERIFY(m_view->selectedText().isEmpty()); + + QCOMPARE(signalSpy.at(0).value(0).value<QWebEngineFindTextResult>().numberOfMatches(), 0); + QCOMPARE(signalSpy.at(1).value(0).value<QWebEngineFindTextResult>().numberOfMatches(), 1); + } } void tst_QWebEnginePage::findTextResult() { QSignalSpy findTextSpy(m_view->page(), &QWebEnginePage::findTextFinished); - auto signalResult = [&findTextSpy]() -> QVector<int> { - if (findTextSpy.count() != 1) - return QVector<int>({-1, -1}); + auto signalResult = [&findTextSpy]() -> QList<int> { + if (findTextSpy.size() != 1) + return QList<int>({-1, -1}); auto r = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); - return QVector<int>({ r.numberOfMatches(), r.activeMatchOrdinal() }); + return QList<int>({ r.numberOfMatches(), r.activeMatch() }); }; // findText will abort in blink if the view has an empty size. @@ -1008,41 +1254,41 @@ void tst_QWebEnginePage::findTextResult() QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool))); m_view->setHtml(QString("<html><head></head><body><div>foo bar</div></body></html>")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(findTextSync(m_page, ""), false); - QCOMPARE(signalResult(), QVector<int>({0, 0})); + QCOMPARE(signalResult(), QList<int>({0, 0})); const QStringList words = { "foo", "bar" }; for (const QString &subString : words) { QCOMPARE(findTextSync(m_page, subString), true); - QCOMPARE(signalResult(), QVector<int>({1, 1})); + QCOMPARE(signalResult(), QList<int>({1, 1})); QCOMPARE(findTextSync(m_page, ""), false); - QCOMPARE(signalResult(), QVector<int>({0, 0})); + QCOMPARE(signalResult(), QList<int>({0, 0})); } QCOMPARE(findTextSync(m_page, "blahhh"), false); - QCOMPARE(signalResult(), QVector<int>({0, 0})); + QCOMPARE(signalResult(), QList<int>({0, 0})); QCOMPARE(findTextSync(m_page, ""), false); - QCOMPARE(signalResult(), QVector<int>({0, 0})); + QCOMPARE(signalResult(), QList<int>({0, 0})); } void tst_QWebEnginePage::findTextSuccessiveShouldCallAllCallbacks() { - CallbackSpy<bool> spy1; - CallbackSpy<bool> spy2; - CallbackSpy<bool> spy3; - CallbackSpy<bool> spy4; - CallbackSpy<bool> spy5; + CallbackSpy<QWebEngineFindTextResult> spy1; + CallbackSpy<QWebEngineFindTextResult> spy2; + CallbackSpy<QWebEngineFindTextResult> spy3; + CallbackSpy<QWebEngineFindTextResult> spy4; + CallbackSpy<QWebEngineFindTextResult> spy5; QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool))); m_view->setHtml(QString("<html><head></head><body><div>abcdefg abcdefg abcdefg abcdefg abcdefg</div></body></html>")); - QTRY_COMPARE(loadSpy.count(), 1); - m_page->findText("abcde", 0, spy1.ref()); - m_page->findText("abcd", 0, spy2.ref()); - m_page->findText("abc", 0, spy3.ref()); - m_page->findText("ab", 0, spy4.ref()); - m_page->findText("a", 0, spy5.ref()); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); + m_page->findText("abcde", {}, spy1.ref()); + m_page->findText("abcd", {}, spy2.ref()); + m_page->findText("abc", {}, spy3.ref()); + m_page->findText("ab", {}, spy4.ref()); + m_page->findText("a", {}, spy5.ref()); spy5.waitForResult(); QVERIFY(spy1.wasCalled()); QVERIFY(spy2.wasCalled()); @@ -1059,15 +1305,15 @@ void tst_QWebEnginePage::findTextCalledOnMatch() m_view->resize(800, 600); m_view->show(); m_view->setHtml(QString("<html><head></head><body><div>foo bar</div></body></html>")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); // CALLBACK bool callbackCalled = false; - m_view->page()->findText("foo", 0, [this, &callbackCalled](bool found) { - QVERIFY(found); + m_view->page()->findText("foo", {}, [this, &callbackCalled](QWebEngineFindTextResult result) { + QVERIFY(result.numberOfMatches()); - m_view->page()->findText("bar", 0, [&callbackCalled](bool found) { - QVERIFY(found); + m_view->page()->findText("bar", {}, [&callbackCalled](QWebEngineFindTextResult result) { + QVERIFY(result.numberOfMatches()); callbackCalled = true; }); }); @@ -1096,87 +1342,108 @@ void tst_QWebEnginePage::findTextActiveMatchOrdinal() m_view->resize(800, 600); m_view->show(); m_view->setHtml(QString("<html><head></head><body><div>foo bar foo bar foo</div></body></html>")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); // Iterate over all "foo" matches. for (int i = 1; i <= 3; ++i) { - m_view->page()->findText("foo", 0); - QTRY_COMPARE(findTextSpy.count(), 1); + m_view->page()->findText("foo", {}); + QTRY_COMPARE(findTextSpy.size(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 3); - QCOMPARE(result.activeMatchOrdinal(), i); + QCOMPARE(result.activeMatch(), i); } // The last match is followed by the fist one. - m_view->page()->findText("foo", 0); - QTRY_COMPARE(findTextSpy.count(), 1); + m_view->page()->findText("foo", {}); + QTRY_COMPARE(findTextSpy.size(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 3); - QCOMPARE(result.activeMatchOrdinal(), 1); + QCOMPARE(result.activeMatch(), 1); // The first match is preceded by the last one. m_view->page()->findText("foo", QWebEnginePage::FindBackward); - QTRY_COMPARE(findTextSpy.count(), 1); + QTRY_COMPARE(findTextSpy.size(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 3); - QCOMPARE(result.activeMatchOrdinal(), 3); + QCOMPARE(result.activeMatch(), 3); - // Finding another word resets the activeMatchOrdinal. - m_view->page()->findText("bar", 0); - QTRY_COMPARE(findTextSpy.count(), 1); + // Finding another word resets the activeMatch. + m_view->page()->findText("bar", {}); + QTRY_COMPARE(findTextSpy.size(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 2); - QCOMPARE(result.activeMatchOrdinal(), 1); + QCOMPARE(result.activeMatch(), 1); - // If no match activeMatchOrdinal is 0. - m_view->page()->findText("bla", 0); - QTRY_COMPARE(findTextSpy.count(), 1); + // If no match activeMatch is 0. + m_view->page()->findText("bla", {}); + QTRY_COMPARE(findTextSpy.size(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 0); - QCOMPARE(result.activeMatchOrdinal(), 0); + QCOMPARE(result.activeMatch(), 0); } static QWindow *findNewTopLevelWindow(const QWindowList &oldTopLevelWindows) { const auto tlws = QGuiApplication::topLevelWindows(); for (auto w : tlws) { - if (!oldTopLevelWindows.contains(w)) { + // note 'offscreen' window is a top-level window + if (!oldTopLevelWindows.contains(w) + && !QQuickRenderControl::renderWindowFor(qobject_cast<QQuickWindow *>(w))) { return w; } } return nullptr; } +void tst_QWebEnginePage::comboBoxPopupPositionAfterMove_data() +{ + QTest::addColumn<bool>("withTouch"); + QTest::addRow("mouse") << false; + QTest::addRow("touch") << true; +} + void tst_QWebEnginePage::comboBoxPopupPositionAfterMove() { +#if defined(Q_OS_MACOS) && (defined(__arm64__) || defined(__aarch64__)) + QSKIP("This test crashes for Apple M1"); +#endif QWebEngineView view; + QTRY_VERIFY(QGuiApplication::primaryScreen()); view.move(QGuiApplication::primaryScreen()->availableGeometry().topLeft()); view.resize(640, 480); view.show(); - - QSignalSpy loadSpy(&view, SIGNAL(loadFinished(bool))); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + QSignalSpy spyLoadFinished(&view, SIGNAL(loadFinished(bool))); view.setHtml(QLatin1String("<html><head></head><body><select id='foo'>" "<option>fran</option><option>troz</option>" "</select></body></html>")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(spyLoadFinished.size(), 1); const auto oldTlws = QGuiApplication::topLevelWindows(); - QWindow *window = view.windowHandle(); - QTest::mouseClick(window, Qt::LeftButton, Qt::KeyboardModifiers(), - elementCenter(view.page(), "foo")); - + QFETCH(bool, withTouch); + QPointer<QWindow> window = view.windowHandle(); + auto pos = elementCenter(view.page(), "foo"); + makeClick(window, withTouch, pos); QWindow *popup = nullptr; - QTRY_VERIFY(popup = findNewTopLevelWindow(oldTlws)); + QTRY_VERIFY((popup = findNewTopLevelWindow(oldTlws))); + QVERIFY(QTest::qWaitForWindowExposed(popup)); + QTRY_VERIFY(popup->width() > 0 && popup->height() > 0); QTRY_VERIFY(QGuiApplication::topLevelWindows().contains(popup)); QTRY_VERIFY(!popup->position().isNull()); QPoint popupPos = popup->position(); - + QPointer<QWindow> pw(popup); // Close the popup by clicking somewhere into the page. - QTest::mouseClick(window, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(1, 1)); + makeClick(window, withTouch, QPoint(1, 1)); QTRY_VERIFY(!QGuiApplication::topLevelWindows().contains(popup)); - + QTRY_VERIFY(!pw); auto jsViewPosition = [&view]() { QLatin1String script("(function() { return [window.screenX, window.screenY]; })()"); QVariantList posList = evaluateJavaScriptSync(view.page(), script).toList(); + + if (posList.size() != 2) { + qWarning("jsViewPosition failed."); + return QPoint(); + } + return QPoint(posList.at(0).toInt(), posList.at(1).toInt()); }; @@ -1184,16 +1451,28 @@ void tst_QWebEnginePage::comboBoxPopupPositionAfterMove() const QPoint offset(12, 13); view.move(view.pos() + offset); QTRY_COMPARE(jsViewPosition(), view.pos()); - QTest::mouseClick(window, Qt::LeftButton, Qt::KeyboardModifiers(), - elementCenter(view.page(), "foo")); - QTRY_VERIFY(popup = findNewTopLevelWindow(oldTlws)); + makeClick(window, withTouch, elementCenter(view.page(), "foo")); + QTRY_VERIFY((popup = findNewTopLevelWindow(oldTlws))); + QTRY_VERIFY(popup->width() > 0 && popup->height() > 0); QTRY_VERIFY(QGuiApplication::topLevelWindows().contains(popup)); QTRY_VERIFY(!popup->position().isNull()); QCOMPARE(popupPos + offset, popup->position()); + makeClick(window, withTouch, QPoint(1, 1)); + QTRY_VERIFY(!QGuiApplication::topLevelWindows().contains(popup)); +} + +void tst_QWebEnginePage::comboBoxPopupPositionAfterChildMove_data() +{ + QTest::addColumn<bool>("withTouch"); + QTest::addRow("mouse") << false; + QTest::addRow("touch") << true; } void tst_QWebEnginePage::comboBoxPopupPositionAfterChildMove() { +#if defined(Q_OS_MACOS) && (defined(__arm64__) || defined(__aarch64__)) + QSKIP("This test crashes for Apple M1"); +#endif QWidget mainWidget; mainWidget.setLayout(new QHBoxLayout); @@ -1204,29 +1483,33 @@ void tst_QWebEnginePage::comboBoxPopupPositionAfterChildMove() mainWidget.layout()->addWidget(&view); QScreen *screen = QGuiApplication::primaryScreen(); + Q_ASSERT(screen); mainWidget.move(screen->availableGeometry().topLeft()); mainWidget.resize(640, 480); mainWidget.show(); + QVERIFY(QTest::qWaitForWindowExposed(&mainWidget)); QSignalSpy loadSpy(&view, SIGNAL(loadFinished(bool))); view.setHtml(QLatin1String("<html><head></head><body><select autofocus id='foo'>" "<option value=\"narf\">narf</option><option>zort</option>" "</select></body></html>")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); const auto oldTlws = QGuiApplication::topLevelWindows(); - QWindow *window = view.window()->windowHandle(); - QTest::mouseClick(window, Qt::LeftButton, Qt::KeyboardModifiers(), - view.mapTo(view.window(), elementCenter(view.page(), "foo"))); + + QFETCH(bool, withTouch); + QPointer<QWindow> window = view.window()->windowHandle(); + makeClick(window, withTouch, view.mapTo(view.window(), elementCenter(view.page(), "foo"))); QWindow *popup = nullptr; - QTRY_VERIFY(popup = findNewTopLevelWindow(oldTlws)); + QTRY_VERIFY((popup = findNewTopLevelWindow(oldTlws))); + QVERIFY(QTest::qWaitForWindowExposed(popup)); + QTRY_VERIFY(popup->width() > 0 && popup->height() > 0); QTRY_VERIFY(QGuiApplication::topLevelWindows().contains(popup)); QTRY_VERIFY(!popup->position().isNull()); QPoint popupPos = popup->position(); // Close the popup by clicking somewhere into the page. - QTest::mouseClick(window, Qt::LeftButton, Qt::KeyboardModifiers(), - view.mapTo(view.window(), QPoint(1, 1))); + makeClick(window, withTouch, view.mapTo(view.window(), QPoint(1, 1))); QTRY_VERIFY(!QGuiApplication::topLevelWindows().contains(popup)); int originalViewWidth = view.size().width(); @@ -1236,19 +1519,25 @@ void tst_QWebEnginePage::comboBoxPopupPositionAfterChildMove() return viewWidth; }; + QCOMPARE(jsViewWidth(), originalViewWidth); + // Resize the "spacer" widget, and implicitly change the global position of the QWebEngineView. const int offset = 50; spacer.setMinimumWidth(spacer.size().width() + offset); + QTRY_COMPARE(jsViewWidth(), originalViewWidth - offset); - QTest::mouseClick(window, Qt::LeftButton, Qt::KeyboardModifiers(), - view.mapTo(view.window(), elementCenter(view.page(), "foo"))); - QTRY_VERIFY(popup = findNewTopLevelWindow(oldTlws)); + makeClick(window, withTouch, view.mapTo(view.window(), elementCenter(view.page(), "foo"))); + QTRY_VERIFY((popup = findNewTopLevelWindow(oldTlws))); + QVERIFY(QTest::qWaitForWindowExposed(popup)); + QTRY_VERIFY(popup->width() > 0 && popup->height() > 0); QTRY_VERIFY(!popup->position().isNull()); - QCOMPARE(popupPos + QPoint(50, 0), popup->position()); + QCOMPARE(popupPos + QPoint(offset, 0), popup->position()); + makeClick(window, withTouch, QPoint(1, 1)); + QTRY_VERIFY(!QGuiApplication::topLevelWindows().contains(popup)); } -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS void tst_QWebEnginePage::macCopyUnicodeToClipboard() { QString unicodeText = QString::fromUtf8("αβγδεζηθικλμπ"); @@ -1276,6 +1565,8 @@ void tst_QWebEnginePage::deleteQWebEngineViewTwice() } } +// TODO: Reimplement test without QStateMachine or add qtscxml module dependency +#if defined(QT_STATEMACHINE_LIB) class SpyForLoadSignalsOrder : public QStateMachine { Q_OBJECT public: @@ -1331,8 +1622,9 @@ void tst_QWebEnginePage::loadSignalsOrder() QSignalSpy spyLoadSpy(&loadSpy, &SpyForLoadSignalsOrder::started); QVERIFY(spyLoadSpy.wait(500)); page.load(url); - QTRY_VERIFY(loadSpy.isFinished()); + QTRY_VERIFY_WITH_TIMEOUT(loadSpy.isFinished(), 20000); } +#endif // defined(QT_STATEMACHINE_LIB) void tst_QWebEnginePage::renderWidgetHostViewNotShowTopLevel() { @@ -1369,20 +1661,26 @@ public: connect(this, &QWebEnginePage::loadFinished, [this](bool success){ m_loadSucceeded = success; }); + profile()->setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions); // 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) + void jsGetMedia(const QString &call) { evaluateJavaScriptSync(this, QStringLiteral( "var promiseFulfilled = false;" "var promiseRejected = false;" - "navigator.mediaDevices.getUserMedia(%1)" + "navigator.mediaDevices.%1" ".then(stream => { promiseFulfilled = true})" ".catch(err => { promiseRejected = true})") - .arg(constraints)); + .arg(call)); + } + + void jsGetUserMedia(const QString &constraints) + { + jsGetMedia(QStringLiteral("getUserMedia(%1)").arg(constraints)); } bool jsPromiseFulfilled() @@ -1439,43 +1737,45 @@ private: void tst_QWebEnginePage::getUserMediaRequest_data() { - QTest::addColumn<QString>("constraints"); + QTest::addColumn<QString>("call"); QTest::addColumn<QWebEnginePage::Feature>("feature"); QTest::addRow("device audio") - << "{audio: true}" << QWebEnginePage::MediaAudioCapture; + << "getUserMedia({audio: true})" << QWebEnginePage::MediaAudioCapture; QTest::addRow("device video") - << "{video: true}" << QWebEnginePage::MediaVideoCapture; + << "getUserMedia({video: true})" << QWebEnginePage::MediaVideoCapture; QTest::addRow("device audio+video") - << "{audio: true, video: true}" << QWebEnginePage::MediaAudioVideoCapture; + << "getUserMedia({audio: true, video: true})" << QWebEnginePage::MediaAudioVideoCapture; QTest::addRow("desktop video") - << "{video: { mandatory: { chromeMediaSource: 'desktop' }}}" + << "getUserMedia({video: { mandatory: { chromeMediaSource: 'desktop' }}})" << QWebEnginePage::DesktopVideoCapture; QTest::addRow("desktop audio+video") - << "{audio: { mandatory: { chromeMediaSource: 'desktop' }}, video: { mandatory: { chromeMediaSource: 'desktop' }}}" + << "getUserMedia({audio: { mandatory: { chromeMediaSource: 'desktop' }}, video: { mandatory: { chromeMediaSource: 'desktop' }}})" << QWebEnginePage::DesktopAudioVideoCapture; + QTest::addRow("display video") + << "getDisplayMedia()" << QWebEnginePage::DesktopVideoCapture; } void tst_QWebEnginePage::getUserMediaRequest() { - QFETCH(QString, constraints); + QFETCH(QString, call); QFETCH(QWebEnginePage::Feature, feature); GetUserMediaTestPage page; + QWebEngineView view; if (feature == QWebEnginePage::DesktopVideoCapture || feature == QWebEnginePage::DesktopAudioVideoCapture) { // Desktop capture needs to be on a desktop. - QWebEngineView view; view.setPage(&page); view.resize(640, 480); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); } - QTRY_VERIFY_WITH_TIMEOUT(page.loadSucceeded(), 20000); + QTRY_VERIFY_WITH_TIMEOUT(page.loadSucceeded(), 60000); page.settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true); // 1. Rejecting request on C++ side should reject promise on JS side. - page.jsGetUserMedia(constraints); + page.jsGetMedia(call); QTRY_VERIFY(page.gotFeatureRequest(feature)); page.rejectPendingRequest(); QTRY_VERIFY(!page.jsPromiseFulfilled() && page.jsPromiseRejected()); @@ -1485,13 +1785,13 @@ void tst_QWebEnginePage::getUserMediaRequest() // deeper in the content layer we cannot guarantee that the promise will // always be fulfilled, however in this case an error should be returned to // JS instead of leaving the Promise in limbo. - page.jsGetUserMedia(constraints); + page.jsGetMedia(call); QTRY_VERIFY(page.gotFeatureRequest(feature)); page.acceptPendingRequest(); QTRY_VERIFY(page.jsPromiseFulfilled() || page.jsPromiseRejected()); // 3. Media feature permissions are not remembered. - page.jsGetUserMedia(constraints); + page.jsGetMedia(call); QTRY_VERIFY(page.gotFeatureRequest(feature)); page.acceptPendingRequest(); QTRY_VERIFY(page.jsPromiseFulfilled() || page.jsPromiseRejected()); @@ -1592,9 +1892,9 @@ void tst_QWebEnginePage::savePage() QWebEnginePage *page = view.page(); connect(page->profile(), &QWebEngineProfile::downloadRequested, - [] (QWebEngineDownloadItem *item) + [] (QWebEngineDownloadRequest *item) { - connect(item, &QWebEngineDownloadItem::finished, + connect(item, &QWebEngineDownloadRequest::isFinishedChanged, &QTestEventLoop::instance(), &QTestEventLoop::exitLoop, Qt::QueuedConnection); }); @@ -1608,7 +1908,7 @@ void tst_QWebEnginePage::savePage() // Save the loaded page as HTML. QTemporaryDir tempDir(QDir::tempPath() + "/tst_qwebengineview-XXXXXX"); const QString filePath = tempDir.path() + "/thingumbob.html"; - page->save(filePath, QWebEngineDownloadItem::CompleteHtmlSaveFormat); + page->save(filePath, QWebEngineDownloadRequest::CompleteHtmlSaveFormat); QTestEventLoop::instance().enterLoop(10); // Load something else. @@ -1625,18 +1925,21 @@ void tst_QWebEnginePage::savePage() void tst_QWebEnginePage::openWindowDefaultSize() { TestPage page; + QSignalSpy spyFinished(&page, &QWebEnginePage::loadFinished); QSignalSpy windowCreatedSpy(&page, SIGNAL(windowCreated())); QWebEngineView view; - page.setView(&view); + view.setPage(&page); + page.settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true); + view.setUrl(QUrl("about:blank")); view.show(); + QTRY_COMPARE(spyFinished.size(), 1); - page.settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true); // Open a default window. page.runJavaScript("window.open()"); - QTRY_COMPARE(windowCreatedSpy.count(), 1); + QTRY_COMPARE(windowCreatedSpy.size(), 1); // Open a too small window. evaluateJavaScriptSync(&page, "window.open('','about:blank','width=10,height=10')"); - QTRY_COMPARE(windowCreatedSpy.count(), 2); + QTRY_COMPARE(windowCreatedSpy.size(), 2); // The number of popups created should be two. QCOMPARE(page.createdWindows.size(), 2); @@ -1652,156 +1955,51 @@ void tst_QWebEnginePage::openWindowDefaultSize() QCOMPARE(requestedGeometry.height(), 100); } -class JavaScriptCallbackBase +bool tst_QWebEnginePage::isFalseJavaScriptResult(QWebEnginePage *page, const QString &javaScript) { -public: - JavaScriptCallbackBase() - { - if (watcher) - QMetaObject::invokeMethod(watcher, "add"); - } - - void operator() (const QVariant &result) - { - check(result); - if (watcher) - QMetaObject::invokeMethod(watcher, "notify"); - } - -protected: - virtual void check(const QVariant &result) = 0; - -private: - friend class JavaScriptCallbackWatcher; - static QPointer<QObject> watcher; -}; - -QPointer<QObject> JavaScriptCallbackBase::watcher = 0; - -class JavaScriptCallback : public JavaScriptCallbackBase -{ -public: - JavaScriptCallback() { } - JavaScriptCallback(const QVariant& _expected) : expected(_expected) { } - - void check(const QVariant& result) override - { - QVERIFY(result.isValid()); - QCOMPARE(result, expected); - } - -private: - QVariant expected; -}; - -class JavaScriptCallbackNull : public JavaScriptCallbackBase -{ -public: - void check(const QVariant& result) override - { - QVERIFY(result.isNull()); -// FIXME: Returned null values are currently invalid QVariants. -// QVERIFY(result.isValid()); - } -}; + QVariant result = evaluateJavaScriptSync(page, javaScript); + return !result.isNull() && result.isValid() && result == QVariant(false); +} -class JavaScriptCallbackUndefined : public JavaScriptCallbackBase +bool tst_QWebEnginePage::isTrueJavaScriptResult(QWebEnginePage *page, const QString &javaScript) { -public: - void check(const QVariant& result) override - { - QVERIFY(result.isNull()); - QVERIFY(!result.isValid()); - } -}; + QVariant result = evaluateJavaScriptSync(page, javaScript); + return !result.isNull() && result.isValid() && result == QVariant(true); +} -class JavaScriptCallbackWatcher : public QObject +bool tst_QWebEnginePage::isEmptyListJavaScriptResult(QWebEnginePage *page, const QString &javaScript) { - Q_OBJECT -public: - JavaScriptCallbackWatcher() - { - Q_ASSERT(!JavaScriptCallbackBase::watcher); - JavaScriptCallbackBase::watcher = this; - } - - Q_INVOKABLE void add() - { - available++; - } - - Q_INVOKABLE void notify() - { - called++; - if (called == available) - emit allCalled(); - } - - bool wait(int maxSeconds = 30) - { - if (called == available) - return true; - - QTestEventLoop loop; - connect(this, SIGNAL(allCalled()), &loop, SLOT(exitLoop())); - loop.enterLoop(maxSeconds); - return !loop.timeout(); - } - -signals: - void allCalled(); - -private: - int available = 0; - int called = 0; -}; - + QVariant result = evaluateJavaScriptSync(page, javaScript); + return !result.isNull() && result.isValid() && result == QList<QVariant>(); +} void tst_QWebEnginePage::runJavaScript() { TestPage page; - JavaScriptCallbackWatcher watcher; - - JavaScriptCallback callbackBool(QVariant(false)); - page.runJavaScript("false", QWebEngineCallback<const QVariant&>(callbackBool)); - - JavaScriptCallback callbackInt(QVariant(2)); - page.runJavaScript("2", QWebEngineCallback<const QVariant&>(callbackInt)); - - JavaScriptCallback callbackDouble(QVariant(2.5)); - page.runJavaScript("2.5", QWebEngineCallback<const QVariant&>(callbackDouble)); - - JavaScriptCallback callbackString(QVariant(QStringLiteral("Test"))); - page.runJavaScript("\"Test\"", QWebEngineCallback<const QVariant&>(callbackString)); - - QVariantList list; - JavaScriptCallback callbackList(list); - page.runJavaScript("[]", QWebEngineCallback<const QVariant&>(callbackList)); - + QVariant result; QVariantMap map; - map.insert(QStringLiteral("test"), QVariant(2)); - JavaScriptCallback callbackMap(map); - page.runJavaScript("var el = {\"test\": 2}; el", QWebEngineCallback<const QVariant&>(callbackMap)); - JavaScriptCallbackNull callbackNull; - page.runJavaScript("null", QWebEngineCallback<const QVariant&>(callbackNull)); + QVERIFY(isFalseJavaScriptResult(&page, "false")); + QCOMPARE(evaluateJavaScriptSync(&page, "2").toInt(), 2); + QCOMPARE(evaluateJavaScriptSync(&page, "2.5").toDouble(), 2.5); + QCOMPARE(evaluateJavaScriptSync(&page, "\"Test\"").toString(), "Test"); + QVERIFY(isEmptyListJavaScriptResult(&page, "[]")); - JavaScriptCallbackUndefined callbackUndefined; - page.runJavaScript("undefined", QWebEngineCallback<const QVariant&>(callbackUndefined)); + map.insert(QStringLiteral("test"), QVariant(2)); + QCOMPARE(evaluateJavaScriptSync(&page, "var el = {\"test\": 2}; el").toMap(), map); - JavaScriptCallback callbackDate(QVariant(42.0)); - page.runJavaScript("new Date(42000)", QWebEngineCallback<const QVariant&>(callbackDate)); + QVERIFY(evaluateJavaScriptSync(&page, "null").isNull()); - JavaScriptCallback callbackBlob(QVariant(QByteArray(8, 0))); - page.runJavaScript("new ArrayBuffer(8)", QWebEngineCallback<const QVariant&>(callbackBlob)); + result = evaluateJavaScriptSync(&page, "undefined"); + QVERIFY(result.isNull() && !result.isValid()); - JavaScriptCallbackUndefined callbackFunction; - page.runJavaScript("(function(){})", QWebEngineCallback<const QVariant&>(callbackFunction)); + QCOMPARE(evaluateJavaScriptSync(&page, "new Date(42000)").toDate(), QVariant(42.0).toDate()); + QCOMPARE(evaluateJavaScriptSync(&page, "new ArrayBuffer(8)").toByteArray(), QByteArray(8, 0)); - JavaScriptCallback callbackPromise(QVariant(QVariantMap{})); - page.runJavaScript("new Promise(function(){})", QWebEngineCallback<const QVariant&>(callbackPromise)); + result = evaluateJavaScriptSync(&page, "(function(){})"); + QVERIFY(result.isNull() && !result.isValid()); - QVERIFY(watcher.wait()); + QCOMPARE(evaluateJavaScriptSync(&page, "new Promise(function(){})"), QVariant(QVariantMap{})); } void tst_QWebEnginePage::runJavaScriptDisabled() @@ -1812,7 +2010,7 @@ void tst_QWebEnginePage::runJavaScriptDisabled() // Settings changes take effect asynchronously. The load and wait ensure // that the settings are applied by the time we start to execute JavaScript. page.load(QStringLiteral("about:blank")); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 20000); QCOMPARE(evaluateJavaScriptSyncInWorld(&page, QStringLiteral("1+1"), QWebEngineScript::MainWorld), QVariant()); QCOMPARE(evaluateJavaScriptSyncInWorld(&page, QStringLiteral("1+1"), QWebEngineScript::ApplicationWorld), @@ -1824,42 +2022,39 @@ void tst_QWebEnginePage::runJavaScriptFromSlot() { QWebEngineProfile profile; QWebEnginePage page(&profile); - page.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); QSignalSpy loadFinishedSpy(&page, &QWebEnginePage::loadFinished); page.setHtml("<html><body>" " <input type='text' id='input1' value='QtWebEngine' size='50' />" "</body></html>"); - QTRY_COMPARE(loadFinishedSpy.count(), 1); - // Workaround for QTBUG-74718 - QTRY_VERIFY(page.action(QWebEnginePage::SelectAll)->isEnabled()); + QTRY_COMPARE(loadFinishedSpy.size(), 1); - QVariant result(-1); + bool done = false; connect(&page, &QWebEnginePage::selectionChanged, [&]() { - result = evaluateJavaScriptSync(&page, QStringLiteral("2+2")); + QTRY_COMPARE(evaluateJavaScriptSync(&page, QStringLiteral("2+2")), QVariant(4)); + done = true; }); evaluateJavaScriptSync(&page, QStringLiteral("const input = document.getElementById('input1');" "input.focus();" "input.select();")); - QTRY_COMPARE(result, QVariant(4)); + QTRY_VERIFY(done); } void tst_QWebEnginePage::fullScreenRequested() { - JavaScriptCallbackWatcher watcher; QWebEngineView view; QWebEnginePage* page = view.page(); + view.resize(640, 480); view.show(); page->settings()->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true); QSignalSpy loadSpy(&view, SIGNAL(loadFinished(bool))); page->load(QUrl("qrc:///resources/fullscreen.html")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); - page->runJavaScript("document.webkitFullscreenEnabled", JavaScriptCallback(true)); - page->runJavaScript("document.webkitIsFullScreen", JavaScriptCallback(false)); - QVERIFY(watcher.wait()); + QTRY_VERIFY(isTrueJavaScriptResult(page, "document.webkitFullscreenEnabled")); + QVERIFY(isFalseJavaScriptResult(page, "document.webkitIsFullScreen")); // FullscreenRequest must be a user gesture bool acceptRequest = true; @@ -1869,21 +2064,36 @@ void tst_QWebEnginePage::fullScreenRequested() }); QTest::keyPress(view.focusProxy(), Qt::Key_Space); - QTRY_VERIFY(evaluateJavaScriptSync(page, "document.webkitIsFullScreen").toBool()); - page->runJavaScript("document.webkitExitFullscreen()", JavaScriptCallbackUndefined()); - QVERIFY(watcher.wait()); + QTRY_VERIFY(isTrueJavaScriptResult(page, "document.webkitIsFullScreen")); + + QTest::mouseMove(view.windowHandle(), QPoint(10,10)); + QTest::mouseClick(view.windowHandle(), Qt::RightButton); + QTRY_COMPARE(view.findChildren<QMenu *>().size(), 1); + auto menu = view.findChildren<QMenu *>().first(); + QVERIFY(menu->actions().contains(page->action(QWebEnginePage::ExitFullScreen))); + + page->runJavaScript("document.webkitExitFullscreen()"); + QTRY_VERIFY(isFalseJavaScriptResult(page, "document.webkitIsFullScreen")); acceptRequest = false; - page->runJavaScript("document.webkitFullscreenEnabled", JavaScriptCallback(true)); + QVERIFY(isTrueJavaScriptResult(page, "document.webkitFullscreenEnabled")); QTest::keyPress(view.focusProxy(), Qt::Key_Space); - QVERIFY(watcher.wait()); - page->runJavaScript("document.webkitIsFullScreen", JavaScriptCallback(false)); - QVERIFY(watcher.wait()); + QTRY_VERIFY(isFalseJavaScriptResult(page, "document.webkitIsFullScreen")); } -void tst_QWebEnginePage::quotaRequested() +void tst_QWebEnginePage::requestQuota_data() { + QTest::addColumn<QString>("storage"); + QTest::addRow("webkitPersistentStorage") << "navigator.webkitPersistentStorage"; + QTest::addRow("webkitTemporaryStorage") << "navigator.webkitTemporaryStorage"; + +} + +void tst_QWebEnginePage::requestQuota() +{ + QFETCH(QString, storage); + ConsolePage page; QWebEngineView view; view.setPage(&page); @@ -1891,35 +2101,23 @@ void tst_QWebEnginePage::quotaRequested() page.load(QUrl("qrc:///resources/content.html")); QVERIFY(loadFinishedSpy.wait()); - connect(&page, &QWebEnginePage::quotaRequested, - [] (QWebEngineQuotaRequest request) - { - if (request.requestedSize() <= 5000) - request.accept(); - else - request.reject(); - }); - - evaluateJavaScriptSync(&page, - "navigator.webkitPersistentStorage.requestQuota(1024, function(grantedSize) {" \ - "console.log(grantedSize);" \ - "});"); - QTRY_COMPARE(page.messages.count(), 1); + evaluateJavaScriptSync(&page, QString( + "var storage = %1;" + "storage.requestQuota(1024, function(grantedSize) {" + " console.log(grantedSize);" + "});").arg(storage)); + QTRY_COMPARE(page.messages.size(), 1); QTRY_COMPARE(page.messages[0], QString("1024")); - evaluateJavaScriptSync(&page, - "navigator.webkitPersistentStorage.requestQuota(6000, function(grantedSize) {" \ - "console.log(grantedSize);" \ - "});"); - QTRY_COMPARE(page.messages.count(), 2); - QTRY_COMPARE(page.messages[1], QString("1024")); - - evaluateJavaScriptSync(&page, - "navigator.webkitPersistentStorage.queryUsageAndQuota(function(usedBytes, grantedBytes) {" \ - "console.log(usedBytes + ', ' + grantedBytes);" \ - "});"); - QTRY_COMPARE(page.messages.count(), 3); - QTRY_COMPARE(page.messages[2], QString("0, 1024")); + evaluateJavaScriptSync(&page, QString( + "var storage = %1;" + "storage.queryUsageAndQuota(function(usedBytes, grantedBytes) {" + " console.log(usedBytes);" + " console.log(grantedBytes);" + "});").arg(storage)); + QTRY_COMPARE(page.messages.size(), 3); + QTRY_COMPARE(page.messages[1], QString("0")); + QTRY_VERIFY(page.messages[2].toLongLong() >= 1024); } void tst_QWebEnginePage::symmetricUrl() @@ -1934,13 +2132,14 @@ void tst_QWebEnginePage::symmetricUrl() QUrl dataUrl("data:text/html,<h1>Test"); view.setUrl(dataUrl); + view.show(); QCOMPARE(view.url(), dataUrl); QCOMPARE(view.history()->count(), 0); // loading is _not_ immediate, so the text isn't set just yet. QVERIFY(toPlainTextSync(view.page()).isEmpty()); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 20000); QCOMPARE(view.history()->count(), 1); QCOMPARE(toPlainTextSync(view.page()), QString("Test")); @@ -1954,8 +2153,8 @@ void tst_QWebEnginePage::symmetricUrl() QCOMPARE(view.url(), dataUrl3); // setUrl(dataUrl3) might override the pending load for dataUrl2. Or not. - QTRY_VERIFY(loadFinishedSpy.count() >= 2); - QTRY_VERIFY(loadFinishedSpy.count() <= 3); + QTRY_VERIFY(loadFinishedSpy.size() >= 2); + QTRY_VERIFY(loadFinishedSpy.size() <= 3); // setUrl(dataUrl3) might stop Chromium from adding a navigation entry for dataUrl2, // depending on whether the load of dataUrl2 could be completed in time. @@ -2033,7 +2232,7 @@ public: setAttribute(QNetworkRequest::RedirectionTargetAttribute, QUrl("qrc:/test2.html")); QTimer::singleShot(0, this, SLOT(continueRedirect())); } -#ifndef QT_NO_OPENSSL +#if QT_CONFIG(openssl) else if (request.url() == QUrl("qrc:/fake-ssl-error.html")) { setError(QNetworkReply::SslHandshakeFailedError, tr("Fake error!")); QTimer::singleShot(0, this, SLOT(continueError())); @@ -2054,11 +2253,11 @@ public: { close(); } - virtual void abort() {} - virtual void close() {} + void abort() override {} + void close() override {} protected: - qint64 readData(char*, qint64) + qint64 readData(char*, qint64) override { return 0; } @@ -2072,7 +2271,7 @@ private Q_SLOTS: void continueError() { - emit error(this->error()); + emit errorOccurred(this->error()); emit finished(); } }; @@ -2086,11 +2285,11 @@ public: FakeNetworkManager(QObject* parent) : QNetworkAccessManager(parent) { } protected: - virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, QIODevice* outgoingData) + QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, QIODevice* outgoingData) override { QString url = request.url().toString(); if (op == QNetworkAccessManager::GetOperation) { -#ifndef QT_NO_OPENSSL +#if QT_CONFIG(openssl) if (url == "qrc:/fake-ssl-error.html") { FakeReply* reply = new FakeReply(request, this); QList<QSslError> errors; @@ -2114,7 +2313,7 @@ void tst_QWebEnginePage::requestedUrlAfterSetAndLoadFailures() const QUrl first("http://abcdef.abcdef/"); page.setUrl(first); - QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 20000); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 20000); QCOMPARE(page.url(), first); QCOMPARE(page.requestedUrl(), first); QVERIFY(!spy.at(0).first().toBool()); @@ -2123,8 +2322,8 @@ void tst_QWebEnginePage::requestedUrlAfterSetAndLoadFailures() QVERIFY(first != second); page.load(second); - QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 2, 20000); - QCOMPARE(page.url(), second); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 2, 20000); + QCOMPARE(page.url(), first); QCOMPARE(page.requestedUrl(), second); QVERIFY(!spy.at(1).first().toBool()); } @@ -2169,7 +2368,7 @@ void tst_QWebEnginePage::setHtmlWithImageResource() QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); page.setHtml(html, QUrl("file:///path/to/file")); - QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 12000); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 12000); QCOMPARE(evaluateJavaScriptSync(&page, "document.images.length").toInt(), 1); QCOMPARE(evaluateJavaScriptSync(&page, "document.images[0].width").toInt(), 128); @@ -2178,7 +2377,7 @@ void tst_QWebEnginePage::setHtmlWithImageResource() // Now we test the opposite: without a baseUrl as a local file, we can still request qrc resources. page.setHtml(html); - QTRY_COMPARE(spy.count(), 2); + QTRY_COMPARE(spy.size(), 2); QCOMPARE(evaluateJavaScriptSync(&page, "document.images.length").toInt(), 1); QCOMPARE(evaluateJavaScriptSync(&page, "document.images[0].width").toInt(), 128); QCOMPARE(evaluateJavaScriptSync(&page, "document.images[0].height").toInt(), 128); @@ -2219,10 +2418,15 @@ void tst_QWebEnginePage::setHtmlWithBaseURL() // This tests if baseUrl is indeed affecting the relative paths from resources. // As we are using a local file as baseUrl, its security origin should be able to load local resources. - if (!QDir(TESTS_SOURCE_DIR).exists()) - W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + if (!QDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'") + .arg(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()) + .toLatin1() + .constData(), + SkipAll); - QDir::setCurrent(TESTS_SOURCE_DIR); + QDir::setCurrent(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()); + qDebug()<<QDir::current(); QString html("<html><body><p>hello world</p><img src='resources/image2.png'/></body></html>"); @@ -2231,10 +2435,12 @@ void tst_QWebEnginePage::setHtmlWithBaseURL() // in few seconds, the image should be completey loaded QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); - page.setHtml(html, QUrl::fromLocalFile(TESTS_SOURCE_DIR)); + page.setHtml(html, + QUrl::fromLocalFile( + QString("%1/foo.html").arg(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()))); QSignalSpy spyFinished(&page, &QWebEnginePage::loadFinished); QVERIFY(spyFinished.wait()); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(evaluateJavaScriptSync(&page, "document.images.length").toInt(), 1); QCOMPARE(evaluateJavaScriptSync(&page, "document.images[0].width").toInt(), 128); @@ -2251,7 +2457,7 @@ public: int alerts; protected: - virtual void javaScriptAlert(const QUrl &securityOrigin, const QString &msg) + void javaScriptAlert(const QUrl &securityOrigin, const QString &msg) override { alerts++; QCOMPARE(securityOrigin, QUrl(QStringLiteral("http://test.origin.com/"))); @@ -2270,6 +2476,38 @@ void tst_QWebEnginePage::setHtmlWithJSAlert() QCOMPARE(toHtmlSync(&page), html); } +void tst_QWebEnginePage::setHtmlWithModuleImport() +{ + HttpServer server; + connect(&server, &HttpServer::newRequest, [&](HttpReqRep *rr) { + if (rr->requestMethod() == "GET" && rr->requestPath() == "/fibonacci.mjs") { + rr->setResponseBody("export function fib(n) {\n" + " return n < 2 ? n : fib(n-1) + fib(n-2)\n" + "}\n"); + rr->setResponseHeader("Content-Type", "text/javascript"); + rr->sendResponse(); + } + }); + QVERIFY(server.start()); + + QString html("<html>\n" + " <head>\n" + " <script type='module'>\n" + " import {fib} from './fibonacci.mjs'\n" + " window.fib7 = fib(7)\n" + " </script>\n" + " </head>\n" + " <body></body>\n" + "</html>\n"); + + QWebEnginePage page; + QSignalSpy spy(&page, &QWebEnginePage::loadFinished); + page.setHtml(html, server.url()); + QVERIFY(spy.size() || spy.wait()); + + QCOMPARE(evaluateJavaScriptSync(&page, "fib7"), QVariant(13)); +} + void tst_QWebEnginePage::baseUrl_data() { QTest::addColumn<QString>("html"); @@ -2301,7 +2539,7 @@ void tst_QWebEnginePage::baseUrl() QSignalSpy loadSpy(m_page, SIGNAL(loadFinished(bool))); m_page->setHtml(html, loadUrl); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(m_page->url(), url); QEXPECT_FAIL("null", "Slight change: We now translate QUrl() to about:blank for the virtual url, but not for the baseUrl", Continue); QCOMPARE(baseUrlSync(m_page), baseUrl); @@ -2310,7 +2548,8 @@ void tst_QWebEnginePage::baseUrl() void tst_QWebEnginePage::scrollPosition() { // enlarged image in a small viewport, to provoke the scrollbars to appear - QString html("<html><body><img src='qrc:/image.png' height=500 width=500/></body></html>"); + QString html( + "<html><body><img src='qrc:/resources/image.png' height=500 width=500/></body></html>"); QWebEngineView view; view.setFixedSize(200,200); @@ -2320,12 +2559,12 @@ void tst_QWebEnginePage::scrollPosition() QSignalSpy loadSpy(view.page(), SIGNAL(loadFinished(bool))); view.setHtml(html); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); // try to set the scroll offset programmatically view.page()->runJavaScript("window.scrollTo(23, 29);"); - QTRY_COMPARE(view.page()->scrollPosition().x(), 23 * view.windowHandle()->devicePixelRatio()); - QCOMPARE(view.page()->scrollPosition().y(), 29 * view.windowHandle()->devicePixelRatio()); + QTRY_COMPARE(view.page()->scrollPosition().x(), 23); + QCOMPARE(view.page()->scrollPosition().y(), 29); int x = evaluateJavaScriptSync(view.page(), "window.scrollX").toInt(); int y = evaluateJavaScriptSync(view.page(), "window.scrollY").toInt(); @@ -2346,7 +2585,7 @@ void tst_QWebEnginePage::scrollbarsOff() QSignalSpy loadSpy(&view, SIGNAL(loadFinished(bool))); view.setHtml(html); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QVERIFY(evaluateJavaScriptSync(view.page(), "innerWidth == document.documentElement.offsetWidth").toBool()); } @@ -2357,7 +2596,8 @@ signals: void repaintRequested(); protected: - bool event(QEvent *event) { + bool event(QEvent *event) override + { if (event->type() == QEvent::UpdateRequest) emit repaintRequested(); @@ -2380,7 +2620,7 @@ void tst_QWebEnginePage::evaluateWillCauseRepaint() QSignalSpy loadSpy(&view, SIGNAL(loadFinished(bool))); view.setHtml(html); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); evaluateJavaScriptSync(view.page(), "document.getElementById('junk').style.display = 'none';"); QSignalSpy repaintSpy(&view, &WebView::repaintRequested); @@ -2396,9 +2636,13 @@ void tst_QWebEnginePage::setContent_data() QString str = QString::fromUtf8("ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει"); QTest::newRow("UTF-8 plain text") << "text/plain; charset=utf-8" << str.toUtf8() << str; - QTextCodec *utf16 = QTextCodec::codecForName("UTF-16"); - if (utf16) - QTest::newRow("UTF-16 plain text") << "text/plain; charset=utf-16" << utf16->fromUnicode(str) << str; + QBuffer out16; + out16.open(QIODevice::WriteOnly); + QTextStream stream16(&out16); + stream16.setEncoding(QStringConverter::Utf16); + stream16 << str; + stream16.flush(); + QTest::newRow("UTF-16 plain text") << "text/plain; charset=utf-16" << out16.buffer() << str; str = QString::fromUtf8("Une chaîne de caractères à sa façon."); QTest::newRow("latin-1 plain text") << "text/plain; charset=iso-8859-1" << str.toLatin1() << str; @@ -2425,7 +2669,7 @@ public: { } - virtual QNetworkReply* createRequest(Operation, const QNetworkRequest& request, QIODevice*) + QNetworkReply* createRequest(Operation, const QNetworkRequest& request, QIODevice*) override { QVariant cacheLoad = request.attribute(QNetworkRequest::CacheLoadControlAttribute); if (cacheLoad.isValid()) @@ -2470,7 +2714,7 @@ void tst_QWebEnginePage::setUrlToEmpty() expectedLoadFinishedCount++; QVERIFY(spy.wait()); - QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(spy.size(), expectedLoadFinishedCount); QCOMPARE(page.url(), url); QCOMPARE(page.requestedUrl(), url); QCOMPARE(baseUrlSync(&page), url); @@ -2479,7 +2723,7 @@ void tst_QWebEnginePage::setUrlToEmpty() page.setUrl(QUrl()); expectedLoadFinishedCount++; - QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QTRY_COMPARE(spy.size(), expectedLoadFinishedCount); QCOMPARE(page.url(), aboutBlank); QCOMPARE(page.requestedUrl(), QUrl()); QCOMPARE(baseUrlSync(&page), aboutBlank); @@ -2488,7 +2732,7 @@ void tst_QWebEnginePage::setUrlToEmpty() page.setUrl(url); expectedLoadFinishedCount++; - QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QTRY_COMPARE(spy.size(), expectedLoadFinishedCount); QCOMPARE(page.url(), url); QCOMPARE(page.requestedUrl(), url); QCOMPARE(baseUrlSync(&page), url); @@ -2497,7 +2741,7 @@ void tst_QWebEnginePage::setUrlToEmpty() page.load(QUrl()); expectedLoadFinishedCount++; - QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QTRY_COMPARE(spy.size(), expectedLoadFinishedCount); QCOMPARE(page.url(), aboutBlank); QCOMPARE(page.requestedUrl(), QUrl()); QCOMPARE(baseUrlSync(&page), aboutBlank); @@ -2552,9 +2796,9 @@ void tst_QWebEnginePage::setUrlToBadDomain() page.setUrl(url1); - QTRY_COMPARE(urlSpy.count(), 1); - QTRY_COMPARE_WITH_TIMEOUT(titleSpy.count(), 1, 20000); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(urlSpy.size(), 1); + QTRY_COMPARE_WITH_TIMEOUT(titleSpy.size(), 1, 20000); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), url1); QCOMPARE(titleSpy.takeFirst().value(0).toString(), url1.host()); @@ -2565,9 +2809,9 @@ void tst_QWebEnginePage::setUrlToBadDomain() page.setUrl(url2); - QTRY_COMPARE(urlSpy.count(), 1); - QTRY_COMPARE_WITH_TIMEOUT(titleSpy.count(), 1, 20000); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(urlSpy.size(), 1); + QTRY_COMPARE_WITH_TIMEOUT(titleSpy.size(), 1, 20000); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), url2); QCOMPARE(titleSpy.takeFirst().value(0).toString(), url2.host()); @@ -2591,9 +2835,9 @@ void tst_QWebEnginePage::setUrlToBadPort() page.setUrl(url1); - QTRY_COMPARE(urlSpy.count(), 1); - QTRY_COMPARE(titleSpy.count(), 2); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(urlSpy.size(), 1); + QTRY_COMPARE(titleSpy.size(), 2); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), url1); QCOMPARE(titleSpy.takeFirst().value(0).toString(), url1.authority()); @@ -2605,9 +2849,9 @@ void tst_QWebEnginePage::setUrlToBadPort() page.setUrl(url2); - QTRY_COMPARE(urlSpy.count(), 1); - QTRY_COMPARE(titleSpy.count(), 2); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(urlSpy.size(), 1); + QTRY_COMPARE(titleSpy.size(), 2); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), url2); QCOMPARE(titleSpy.takeFirst().value(0).toString(), url2.authority()); @@ -2638,7 +2882,7 @@ void tst_QWebEnginePage::setUrlHistory() m_page->setUrl(QUrl()); expectedLoadFinishedCount++; - QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QTRY_COMPARE(spy.size(), expectedLoadFinishedCount); QCOMPARE(m_page->url(), aboutBlank); QCOMPARE(m_page->requestedUrl(), QUrl()); // Chromium stores navigation entry for every successful loads. The load of the empty page is committed and stored as about:blank. @@ -2647,7 +2891,7 @@ void tst_QWebEnginePage::setUrlHistory() url = QUrl("http://url.invalid/"); m_page->setUrl(url); expectedLoadFinishedCount++; - QTRY_COMPARE_WITH_TIMEOUT(spy.count(), expectedLoadFinishedCount, 20000); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), expectedLoadFinishedCount, 20000); // When error page is disabled in case of LoadFail the entry of the unavailable page is not stored. // We expect the url of the previously loaded page here. QCOMPARE(m_page->url(), aboutBlank); @@ -2658,14 +2902,14 @@ void tst_QWebEnginePage::setUrlHistory() url = QUrl("qrc:/resources/test1.html"); m_page->setUrl(url); expectedLoadFinishedCount++; - QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QTRY_COMPARE(spy.size(), expectedLoadFinishedCount); QCOMPARE(m_page->url(), url); QCOMPARE(m_page->requestedUrl(), url); QCOMPARE(collectHistoryUrls(m_page->history()), QStringList() << aboutBlank.toString() << QStringLiteral("qrc:/resources/test1.html")); m_page->setUrl(QUrl()); expectedLoadFinishedCount++; - QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QTRY_COMPARE(spy.size(), expectedLoadFinishedCount); QCOMPARE(m_page->url(), aboutBlank); QCOMPARE(m_page->requestedUrl(), QUrl()); // Chromium stores navigation entry for every successful loads. The load of the empty page is committed and stored as about:blank. @@ -2677,7 +2921,7 @@ void tst_QWebEnginePage::setUrlHistory() url = QUrl("qrc:/resources/test1.html"); m_page->setUrl(url); expectedLoadFinishedCount++; - QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QTRY_COMPARE(spy.size(), expectedLoadFinishedCount); QCOMPARE(m_page->url(), url); QCOMPARE(m_page->requestedUrl(), url); // The history count DOES change since the about:blank is in the list. @@ -2690,7 +2934,7 @@ void tst_QWebEnginePage::setUrlHistory() url = QUrl("qrc:/resources/test2.html"); m_page->setUrl(url); expectedLoadFinishedCount++; - QTRY_COMPARE(spy.count(), expectedLoadFinishedCount); + QTRY_COMPARE(spy.size(), expectedLoadFinishedCount); QCOMPARE(m_page->url(), url); QCOMPARE(m_page->requestedUrl(), url); QCOMPARE(collectHistoryUrls(m_page->history()), QStringList() @@ -2705,6 +2949,7 @@ void tst_QWebEnginePage::setUrlUsingStateObject() { QUrl url; QSignalSpy urlChangedSpy(m_page, SIGNAL(urlChanged(QUrl))); + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); int expectedUrlChangeCount = 0; QCOMPARE(m_page->history()->count(), 0); @@ -2712,20 +2957,22 @@ void tst_QWebEnginePage::setUrlUsingStateObject() url = QUrl("qrc:/resources/test1.html"); m_page->setUrl(url); expectedUrlChangeCount++; - QTRY_COMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QTRY_COMPARE(urlChangedSpy.size(), expectedUrlChangeCount); + QCOMPARE(m_page->url(), url); + QTRY_COMPARE(loadFinishedSpy.size(), 1); QCOMPARE(m_page->url(), url); - QTRY_COMPARE(m_page->history()->count(), 1); + QCOMPARE(m_page->history()->count(), 1); evaluateJavaScriptSync(m_page, "window.history.pushState(null, 'push', 'navigate/to/here')"); expectedUrlChangeCount++; - QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QTRY_COMPARE(urlChangedSpy.size(), expectedUrlChangeCount); QCOMPARE(m_page->url(), QUrl("qrc:/resources/navigate/to/here")); QCOMPARE(m_page->history()->count(), 2); QVERIFY(m_page->history()->canGoBack()); evaluateJavaScriptSync(m_page, "window.history.replaceState(null, 'replace', 'another/location')"); expectedUrlChangeCount++; - QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QTRY_COMPARE(urlChangedSpy.size(), expectedUrlChangeCount); QCOMPARE(m_page->url(), QUrl("qrc:/resources/navigate/to/another/location")); QCOMPARE(m_page->history()->count(), 2); QVERIFY(!m_page->history()->canGoForward()); @@ -2733,7 +2980,7 @@ void tst_QWebEnginePage::setUrlUsingStateObject() evaluateJavaScriptSync(m_page, "window.history.back()"); expectedUrlChangeCount++; - QTRY_COMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QTRY_COMPARE(urlChangedSpy.size(), expectedUrlChangeCount); QCOMPARE(m_page->url(), QUrl("qrc:/resources/test1.html")); QVERIFY(m_page->history()->canGoForward()); QVERIFY(!m_page->history()->canGoBack()); @@ -2762,9 +3009,9 @@ void tst_QWebEnginePage::setUrlThenLoads() QSignalSpy finishedSpy(m_page, SIGNAL(loadFinished(bool))); m_page->setUrl(url); - QTRY_COMPARE(startedSpy.count(), 1); - QTRY_COMPARE(urlChangedSpy.count(), 1); - QTRY_COMPARE(finishedSpy.count(), 1); + QTRY_COMPARE(startedSpy.size(), 1); + QTRY_COMPARE(urlChangedSpy.size(), 1); + QTRY_COMPARE(finishedSpy.size(), 1); QVERIFY(finishedSpy.at(0).first().toBool()); QCOMPARE(m_page->url(), url); QCOMPARE(m_page->requestedUrl(), url); @@ -2774,49 +3021,35 @@ void tst_QWebEnginePage::setUrlThenLoads() const QUrl urlToLoad2("qrc:/resources/test1.html"); m_page->load(urlToLoad1); - QCOMPARE(m_page->url(), urlToLoad1); - QCOMPARE(m_page->requestedUrl(), urlToLoad1); + QTRY_COMPARE(m_page->url(), urlToLoad1); + QTRY_COMPARE(m_page->requestedUrl(), urlToLoad1); // baseUrlSync spins an event loop and this sometimes return the next result. // QCOMPARE(baseUrlSync(m_page), baseUrl); - QTRY_COMPARE(startedSpy.count(), 2); + QTRY_COMPARE(startedSpy.size(), 2); // After first URL changed. - QTRY_COMPARE(urlChangedSpy.count(), 2); - QTRY_COMPARE(finishedSpy.count(), 2); + QTRY_COMPARE(urlChangedSpy.size(), 2); + QTRY_COMPARE(finishedSpy.size(), 2); QVERIFY(finishedSpy.at(1).first().toBool()); QCOMPARE(m_page->url(), urlToLoad1); QCOMPARE(m_page->requestedUrl(), urlToLoad1); QCOMPARE(baseUrlSync(m_page), extractBaseUrl(urlToLoad1)); m_page->load(urlToLoad2); - QCOMPARE(m_page->url(), urlToLoad2); + QCOMPARE(m_page->url(), urlToLoad1); QCOMPARE(m_page->requestedUrl(), urlToLoad2); QCOMPARE(baseUrlSync(m_page), extractBaseUrl(urlToLoad1)); - QTRY_COMPARE(startedSpy.count(), 3); + QTRY_COMPARE(startedSpy.size(), 3); // After second URL changed. - QTRY_COMPARE(urlChangedSpy.count(), 3); - QTRY_COMPARE(finishedSpy.count(), 3); + QTRY_COMPARE(urlChangedSpy.size(), 3); + QTRY_COMPARE(finishedSpy.size(), 3); QVERIFY(finishedSpy.at(2).first().toBool()); QCOMPARE(m_page->url(), urlToLoad2); QCOMPARE(m_page->requestedUrl(), urlToLoad2); QCOMPARE(baseUrlSync(m_page), extractBaseUrl(urlToLoad2)); } -void tst_QWebEnginePage::loadFinishedAfterNotFoundError() -{ - QWebEnginePage page; - QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); - - page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); - page.setUrl(QUrl("http://non.existent/url")); - QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 20000); - - page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, true); - page.setUrl(QUrl("http://another.non.existent/url")); - QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 2, 20000); -} - class URLSetter : public QObject { Q_OBJECT @@ -2898,11 +3131,7 @@ void tst_QWebEnginePage::loadInSignalHandlers() URLSetter setter(m_page, signal, type, urlForSetter); QSignalSpy spy(&setter, &URLSetter::finished); m_page->load(url); - // every loadStarted() call should have also loadFinished() - if (signal == URLSetter::LoadStarted) - QTRY_COMPARE(spy.count(), 2); - else - QTRY_COMPARE(spy.count(), 1); + QTRY_VERIFY_WITH_TIMEOUT(spy.size() >= 1, 20000); QCOMPARE(m_page->url(), urlForSetter); } @@ -2913,31 +3142,31 @@ void tst_QWebEnginePage::loadFromQrc() // Standard case. page.load(QStringLiteral("qrc:///resources/foo.txt")); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().value(0).toBool(), true); QCOMPARE(toPlainTextSync(&page), QStringLiteral("foo\n")); // Query and fragment parts are ignored. page.load(QStringLiteral("qrc:///resources/bar.txt?foo=1#bar")); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().value(0).toBool(), true); QCOMPARE(toPlainTextSync(&page), QStringLiteral("bar\n")); // Literal spaces are OK. page.load(QStringLiteral("qrc:///resources/path with spaces.txt")); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().value(0).toBool(), true); QCOMPARE(toPlainTextSync(&page), QStringLiteral("contents with spaces\n")); // Escaped spaces are OK too. page.load(QStringLiteral("qrc:///resources/path%20with%20spaces.txt")); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().value(0).toBool(), true); QCOMPARE(toPlainTextSync(&page), QStringLiteral("contents with spaces\n")); // Resource not found, loading fails. page.load(QStringLiteral("qrc:///nope")); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 10000); QCOMPARE(spy.takeFirst().value(0).toBool(), false); } @@ -2954,7 +3183,7 @@ void tst_QWebEnginePage::restoreHistory() QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); page.load(QUrl(QStringLiteral("qrc:/resources/test1.html"))); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE(spy.size(), 1); QCOMPARE(page.webChannel(), &channel); QVERIFY(page.scripts().contains(script)); @@ -2964,7 +3193,7 @@ void tst_QWebEnginePage::restoreHistory() out << *page.history(); QDataStream in(&data, QIODevice::ReadOnly); in >> *page.history(); - QTRY_COMPARE(spy.count(), 2); + QTRY_COMPARE(spy.size(), 2); QCOMPARE(page.webChannel(), &channel); QVERIFY(page.scripts().contains(script)); @@ -2987,42 +3216,59 @@ void tst_QWebEnginePage::toPlainTextLoadFinishedRace() QSignalSpy spy(page.data(), SIGNAL(loadFinished(bool))); page->load(QUrl("data:text/plain,foobarbaz")); - QTRY_VERIFY(spy.count() == 1); + QTRY_VERIFY(spy.size() == 1); QCOMPARE(toPlainTextSync(page.data()), QString("foobarbaz")); page->load(QUrl("http://fail.invalid/")); - QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 2, 20000); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 2, 20000); QString s = toPlainTextSync(page.data()); QVERIFY(s.contains("foobarbaz") == !enableErrorPage); page->load(QUrl("data:text/plain,lalala")); - QTRY_COMPARE(spy.count(), 3); + QTRY_COMPARE(spy.size(), 3); QTRY_COMPARE(toPlainTextSync(page.data()), QString("lalala")); page.reset(); - QCOMPARE(spy.count(), 3); + QCOMPARE(spy.size(), 3); } void tst_QWebEnginePage::setZoomFactor() { - QWebEnginePage page; + TestBasePage page, page2; - QVERIFY(qFuzzyCompare(page.zoomFactor(), 1.0)); + QCOMPARE(page.zoomFactor(), 1.0); page.setZoomFactor(2.5); - QVERIFY(qFuzzyCompare(page.zoomFactor(), 2.5)); - - const QUrl urlToLoad("qrc:/resources/test1.html"); - - QSignalSpy finishedSpy(&page, SIGNAL(loadFinished(bool))); - page.load(urlToLoad); - QTRY_COMPARE(finishedSpy.count(), 1); - QVERIFY(finishedSpy.at(0).first().toBool()); - QVERIFY(qFuzzyCompare(page.zoomFactor(), 2.5)); - - page.setZoomFactor(5.5); - QVERIFY(qFuzzyCompare(page.zoomFactor(), 2.5)); + QCOMPARE(page.zoomFactor(), 2.5); + + const QUrl url1("qrc:/resources/test1.html"), url2(QUrl("qrc:/resources/test2.html")); + + page.load(url1); + QTRY_COMPARE(page.loadSpy.size(), 1); + QVERIFY(page.loadSpy.at(0).first().toBool()); + QCOMPARE(page.zoomFactor(), 2.5); + + page.setZoomFactor(5.5); // max accepted zoom: kMaximumPageZoomFactor = 5.0 + QCOMPARE(page.zoomFactor(), 2.5); + + page.setZoomFactor(0.1); // min accepted zoom: kMinimumPageZoomFactor = 0.25 + QCOMPARE(page.zoomFactor(), 2.5); + + // try loading different url and check new values after load + page.loadSpy.clear(); + for (auto &&p : { + qMakePair(&page, 2.5), // navigating away to different url should keep zoom + qMakePair(&page2, 1.0), // same url navigation in diffent page shouldn't be affected + }) { + auto &&page = *p.first; auto zoomFactor = p.second; + page.load(url2); + QTRY_COMPARE(page.loadSpy.size(), 1); + QVERIFY(page.loadSpy.last().first().toBool()); + QCOMPARE(page.zoomFactor(), zoomFactor); + } - page.setZoomFactor(0.1); - QVERIFY(qFuzzyCompare(page.zoomFactor(), 2.5)); + // should have no influence on first page + page2.setZoomFactor(3.5); + for (auto &&p : { qMakePair(&page, 2.5), qMakePair(&page2, 3.5), }) + QCOMPARE(p.first->zoomFactor(), p.second); } void tst_QWebEnginePage::mouseButtonTranslation() @@ -3042,17 +3288,20 @@ void tst_QWebEnginePage::mouseButtonTranslation() view.resize(640, 480); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); - QTRY_VERIFY(spy.count() == 1); + QTRY_VERIFY(spy.size() == 1); QVERIFY(view.focusProxy() != nullptr); - QMouseEvent evpres(QEvent::MouseButtonPress, view.rect().center(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + const QPoint mousePos = view.rect().center(); + QMouseEvent evpres(QEvent::MouseButtonPress, mousePos, view.mapToGlobal(mousePos), + Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QGuiApplication::sendEvent(view.focusProxy(), &evpres); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "lastEvent.button").toInt(), 0); QCOMPARE(evaluateJavaScriptSync(view.page(), "lastEvent.buttons").toInt(), 1); - QMouseEvent evpres2(QEvent::MouseButtonPress, view.rect().center(), Qt::RightButton, Qt::LeftButton | Qt::RightButton, Qt::NoModifier); + QMouseEvent evpres2(QEvent::MouseButtonPress, mousePos, view.mapToGlobal(mousePos), + Qt::RightButton, Qt::LeftButton | Qt::RightButton, Qt::NoModifier); QGuiApplication::sendEvent(view.focusProxy(), &evpres2); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "lastEvent.button").toInt(), 2); @@ -3081,34 +3330,17 @@ void tst_QWebEnginePage::mouseMovementProperties() loadFinishedSpy.wait(); QTest::mouseMove(&view, QPoint(20, 20)); - QTRY_COMPARE(page.messages.count(), 1); + QTRY_COMPARE(page.messages.size(), 1); QTest::mouseMove(&view, QPoint(30, 30)); - QTRY_COMPARE(page.messages.count(), 2); + QTRY_COMPARE(page.messages.size(), 2); QTRY_COMPARE(page.messages[1], QString("10, 10")); QTest::mouseMove(&view, QPoint(20, 20)); - QTRY_COMPARE(page.messages.count(), 3); + QTRY_COMPARE(page.messages.size(), 3); QTRY_COMPARE(page.messages[2], QString("-10, -10")); } -QPoint tst_QWebEnginePage::elementCenter(QWebEnginePage *page, const QString &id) -{ - QVariantList rectList = evaluateJavaScriptSync(page, - "(function(){" - "var elem = document.getElementById('" + id + "');" - "var rect = elem.getBoundingClientRect();" - "return [(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2];" - "})()").toList(); - - if (rectList.count() != 2) { - qWarning("elementCenter failed."); - return QPoint(); - } - - return QPoint(rectList.at(0).toInt(), rectList.at(1).toInt()); -} - void tst_QWebEnginePage::viewSource() { TestPage page; @@ -3117,12 +3349,12 @@ void tst_QWebEnginePage::viewSource() const QUrl url("qrc:/resources/test1.html"); page.load(url); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); QCOMPARE(page.title(), QStringLiteral("Test page 1")); QVERIFY(page.action(QWebEnginePage::ViewSource)->isEnabled()); page.triggerAction(QWebEnginePage::ViewSource); - QTRY_COMPARE(windowCreatedSpy.count(), 1); + QTRY_COMPARE(windowCreatedSpy.size(), 1); QCOMPARE(page.createdWindows.size(), 1); QTRY_COMPARE(page.createdWindows[0]->url().toString(), QStringLiteral("view-source:%1").arg(url.toString())); @@ -3143,7 +3375,8 @@ void tst_QWebEnginePage::viewSourceURL_data() QTest::newRow("view-source:") << QUrl("view-source:") << true << QUrl("view-source:") << QUrl("about:blank") << QString("view-source:"); QTest::newRow("view-source:about:blank") << QUrl("view-source:about:blank") << true << QUrl("view-source:about:blank") << QUrl("about:blank") << QString("view-source:about:blank"); - QString localFilePath = QString("%1qwebenginepage/resources/test1.html").arg(TESTS_SOURCE_DIR); + QString localFilePath = + QString("%1/resources/test1.html").arg(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()); QUrl testLocalUrl = QUrl(QString("view-source:%1").arg(QUrl::fromLocalFile(localFilePath).toString())); QUrl testLocalUrlWithoutScheme = QUrl(QString("view-source:%1").arg(localFilePath)); QTest::newRow(testLocalUrl.toString().toStdString().c_str()) << testLocalUrl << true << testLocalUrl << QUrl::fromLocalFile(localFilePath) << QString("test1.html"); @@ -3159,8 +3392,12 @@ void tst_QWebEnginePage::viewSourceURL_data() void tst_QWebEnginePage::viewSourceURL() { - if (!QDir(TESTS_SOURCE_DIR).exists()) - W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + if (!QDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'") + .arg(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()) + .toLatin1() + .constData(), + SkipAll); QFETCH(QUrl, userInputUrl); QFETCH(bool, loadSucceed); @@ -3172,7 +3409,7 @@ void tst_QWebEnginePage::viewSourceURL() QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); page.load(userInputUrl); - QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 12000); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 12000); QList<QVariant> arguments = loadFinishedSpy.takeFirst(); QCOMPARE(arguments.at(0).toBool(), loadSucceed); @@ -3207,7 +3444,7 @@ void tst_QWebEnginePage::viewSourceCredentials() QVERIFY(page.action(QWebEnginePage::ViewSource)->isEnabled()); page.triggerAction(QWebEnginePage::ViewSource); - QTRY_COMPARE(windowCreatedSpy.count(), 1); + QTRY_COMPARE(windowCreatedSpy.size(), 1); QCOMPARE(page.createdWindows.size(), 1); QTRY_COMPARE(page.createdWindows[0]->url().toString(), QString("view-source:" + url.toDisplayString(QUrl::RemoveUserInfo))); @@ -3229,7 +3466,7 @@ void tst_QWebEnginePage::proxyConfigWithUnexpectedHostPortPair() QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); m_page->load(QStringLiteral("http://127.0.0.1:245/")); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); } void tst_QWebEnginePage::registerProtocolHandler_data() @@ -3252,19 +3489,17 @@ void tst_QWebEnginePage::registerProtocolHandler() } else if (rr->requestMethod() == "GET" && rr->requestPath() == "/mail?uri=mailto%3Afoo%40bar.com") { mailRequestCount++; rr->sendResponse(); - } else { - rr->setResponseStatus(404); - rr->sendResponse(); } }); QVERIFY(server.start()); - QWebEnginePage page; + QWebEngineProfile profile(QStringLiteral("registerProtocolHandler%1").arg(QTest::currentDataTag())); + QWebEnginePage page(&profile, nullptr); QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished); QSignalSpy permissionSpy(&page, &QWebEnginePage::registerProtocolHandlerRequested); page.setUrl(server.url("/")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true); QString callFormat = QStringLiteral("window.navigator.registerProtocolHandler(\"%1\", \"%2\", \"%3\")"); @@ -3274,7 +3509,7 @@ void tst_QWebEnginePage::registerProtocolHandler() QString call = callFormat.arg(scheme).arg(url).arg(title); page.runJavaScript(call); - QTRY_COMPARE(permissionSpy.count(), 1); + QTRY_COMPARE(permissionSpy.size(), 1); auto request = permissionSpy.takeFirst().value(0).value<QWebEngineRegisterProtocolHandlerRequest>(); QCOMPARE(request.origin(), QUrl(url)); QCOMPARE(request.scheme(), scheme); @@ -3285,7 +3520,7 @@ void tst_QWebEnginePage::registerProtocolHandler() page.runJavaScript(QStringLiteral("document.getElementById(\"link\").click()")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0).toBool(), permission); QCOMPARE(mailRequestCount, permission ? 1 : 0); QVERIFY(server.stop()); @@ -3296,24 +3531,14 @@ void tst_QWebEnginePage::dataURLFragment() m_view->resize(800, 600); m_view->show(); QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); - - m_page->setHtml("<html><body>" - "<a id='link' href='#anchor'>anchor</a>" - "</body></html>"); - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QSignalSpy urlChangedSpy(m_page, SIGNAL(urlChanged(QUrl))); - QTest::mouseClick(m_view->focusProxy(), Qt::LeftButton, 0, elementCenter(m_page, "link")); - QVERIFY(urlChangedSpy.wait()); - QCOMPARE(m_page->url().fragment(), QStringLiteral("anchor")); - m_page->setHtml("<html><body>" "<a id='link' href='#anchor'>anchor</a>" "</body></html>", QUrl("http://test.qt.io/mytest.html")); - QTRY_COMPARE(loadFinishedSpy.count(), 2); + QTRY_COMPARE(loadFinishedSpy.size(), 1); - QTest::mouseClick(m_view->focusProxy(), Qt::LeftButton, 0, elementCenter(m_page, "link")); + QTest::mouseClick(m_view->focusProxy(), Qt::LeftButton, {}, elementCenter(m_page, "link")); QVERIFY(urlChangedSpy.wait()); QCOMPARE(m_page->url(), QUrl("http://test.qt.io/mytest.html#anchor")); } @@ -3335,7 +3560,7 @@ void tst_QWebEnginePage::devTools() QCOMPARE(devToolsPage.devToolsPage(), nullptr); QCOMPARE(devToolsPage.inspectedPage(), &inspectedPage1); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 90000); QVERIFY(spy.takeFirst().value(0).toBool()); devToolsPage.setInspectedPage(&inspectedPage2); @@ -3347,7 +3572,7 @@ void tst_QWebEnginePage::devTools() QCOMPARE(devToolsPage.devToolsPage(), nullptr); QCOMPARE(devToolsPage.inspectedPage(), &inspectedPage2); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 90000); QVERIFY(spy.takeFirst().value(0).toBool()); devToolsPage.setInspectedPage(nullptr); @@ -3358,19 +3583,20 @@ void tst_QWebEnginePage::devTools() QCOMPARE(inspectedPage2.inspectedPage(), nullptr); QCOMPARE(devToolsPage.devToolsPage(), nullptr); QCOMPARE(devToolsPage.inspectedPage(), nullptr); + + QVERIFY(!inspectedPage1.devToolsId().isEmpty()); } void tst_QWebEnginePage::openLinkInDifferentProfile() { - class Page : public QWebEnginePage { - public: - QWebEnginePage *targetPage = nullptr; - Page(QWebEngineProfile *profile) : QWebEnginePage(profile) {} - private: - QWebEnginePage *createWindow(WebWindowType) override { return targetPage; } - }; + QWebEnginePage *targetPage = nullptr; QWebEngineProfile profile1, profile2; - Page page1(&profile1), page2(&profile2); + profile1.installUrlSchemeHandler("echo", new EchoingUrlSchemeHandler(&profile1)); + profile2.installUrlSchemeHandler("echo", new EchoingUrlSchemeHandler(&profile2)); + QWebEnginePage page1(&profile1), page2(&profile2); + connect(&page1, &QWebEnginePage::newWindowRequested, [&](QWebEngineNewWindowRequest &request) { + request.openIn(targetPage); + }); QWebEngineView view; view.resize(500, 500); view.setPage(&page1); @@ -3378,16 +3604,176 @@ void tst_QWebEnginePage::openLinkInDifferentProfile() QVERIFY(QTest::qWaitForWindowExposed(&view)); QSignalSpy spy1(&page1, &QWebEnginePage::loadFinished), spy2(&page2, &QWebEnginePage::loadFinished); page1.setHtml("<html><body>" - "<a id='link' href='data:,hello'>link</a>" - "</body></html>"); - QTRY_COMPARE(spy1.count(), 1); + "<a id='link' href='hello'>link</a>" + "</body></html>", QUrl("echo:/")); + QTRY_COMPARE(spy1.size(), 1); QVERIFY(spy1.takeFirst().value(0).toBool()); - page1.targetPage = &page2; - QTest::mouseClick(view.focusProxy(), Qt::MiddleButton, 0, elementCenter(&page1, "link")); - QTRY_COMPARE(spy2.count(), 1); + targetPage = &page2; + QTest::mouseClick(view.focusProxy(), Qt::MiddleButton, {}, elementCenter(&page1, "link")); + QTRY_COMPARE(spy2.size(), 1); QVERIFY(spy2.takeFirst().value(0).toBool()); } +// What does createWindow do? +enum class OpenLinkInNewPageDecision { + // Returns nullptr, + ReturnNull, + // Returns this, + ReturnSelf, + // Returns page != this + ReturnOther, +}; + +// What causes createWindow to be called? +enum class OpenLinkInNewPageCause { + // User clicks on a link with target=_blank. + TargetBlank, + // User clicks with MiddleButton. + MiddleClick, +}; + +// What happens after createWindow? +enum class OpenLinkInNewPageEffect { + // The navigation request disappears into the ether. + Blocked, + // The navigation request becomes a navigation in the original page. + LoadInSelf, + // The navigation request becomes a navigation in a different page. + LoadInOther, +}; + +Q_DECLARE_METATYPE(OpenLinkInNewPageCause) +Q_DECLARE_METATYPE(OpenLinkInNewPageDecision) +Q_DECLARE_METATYPE(OpenLinkInNewPageEffect) + +void tst_QWebEnginePage::openLinkInNewPage_data() +{ + using Decision = OpenLinkInNewPageDecision; + using Cause = OpenLinkInNewPageCause; + using Effect = OpenLinkInNewPageEffect; + + QTest::addColumn<Decision>("decision"); + QTest::addColumn<Cause>("cause"); + QTest::addColumn<Effect>("effect"); + + // Note that the meaning of returning nullptr from createWindow is not + // consistent between the TargetBlank and MiddleClick scenarios. + // + // With TargetBlank, the open-in-new-page disposition comes from the HTML + // target attribute; something the user is probably not aware of. Returning + // nullptr is interpreted as a decision by the app to block an unwanted + // popup. + // + // With MiddleClick, the open-in-new-page disposition comes from the user's + // explicit intent. Returning nullptr is then interpreted as a failure by + // the app to fulfill this intent, which we try to compensate by ignoring + // the disposition and performing the navigation request normally. + + QTest::newRow("BlockPopup") << Decision::ReturnNull << Cause::TargetBlank << Effect::Blocked; + QTest::newRow("IgnoreIntent") << Decision::ReturnNull << Cause::MiddleClick << Effect::Blocked; + QTest::newRow("OverridePopup") << Decision::ReturnSelf << Cause::TargetBlank << Effect::LoadInSelf; + QTest::newRow("OverrideIntent") << Decision::ReturnSelf << Cause::MiddleClick << Effect::LoadInSelf; + QTest::newRow("AcceptPopup") << Decision::ReturnOther << Cause::TargetBlank << Effect::LoadInOther; + QTest::newRow("AcceptIntent") << Decision::ReturnOther << Cause::MiddleClick << Effect::LoadInOther; +} + +void tst_QWebEnginePage::openLinkInNewPage() +{ + using Decision = OpenLinkInNewPageDecision; + using Cause = OpenLinkInNewPageCause; + using Effect = OpenLinkInNewPageEffect; + + class Page : public QWebEnginePage + { + public: + Page *targetPage = nullptr; + QSignalSpy spy{this, &QWebEnginePage::loadFinished}; + Page(QWebEngineProfile *profile) : QWebEnginePage(profile) {} + private: + QWebEnginePage *createWindow(WebWindowType) override { return targetPage; } + }; + + class View : public QWebEngineView + { + public: + View(Page *page) + { + resize(500, 500); + setPage(page); + } + }; + + QFETCH(Decision, decision); + QFETCH(Cause, cause); + QFETCH(Effect, effect); + + QWebEngineProfile profile; + profile.installUrlSchemeHandler("echo", new EchoingUrlSchemeHandler(&profile)); + Page page1(&profile); + Page page2(&profile); + View view1(&page1); + View view2(&page2); + + view1.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view1)); + + page1.setHtml("<html><body>" + "<a id='link' href='hello' target='_blank'>link</a>" + "</body></html>", QUrl("echo:/")); + QTRY_COMPARE(page1.spy.size(), 1); + QVERIFY(page1.spy.takeFirst().value(0).toBool()); + + switch (decision) { + case Decision::ReturnNull: + page1.targetPage = nullptr; + break; + case Decision::ReturnSelf: + page1.targetPage = &page1; + break; + case Decision::ReturnOther: + page1.targetPage = &page2; + break; + } + + Qt::MouseButton button = Qt::NoButton; + switch (cause) { + case Cause::TargetBlank: + button = Qt::LeftButton; + break; + case Cause::MiddleClick: + button = Qt::MiddleButton; + break; + } + QTest::mouseClick(view1.focusProxy(), button, {}, elementCenter(&page1, "link")); + + switch (effect) { + case Effect::Blocked: + // Test nothing new loaded + QTest::qWait(500); + QCOMPARE(page1.spy.size(), 0); + QCOMPARE(page2.spy.size(), 0); + break; + case Effect::LoadInSelf: + QTRY_COMPARE(page1.spy.size(), 1); + QVERIFY(page1.spy.takeFirst().value(0).toBool()); + QCOMPARE(page2.spy.size(), 0); + if (decision == Decision::ReturnSelf && cause == Cause::TargetBlank) + // History was discarded due to AddNewContents + QCOMPARE(page1.history()->count(), 1); + else + QCOMPARE(page1.history()->count(), 2); + QCOMPARE(page2.history()->count(), 0); + break; + case Effect::LoadInOther: + QTRY_COMPARE(page2.spy.size(), 1); + QVERIFY(page2.spy.takeFirst().value(0).toBool()); + QCOMPARE(page1.spy.size(), 0); + QCOMPARE(page1.history()->count(), 1); + QCOMPARE(page2.history()->count(), 1); + break; + } +} + void tst_QWebEnginePage::triggerActionWithoutMenu() { // Calling triggerAction should not crash even when for @@ -3403,7 +3789,7 @@ void tst_QWebEnginePage::dynamicFrame() page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); QSignalSpy spy(&page, &QWebEnginePage::loadFinished); page.load(QStringLiteral("qrc:/resources/dynamicFrame.html")); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE(spy.size(), 1); QCOMPARE(toPlainTextSync(&page).trimmed(), QStringLiteral("foo")); } @@ -3439,30 +3825,62 @@ public: } }; -void tst_QWebEnginePage::notificationRequest_data() +void tst_QWebEnginePage::notificationPermission_data() { + QTest::addColumn<bool>("setOnInit"); QTest::addColumn<QWebEnginePage::PermissionPolicy>("policy"); QTest::addColumn<QString>("permission"); - QTest::newRow("deny") << QWebEnginePage::PermissionDeniedByUser << "denied"; - QTest::newRow("grant") << QWebEnginePage::PermissionGrantedByUser << "granted"; + QTest::newRow("denyOnInit") << true << QWebEnginePage::PermissionDeniedByUser << "denied"; + QTest::newRow("deny") << false << QWebEnginePage::PermissionDeniedByUser << "denied"; + QTest::newRow("grant") << false << QWebEnginePage::PermissionGrantedByUser << "granted"; + QTest::newRow("grantOnInit") << true << QWebEnginePage::PermissionGrantedByUser << "granted"; } -void tst_QWebEnginePage::notificationRequest() +void tst_QWebEnginePage::notificationPermission() { + QFETCH(bool, setOnInit); QFETCH(QWebEnginePage::PermissionPolicy, policy); QFETCH(QString, permission); - NotificationPage page(policy); - QVERIFY(page.spyLoad.waitForResult()); + QWebEngineProfile otr; + otr.setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions); + QWebEnginePage page(&otr, nullptr); - page.resetPermission(); - QCOMPARE(page.getPermission(), "default"); + QUrl baseUrl("https://www.example.com/somepage.html"); - page.requestPermission(); - page.spyRequest.waitForResult(); - QVERIFY(page.spyRequest.wasCalled()); + bool permissionRequested = false, errorState = false; + connect(&page, &QWebEnginePage::featurePermissionRequested, &page, [&] (const QUrl &o, QWebEnginePage::Feature f) { + if (f != QWebEnginePage::Notifications) + return; + if (permissionRequested || o != baseUrl.url(QUrl::RemoveFilename)) { + qWarning() << "Unexpected case. Can't proceed." << setOnInit << permissionRequested << o; + errorState = true; + return; + } + permissionRequested = true; + page.setFeaturePermission(o, f, policy); + }); + + if (setOnInit) + page.setFeaturePermission(baseUrl, QWebEnginePage::Notifications, policy); - QCOMPARE(page.getPermission(), permission); + QSignalSpy spy(&page, &QWebEnginePage::loadFinished); + page.setHtml(QString("<html><body>Test</body></html>"), baseUrl); + QTRY_COMPARE(spy.size(), 1); + + QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("Notification.permission")), setOnInit ? permission : QLatin1String("default")); + + if (!setOnInit) { + page.setFeaturePermission(baseUrl, QWebEnginePage::Notifications, policy); + QTRY_COMPARE(evaluateJavaScriptSync(&page, QStringLiteral("Notification.permission")), permission); + } + + auto js = QStringLiteral("var permission; Notification.requestPermission().then(p => { permission = p })"); + evaluateJavaScriptSync(&page, js); + QTRY_COMPARE(evaluateJavaScriptSync(&page, "permission").toString(), permission); + // permission is not 'remembered' from api standpoint, hence is not suppressed on explicit call from JS + QVERIFY(permissionRequested); + QVERIFY(!errorState); } void tst_QWebEnginePage::sendNotification() @@ -3506,6 +3924,151 @@ void tst_QWebEnginePage::sendNotification() QTRY_VERIFY2(page.messages.contains("onclose"), page.messages.join("\n").toLatin1().constData()); } +static QString clipboardPermissionQuery(QString variableName, QString permissionName) +{ + return QString("var %1; navigator.permissions.query({ name:'%2' }).then((p) => { %1 = p.state; " + "});") + .arg(variableName) + .arg(permissionName); +} + + +void tst_QWebEnginePage::clipboardReadWritePermissionInitialState_data() +{ + QTest::addColumn<bool>("canAccessClipboard"); + QTest::addColumn<bool>("canPaste"); + QTest::addColumn<QString>("permission"); + QTest::newRow("access and paste should grant") << true << true << "granted"; + QTest::newRow("no access should prompt") << false << true << "prompt"; + QTest::newRow("no paste should prompt") << true << false << "prompt"; + QTest::newRow("no access or paste should prompt") << false << false << "prompt"; +} + +void tst_QWebEnginePage::clipboardReadWritePermissionInitialState() +{ + QFETCH(bool, canAccessClipboard); + QFETCH(bool, canPaste); + QFETCH(QString, permission); + + QWebEngineProfile otr; + otr.setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions); + QWebEngineView view(&otr); + QWebEnginePage &page = *view.page(); + view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + page.settings()->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, + canAccessClipboard); + page.settings()->setAttribute(QWebEngineSettings::JavascriptCanPaste, canPaste); + + QSignalSpy spy(&page, &QWebEnginePage::loadFinished); + QUrl baseUrl("https://www.example.com/somepage.html"); + page.setHtml(QString("<html><body>Test</body></html>"), baseUrl); + QTRY_COMPARE(spy.size(), 1); + + evaluateJavaScriptSync(&page, clipboardPermissionQuery("readPermission", "clipboard-read")); + QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("readPermission")), permission); + evaluateJavaScriptSync(&page, clipboardPermissionQuery("writePermission", "clipboard-write")); + QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("writePermission")), permission); +} + +void tst_QWebEnginePage::clipboardReadWritePermission_data() +{ + QTest::addColumn<bool>("canAccessClipboard"); + QTest::addColumn<QWebEnginePage::PermissionPolicy>("initialPolicy"); + QTest::addColumn<QString>("initialPermission"); + QTest::addColumn<QWebEnginePage::PermissionPolicy>("requestPolicy"); + QTest::addColumn<QString>("finalPermission"); + + QTest::newRow("noAccessGrantGrant") + << false << QWebEnginePage::PermissionGrantedByUser << "granted" + << QWebEnginePage::PermissionGrantedByUser << "granted"; + QTest::newRow("noAccessGrantDeny") + << false << QWebEnginePage::PermissionGrantedByUser << "granted" + << QWebEnginePage::PermissionDeniedByUser << "denied"; + QTest::newRow("noAccessDenyGrant") + << false << QWebEnginePage::PermissionDeniedByUser << "denied" + << QWebEnginePage::PermissionGrantedByUser << "granted"; + QTest::newRow("noAccessDenyDeny") << false << QWebEnginePage::PermissionDeniedByUser << "denied" + << QWebEnginePage::PermissionDeniedByUser << "denied"; + QTest::newRow("noAccessAskGrant") << false << QWebEnginePage::PermissionUnknown << "prompt" + << QWebEnginePage::PermissionGrantedByUser << "granted"; + + // All policies are ignored and overridden by setting JsCanAccessClipboard and JsCanPaste to + // true + QTest::newRow("accessGrantGrant") + << true << QWebEnginePage::PermissionGrantedByUser << "granted" + << QWebEnginePage::PermissionGrantedByUser << "granted"; + QTest::newRow("accessDenyDeny") << true << QWebEnginePage::PermissionDeniedByUser << "granted" + << QWebEnginePage::PermissionDeniedByUser << "granted"; + QTest::newRow("accessAskAsk") << true << QWebEnginePage::PermissionUnknown << "granted" + << QWebEnginePage::PermissionUnknown << "granted"; +} + +void tst_QWebEnginePage::clipboardReadWritePermission() +{ + QFETCH(bool, canAccessClipboard); + QFETCH(QWebEnginePage::PermissionPolicy, initialPolicy); + QFETCH(QString, initialPermission); + QFETCH(QWebEnginePage::PermissionPolicy, requestPolicy); + QFETCH(QString, finalPermission); + + QWebEngineProfile otr; + otr.setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions); + QWebEngineView view(&otr); + QWebEnginePage &page = *view.page(); + view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + page.settings()->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, + canAccessClipboard); + page.settings()->setAttribute(QWebEngineSettings::JavascriptCanPaste, true); + + QUrl baseUrl("https://www.example.com/somepage.html"); + + int permissionRequestCount = 0; + bool errorState = false; + + // if JavascriptCanAccessClipboard is true, this never fires + connect(&page, &QWebEnginePage::featurePermissionRequested, &page, + [&](const QUrl &o, QWebEnginePage::Feature f) { + if (f != QWebEnginePage::ClipboardReadWrite) + return; + if (o != baseUrl.url(QUrl::RemoveFilename)) { + qWarning() << "Unexpected case. Can't proceed." << o; + errorState = true; + return; + } + permissionRequestCount++; + page.setFeaturePermission(o, f, requestPolicy); + }); + + page.setFeaturePermission(baseUrl, QWebEnginePage::ClipboardReadWrite, initialPolicy); + + QSignalSpy spy(&page, &QWebEnginePage::loadFinished); + page.setHtml(QString("<html><body>Test</body></html>"), baseUrl); + QTRY_COMPARE(spy.size(), 1); + + evaluateJavaScriptSync(&page, clipboardPermissionQuery("readPermission", "clipboard-read")); + QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("readPermission")), initialPermission); + evaluateJavaScriptSync(&page, clipboardPermissionQuery("writePermission", "clipboard-write")); + QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("writePermission")), initialPermission); + + auto triggerRequest = [&page](QString variableName, QString apiCall) + { + auto js = QString("var %1; navigator.clipboard.%2.then((v) => { %1 = 'granted' }, (v) => { %1 = " + "'denied' });") + .arg(variableName) + .arg(apiCall); + evaluateJavaScriptSync(&page, js); + }; + + // permission is not 'remembered' from api standpoint, hence is not suppressed on explicit call + // from JS + triggerRequest("readState", "readText()"); + QTRY_COMPARE(evaluateJavaScriptSync(&page, "readState"), finalPermission); + triggerRequest("writeState", "writeText('foo')"); + QTRY_COMPARE(evaluateJavaScriptSync(&page, "writeState"), finalPermission); + QCOMPARE(permissionRequestCount, canAccessClipboard ? 0 : 2); + QVERIFY(!errorState); +} + void tst_QWebEnginePage::contentsSize() { m_view->resize(800, 600); @@ -3516,8 +4079,8 @@ void tst_QWebEnginePage::contentsSize() m_view->setHtml(QString("<html><body style=\"width: 1600px; height: 1200px;\"><p>hi</p></body></html>")); - QTRY_COMPARE(loadSpy.count(), 1); - QTRY_COMPARE(contentsSizeChangedSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); + QTRY_COMPARE(contentsSizeChangedSpy.size(), 1); // Verify the page's contents size is not limited by the view's size. QCOMPARE(m_page->contentsSize().width(), 1608); @@ -3534,6 +4097,58 @@ void tst_QWebEnginePage::contentsSize() QCOMPARE(m_page->contentsSize().height(), 1216); } +void tst_QWebEnginePage::localFontAccessPermission_data() +{ + QTest::addColumn<QWebEnginePage::PermissionPolicy>("policy"); + QTest::addColumn<bool>("ignore"); + QTest::addColumn<bool>("shouldBeEmpty"); + + QTest::newRow("ignore") << QWebEnginePage::PermissionDeniedByUser << true << true; + QTest::newRow("setDeny") << QWebEnginePage::PermissionDeniedByUser << false << true; + QTest::newRow("setGrant") << QWebEnginePage::PermissionGrantedByUser << false << false; +} + +void tst_QWebEnginePage::localFontAccessPermission() { + QFETCH(QWebEnginePage::PermissionPolicy, policy); + QFETCH(bool, ignore); + QFETCH(bool, shouldBeEmpty); + + QWebEngineView view; + QWebEnginePage page(&view); + page.profile()->setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions); + view.setPage(&page); + + connect(&page, &QWebEnginePage::featurePermissionRequested, &page, [&] (const QUrl &o, QWebEnginePage::Feature f) { + if (f != QWebEnginePage::LocalFontsAccess) + return; + + if (!ignore) + page.setFeaturePermission(o, f, policy); + }); + + QSignalSpy spy(&page, &QWebEnginePage::loadFinished); + page.load(QUrl("qrc:///resources/fontaccess.html")); + QTRY_COMPARE(spy.size(), 1); + + // Font access is only enabled for visible WebContents. + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + if (evaluateJavaScriptSync(&page, QStringLiteral("!window.queryLocalFonts")).toBool()) + W_QSKIP("Local fonts access is not supported.", SkipSingle); + + // Access to the API requires recent user interaction + QTest::keyPress(view.focusProxy(), Qt::Key_Space); + QTRY_COMPARE(evaluateJavaScriptSync(&page, QStringLiteral("activated")).toBool(), true); + + if (ignore) { + QTRY_COMPARE_NE_WITH_TIMEOUT(evaluateJavaScriptSync(&page, QStringLiteral("done")).toBool(), true, 1000); + } else { + QTRY_VERIFY_WITH_TIMEOUT(evaluateJavaScriptSync(&page, QStringLiteral("done")).toBool() == true, 1000); + QVERIFY((evaluateJavaScriptSync(&page, QStringLiteral("fonts.length")).toInt() == 0) == shouldBeEmpty); + } +} + void tst_QWebEnginePage::setLifecycleState() { qRegisterMetaType<QWebEnginePage::LifecycleState>("LifecycleState"); @@ -3545,64 +4160,64 @@ void tst_QWebEnginePage::setLifecycleState() QSignalSpy visibleSpy(&page, &QWebEnginePage::visibleChanged); page.load(QStringLiteral("qrc:/resources/lifecycle.html")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); - QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(lifecycleSpy.size(), 0); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QCOMPARE(visibleSpy.count(), 0); + QCOMPARE(visibleSpy.size(), 0); QCOMPARE(page.isVisible(), false); QCOMPARE(evaluateJavaScriptSync(&page, "document.wasDiscarded"), QVariant(false)); QCOMPARE(evaluateJavaScriptSync(&page, "frozenness"), QVariant(0)); // Active -> Frozen page.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); - QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.size(), 1); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Frozen); - QCOMPARE(visibleSpy.count(), 0); + QCOMPARE(visibleSpy.size(), 0); QCOMPARE(page.isVisible(), false); QCOMPARE(evaluateJavaScriptSync(&page, "document.wasDiscarded"), QVariant(false)); QCOMPARE(evaluateJavaScriptSync(&page, "frozenness"), QVariant(1)); // Frozen -> Active page.setLifecycleState(QWebEnginePage::LifecycleState::Active); - QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.size(), 1); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QCOMPARE(visibleSpy.count(), 0); + QCOMPARE(visibleSpy.size(), 0); QCOMPARE(page.isVisible(), false); QCOMPARE(evaluateJavaScriptSync(&page, "document.wasDiscarded"), QVariant(false)); QCOMPARE(evaluateJavaScriptSync(&page, "frozenness"), QVariant(0)); // Active -> Discarded page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); - QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.size(), 1); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Discarded); - QCOMPARE(visibleSpy.count(), 0); + QCOMPARE(visibleSpy.size(), 0); QCOMPARE(page.isVisible(), false); QTest::ignoreMessage(QtWarningMsg, "runJavaScript: disabled in Discarded state"); QCOMPARE(evaluateJavaScriptSync(&page, "document.wasDiscarded"), QVariant()); QTest::ignoreMessage(QtWarningMsg, "runJavaScript: disabled in Discarded state"); QCOMPARE(evaluateJavaScriptSync(&page, "frozenness"), QVariant()); - QCOMPARE(loadSpy.count(), 0); + QCOMPARE(loadSpy.size(), 0); // Discarded -> Frozen (illegal!) QTest::ignoreMessage(QtWarningMsg, "setLifecycleState: failed to transition from Discarded to Frozen state: " "illegal transition"); page.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); - QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(lifecycleSpy.size(), 0); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Discarded); // Discarded -> Active page.setLifecycleState(QWebEnginePage::LifecycleState::Active); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); - QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.size(), 1); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QCOMPARE(visibleSpy.count(), 0); + QCOMPARE(visibleSpy.size(), 0); QCOMPARE(page.isVisible(), false); QCOMPARE(evaluateJavaScriptSync(&page, "document.wasDiscarded"), QVariant(true)); QCOMPARE(evaluateJavaScriptSync(&page, "frozenness"), QVariant(0)); @@ -3611,21 +4226,21 @@ void tst_QWebEnginePage::setLifecycleState() page.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); page.setLifecycleState(QWebEnginePage::LifecycleState::Active); - QCOMPARE(lifecycleSpy.count(), 3); + QCOMPARE(lifecycleSpy.size(), 3); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QCOMPARE(visibleSpy.count(), 0); + QCOMPARE(visibleSpy.size(), 0); QCOMPARE(page.isVisible(), false); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); QCOMPARE(evaluateJavaScriptSync(&page, "document.wasDiscarded"), QVariant(true)); QCOMPARE(evaluateJavaScriptSync(&page, "frozenness"), QVariant(0)); // Reload clears document.wasDiscarded page.triggerAction(QWebEnginePage::Reload); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); QCOMPARE(evaluateJavaScriptSync(&page, "document.wasDiscarded"), QVariant(false)); } @@ -3641,18 +4256,18 @@ void tst_QWebEnginePage::setVisible() QSignalSpy visibleSpy(&page, &QWebEnginePage::visibleChanged); page.load(QStringLiteral("about:blank")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); - QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(lifecycleSpy.size(), 0); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QCOMPARE(visibleSpy.count(), 0); + QCOMPARE(visibleSpy.size(), 0); QCOMPARE(page.isVisible(), false); // hidden -> visible page.setVisible(true); - QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(lifecycleSpy.size(), 0); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QCOMPARE(visibleSpy.count(), 1); + QCOMPARE(visibleSpy.size(), 1); QCOMPARE(visibleSpy.takeFirst().value(0), QVariant(true)); QCOMPARE(page.isVisible(), true); @@ -3661,28 +4276,28 @@ void tst_QWebEnginePage::setVisible() QtWarningMsg, "setLifecycleState: failed to transition from Active to Frozen state: page is visible"); page.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); - QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(lifecycleSpy.size(), 0); // visible -> hidden page.setVisible(false); - QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(lifecycleSpy.size(), 0); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QCOMPARE(visibleSpy.count(), 1); + QCOMPARE(visibleSpy.size(), 1); QCOMPARE(visibleSpy.takeFirst().value(0), QVariant(false)); QCOMPARE(page.isVisible(), false); // Active -> Frozen page.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); - QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.size(), 1); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Frozen); // hidden -> visible (triggers Frozen -> Active) page.setVisible(true); - QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.size(), 1); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QCOMPARE(visibleSpy.count(), 1); + QCOMPARE(visibleSpy.size(), 1); QCOMPARE(visibleSpy.takeFirst().value(0), QVariant(true)); QCOMPARE(page.isVisible(), true); @@ -3691,31 +4306,31 @@ void tst_QWebEnginePage::setVisible() "setLifecycleState: failed to transition from Active to Discarded state: " "page is visible"); page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); - QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(lifecycleSpy.size(), 0); // visible -> hidden page.setVisible(false); - QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(lifecycleSpy.size(), 0); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QCOMPARE(visibleSpy.count(), 1); + QCOMPARE(visibleSpy.size(), 1); QCOMPARE(visibleSpy.takeFirst().value(0), QVariant(false)); QCOMPARE(page.isVisible(), false); // Active -> Discarded page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); - QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.size(), 1); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Discarded); // hidden -> visible (triggers Discarded -> Active) page.setVisible(true); - QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.size(), 1); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QCOMPARE(visibleSpy.count(), 1); + QCOMPARE(visibleSpy.size(), 1); QCOMPARE(visibleSpy.takeFirst().value(0), QVariant(true)); QCOMPARE(page.isVisible(), true); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); } @@ -3726,7 +4341,7 @@ void tst_QWebEnginePage::discardPreservesProperties() QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished); page.load(QStringLiteral("about:blank")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); // Change as many properties as possible to non-default values @@ -3763,7 +4378,7 @@ void tst_QWebEnginePage::discardPreservesProperties() // Discard + undiscard page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); page.setLifecycleState(QWebEnginePage::LifecycleState::Active); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); // Property changes should be preserved @@ -3802,7 +4417,7 @@ void tst_QWebEnginePage::automaticUndiscard() QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished); page.load(QStringLiteral("about:blank")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); // setUrl @@ -3827,16 +4442,16 @@ void tst_QWebEnginePage::setLifecycleStateWithDevTools() // Ensure pages are initialized inspectedPage.load(QStringLiteral("about:blank")); devToolsPage.load(QStringLiteral("about:blank")); - QTRY_COMPARE(inspectedSpy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(inspectedSpy.size(), 1, 90000); QCOMPARE(inspectedSpy.takeFirst().value(0), QVariant(true)); - QTRY_COMPARE(devToolsSpy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(devToolsSpy.size(), 1, 90000); QCOMPARE(devToolsSpy.takeFirst().value(0), QVariant(true)); // Open DevTools with Frozen inspectedPage inspectedPage.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); inspectedPage.setDevToolsPage(&devToolsPage); QCOMPARE(inspectedPage.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QTRY_COMPARE(devToolsSpy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(devToolsSpy.size(), 1, 90000); QCOMPARE(devToolsSpy.takeFirst().value(0), QVariant(true)); inspectedPage.setDevToolsPage(nullptr); @@ -3844,9 +4459,9 @@ void tst_QWebEnginePage::setLifecycleStateWithDevTools() inspectedPage.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); inspectedPage.setDevToolsPage(&devToolsPage); QCOMPARE(inspectedPage.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QTRY_COMPARE(devToolsSpy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(devToolsSpy.size(), 1, 90000); QCOMPARE(devToolsSpy.takeFirst().value(0), QVariant(true)); - QTRY_COMPARE(inspectedSpy.count(), 1); + QTRY_COMPARE(inspectedSpy.size(), 1); QCOMPARE(inspectedSpy.takeFirst().value(0), QVariant(true)); inspectedPage.setDevToolsPage(nullptr); @@ -3854,7 +4469,7 @@ void tst_QWebEnginePage::setLifecycleStateWithDevTools() devToolsPage.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); devToolsPage.setInspectedPage(&inspectedPage); QCOMPARE(devToolsPage.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QTRY_COMPARE(devToolsSpy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(devToolsSpy.size(), 1, 90000); QCOMPARE(devToolsSpy.takeFirst().value(0), QVariant(true)); devToolsPage.setInspectedPage(nullptr); @@ -3862,8 +4477,7 @@ void tst_QWebEnginePage::setLifecycleStateWithDevTools() devToolsPage.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); devToolsPage.setInspectedPage(&inspectedPage); QCOMPARE(devToolsPage.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QTRY_COMPARE(devToolsSpy.count(), 2); - QCOMPARE(devToolsSpy.takeFirst().value(0), QVariant(false)); + QTRY_COMPARE_WITH_TIMEOUT(devToolsSpy.size(), 1, 90000); QCOMPARE(devToolsSpy.takeFirst().value(0), QVariant(true)); // keep DevTools open @@ -3901,35 +4515,35 @@ void tst_QWebEnginePage::discardPreservesCommittedLoad() QString url = QStringLiteral("qrc:/resources/lifecycle.html"); page.setUrl(url); - QTRY_COMPARE(loadStartedSpy.count(), 1); + QTRY_COMPARE(loadStartedSpy.size(), 1); loadStartedSpy.clear(); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); QCOMPARE(loadFinishedSpy.takeFirst().value(0), QVariant(true)); - QCOMPARE(urlChangedSpy.count(), 1); + QCOMPARE(urlChangedSpy.size(), 1); QCOMPARE(urlChangedSpy.takeFirst().value(0), QVariant(QUrl(url))); QCOMPARE(page.url(), url); - QCOMPARE(titleChangedSpy.count(), 2); + QCOMPARE(titleChangedSpy.size(), 2); QCOMPARE(titleChangedSpy.takeFirst().value(0), QVariant(url)); QString title = QStringLiteral("Lifecycle"); QCOMPARE(titleChangedSpy.takeFirst().value(0), QVariant(title)); QCOMPARE(page.title(), title); page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); - QCOMPARE(loadStartedSpy.count(), 0); - QCOMPARE(loadFinishedSpy.count(), 0); - QCOMPARE(urlChangedSpy.count(), 0); + QCOMPARE(loadStartedSpy.size(), 0); + QCOMPARE(loadFinishedSpy.size(), 0); + QCOMPARE(urlChangedSpy.size(), 0); QCOMPARE(page.url(), QUrl(url)); - QCOMPARE(titleChangedSpy.count(), 0); + QCOMPARE(titleChangedSpy.size(), 0); QCOMPARE(page.title(), title); page.setLifecycleState(QWebEnginePage::LifecycleState::Active); - QTRY_COMPARE(loadStartedSpy.count(), 1); + QTRY_COMPARE(loadStartedSpy.size(), 1); loadStartedSpy.clear(); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); QCOMPARE(loadFinishedSpy.takeFirst().value(0), QVariant(true)); - QCOMPARE(urlChangedSpy.count(), 0); + QCOMPARE(urlChangedSpy.size(), 0); QCOMPARE(page.url(), url); - QCOMPARE(titleChangedSpy.count(), 0); + QCOMPARE(titleChangedSpy.size(), 0); QCOMPARE(page.title(), title); } @@ -3946,21 +4560,21 @@ void tst_QWebEnginePage::discardAbortsPendingLoad() [&]() { page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); }); QUrl url = QStringLiteral("qrc:/resources/lifecycle.html"); page.setUrl(url); - QTRY_COMPARE(loadStartedSpy.count(), 1); + QTRY_COMPARE(loadStartedSpy.size(), 1); loadStartedSpy.clear(); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); QCOMPARE(loadFinishedSpy.takeFirst().value(0), QVariant(false)); - QCOMPARE(urlChangedSpy.count(), 2); + QCOMPARE(urlChangedSpy.size(), 2); QCOMPARE(urlChangedSpy.takeFirst().value(0), QVariant(url)); QCOMPARE(urlChangedSpy.takeFirst().value(0), QVariant(QUrl())); - QCOMPARE(titleChangedSpy.count(), 0); + QCOMPARE(titleChangedSpy.size(), 0); QCOMPARE(page.url(), QUrl()); QCOMPARE(page.title(), QString()); page.setLifecycleState(QWebEnginePage::LifecycleState::Active); - QCOMPARE(loadStartedSpy.count(), 0); - QCOMPARE(loadFinishedSpy.count(), 0); - QCOMPARE(urlChangedSpy.count(), 0); + QCOMPARE(loadStartedSpy.size(), 0); + QCOMPARE(loadFinishedSpy.size(), 0); + QCOMPARE(urlChangedSpy.size(), 0); QCOMPARE(page.url(), QUrl()); QCOMPARE(page.title(), QString()); } @@ -3976,14 +4590,14 @@ void tst_QWebEnginePage::discardAbortsPendingLoadAndPreservesCommittedLoad() QString url1 = QStringLiteral("qrc:/resources/lifecycle.html"); page.setUrl(url1); - QTRY_COMPARE(loadStartedSpy.count(), 1); + QTRY_COMPARE(loadStartedSpy.size(), 1); loadStartedSpy.clear(); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); QCOMPARE(loadFinishedSpy.takeFirst().value(0), QVariant(true)); - QCOMPARE(urlChangedSpy.count(), 1); + QCOMPARE(urlChangedSpy.size(), 1); QCOMPARE(urlChangedSpy.takeFirst().value(0), QVariant(QUrl(url1))); QCOMPARE(page.url(), url1); - QCOMPARE(titleChangedSpy.count(), 2); + QCOMPARE(titleChangedSpy.size(), 2); QCOMPARE(titleChangedSpy.takeFirst().value(0), QVariant(url1)); QString title = QStringLiteral("Lifecycle"); QCOMPARE(titleChangedSpy.takeFirst().value(0), QVariant(title)); @@ -3993,21 +4607,21 @@ void tst_QWebEnginePage::discardAbortsPendingLoadAndPreservesCommittedLoad() [&]() { page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); }); QString url2 = QStringLiteral("about:blank"); page.setUrl(url2); - QTRY_COMPARE(loadStartedSpy.count(), 1); + QTRY_COMPARE(loadStartedSpy.size(), 1); loadStartedSpy.clear(); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); QCOMPARE(loadFinishedSpy.takeFirst().value(0), QVariant(false)); - QCOMPARE(urlChangedSpy.count(), 2); + QCOMPARE(urlChangedSpy.size(), 2); QCOMPARE(urlChangedSpy.takeFirst().value(0), QVariant(QUrl(url2))); QCOMPARE(urlChangedSpy.takeFirst().value(0), QVariant(QUrl(url1))); - QCOMPARE(titleChangedSpy.count(), 0); + QCOMPARE(titleChangedSpy.size(), 0); QCOMPARE(page.url(), url1); QCOMPARE(page.title(), title); page.setLifecycleState(QWebEnginePage::LifecycleState::Active); - QCOMPARE(loadStartedSpy.count(), 0); - QCOMPARE(loadFinishedSpy.count(), 0); - QCOMPARE(urlChangedSpy.count(), 0); + QCOMPARE(loadStartedSpy.size(), 0); + QCOMPARE(loadFinishedSpy.size(), 0); + QCOMPARE(urlChangedSpy.size(), 0); QCOMPARE(page.url(), url1); QCOMPARE(page.title(), title); } @@ -4103,32 +4717,32 @@ void tst_QWebEnginePage::recommendedStateAuto() connect(&page, &QWebEnginePage::recommendedStateChanged, &page, &QWebEnginePage::setLifecycleState); page.load(QStringLiteral("qrc:/resources/lifecycle.html")); - QTRY_COMPARE(lifecycleSpy.count(), 2); + QTRY_COMPARE(lifecycleSpy.size(), 2); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); page.setVisible(true); - QTRY_COMPARE(lifecycleSpy.count(), 1); + QTRY_COMPARE(lifecycleSpy.size(), 1); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); page.setVisible(false); - QTRY_COMPARE(lifecycleSpy.count(), 2); + QTRY_COMPARE(lifecycleSpy.size(), 2); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); page.triggerAction(QWebEnginePage::Reload); - QTRY_COMPARE(lifecycleSpy.count(), 3); + QTRY_COMPARE(lifecycleSpy.size(), 3); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); QWebEnginePage devTools; page.setDevToolsPage(&devTools); - QTRY_COMPARE(lifecycleSpy.count(), 1); + QTRY_COMPARE(lifecycleSpy.size(), 1); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); page.setDevToolsPage(nullptr); - QTRY_COMPARE(lifecycleSpy.count(), 2); + QTRY_COMPARE(lifecycleSpy.size(), 2); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); } @@ -4143,33 +4757,33 @@ void tst_QWebEnginePage::setLifecycleStateAndReload() QSignalSpy lifecycleSpy(&page, &QWebEnginePage::lifecycleStateChanged); page.load(QStringLiteral("qrc:/resources/lifecycle.html")); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); - QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(lifecycleSpy.size(), 0); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); page.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Frozen); - QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.size(), 1); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); page.triggerAction(QWebEnginePage::Reload); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.size(), 1); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Discarded); - QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.size(), 1); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); page.triggerAction(QWebEnginePage::Reload); QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); - QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.size(), 1); QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); } @@ -4188,20 +4802,20 @@ void tst_QWebEnginePage::editActionsWithExplicitFocus() QVERIFY(!page->action(QWebEnginePage::SelectAll)->isEnabled()); page->setHtml(QString("<html><body><div>foo bar</div></body></html>")); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); // Still no focus because focus on navigation is disabled. Edit actions don't do anything (should not crash). QVERIFY(!page->action(QWebEnginePage::SelectAll)->isEnabled()); view.page()->triggerAction(QWebEnginePage::SelectAll); - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 0); QCOMPARE(page->hasSelection(), false); // Focus content by focusing window from JavaScript. Edit actions should be enabled and functional. evaluateJavaScriptSync(page, "window.focus();"); - QTRY_COMPARE(actionChangedSpy.count(), 1); + QTRY_COMPARE(actionChangedSpy.size(), 1); QVERIFY(page->action(QWebEnginePage::SelectAll)->isEnabled()); view.page()->triggerAction(QWebEnginePage::SelectAll); - QTRY_COMPARE(selectionChangedSpy.count(), 1); + QTRY_COMPARE(selectionChangedSpy.size(), 1); QCOMPARE(page->hasSelection(), true); QCOMPARE(page->selectedText(), QStringLiteral("foo bar")); } @@ -4221,13 +4835,13 @@ void tst_QWebEnginePage::editActionsWithInitialFocus() QVERIFY(!page->action(QWebEnginePage::SelectAll)->isEnabled()); page->setHtml(QString("<html><body><div>foo bar</div></body></html>")); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); // Content gets initial focus. - QTRY_COMPARE(actionChangedSpy.count(), 1); + QTRY_COMPARE(actionChangedSpy.size(), 1); QVERIFY(page->action(QWebEnginePage::SelectAll)->isEnabled()); view.page()->triggerAction(QWebEnginePage::SelectAll); - QTRY_COMPARE(selectionChangedSpy.count(), 1); + QTRY_COMPARE(selectionChangedSpy.size(), 1); QCOMPARE(page->hasSelection(), true); QCOMPARE(page->selectedText(), QStringLiteral("foo bar")); } @@ -4247,15 +4861,15 @@ void tst_QWebEnginePage::editActionsWithFocusOnIframe() QVERIFY(!page->action(QWebEnginePage::SelectAll)->isEnabled()); page->load(QUrl("qrc:///resources/iframe2.html")); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); QVERIFY(!page->action(QWebEnginePage::SelectAll)->isEnabled()); // Focusing an iframe. evaluateJavaScriptSync(page, "document.getElementsByTagName('iframe')[0].contentWindow.focus()"); - QTRY_COMPARE(actionChangedSpy.count(), 1); + QTRY_COMPARE(actionChangedSpy.size(), 1); QVERIFY(page->action(QWebEnginePage::SelectAll)->isEnabled()); view.page()->triggerAction(QWebEnginePage::SelectAll); - QTRY_COMPARE(selectionChangedSpy.count(), 1); + QTRY_COMPARE(selectionChangedSpy.size(), 1); QCOMPARE(page->hasSelection(), true); QCOMPARE(page->selectedText(), QStringLiteral("inner")); } @@ -4271,8 +4885,8 @@ void tst_QWebEnginePage::editActionsWithoutSelection() QSignalSpy actionChangedSpy(page->action(QWebEnginePage::SelectAll), &QAction::changed); page->setHtml(QString("<html><body><div>foo bar</div></body></html>")); - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QTRY_COMPARE(actionChangedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QTRY_COMPARE(actionChangedSpy.size(), 1); QVERIFY(!page->action(QWebEnginePage::Cut)->isEnabled()); QVERIFY(!page->action(QWebEnginePage::Copy)->isEnabled()); @@ -4284,7 +4898,7 @@ void tst_QWebEnginePage::editActionsWithoutSelection() QVERIFY(!page->action(QWebEnginePage::Unselect)->isEnabled()); page->triggerAction(QWebEnginePage::SelectAll); - QTRY_COMPARE(selectionChangedSpy.count(), 1); + QTRY_COMPARE(selectionChangedSpy.size(), 1); QCOMPARE(page->hasSelection(), true); QCOMPARE(page->selectedText(), QStringLiteral("foo bar")); @@ -4298,6 +4912,28 @@ void tst_QWebEnginePage::editActionsWithoutSelection() QVERIFY(page->action(QWebEnginePage::Unselect)->isEnabled()); } +struct PageWithNewWindowHandler : QWebEnginePage +{ + QScopedPointer<PageWithNewWindowHandler> newPage; + bool handleInSignal; + QWebEngineProfile *targetProfile = nullptr; + QSignalSpy loadSpy { this, &QWebEnginePage::loadFinished }; + PageWithNewWindowHandler(QWebEngineProfile *p, bool inSignal = false, QWebEngineProfile *tp = nullptr) + : QWebEnginePage(p), handleInSignal(inSignal), targetProfile(tp) { + if (handleInSignal) + connect(this, &QWebEnginePage::newWindowRequested, this, [this] (QWebEngineNewWindowRequest &r) { + newPage.reset(new PageWithNewWindowHandler(targetProfile ? targetProfile : profile(), handleInSignal)); + newPage->acceptAsNewWindow(r); + }); + } + QWebEnginePage *createWindow(WebWindowType) override { + if (handleInSignal) + return nullptr; + newPage.reset(new PageWithNewWindowHandler(targetProfile ? targetProfile : profile(), handleInSignal)); + return newPage.get(); + } +}; + void tst_QWebEnginePage::customUserAgentInNewTab() { HttpServer server; @@ -4310,55 +4946,814 @@ void tst_QWebEnginePage::customUserAgentInNewTab() }); QVERIFY(server.start()); - class Page : public QWebEnginePage { - public: - QWebEngineProfile *targetProfile = nullptr; - QScopedPointer<QWebEnginePage> newPage; - Page(QWebEngineProfile *profile) : QWebEnginePage(profile) {} - private: - QWebEnginePage *createWindow(WebWindowType) override - { - newPage.reset(new QWebEnginePage(targetProfile ? targetProfile : profile(), nullptr)); - return newPage.data(); - } - }; - QWebEngineProfile profile1, profile2; - profile1.setHttpUserAgent(QStringLiteral("custom 1")); - profile2.setHttpUserAgent(QStringLiteral("custom 2")); - Page page(&profile1); - QWebEngineView view; - view.resize(500, 500); - view.setPage(&page); - view.show(); + QString expectedUserAgent("custom 1"); + QWebEngineProfile profile; + profile.setHttpUserAgent(expectedUserAgent); + + PageWithNewWindowHandler page(&profile); + QWebEngineView view; view.resize(500, 500); view.setPage(&page); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); - QSignalSpy spy(&page, &QWebEnginePage::loadFinished); // First check we can get the user-agent passed through normally page.setHtml(QString("<html><body><a id='link' target='_blank' href='") + server.url("/test1").toEncoded() + QString("'>link</a></body></html>")); - QTRY_COMPARE(spy.count(), 1); - QVERIFY(spy.takeFirst().value(0).toBool()); - QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("navigator.userAgent")).toString(), profile1.httpUserAgent()); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, elementCenter(&page, "link")); + QTRY_COMPARE(page.loadSpy.size(), 1); + QVERIFY(page.loadSpy.takeFirst().value(0).toBool()); + QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("navigator.userAgent")).toString(), expectedUserAgent); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, elementCenter(&page, "link")); QTRY_VERIFY(page.newPage); + QTRY_COMPARE(page.newPage->loadSpy.size(), 1); QTRY_VERIFY(!lastUserAgent.isEmpty()); - QCOMPARE(lastUserAgent, profile1.httpUserAgent().toUtf8()); + QCOMPARE(lastUserAgent, expectedUserAgent); + QCOMPARE(evaluateJavaScriptSync(page.newPage.get(), QStringLiteral("navigator.userAgent")).toString(), expectedUserAgent); // Now check we can get the new user-agent of the profile page.newPage.reset(); - page.targetProfile = &profile2; - spy.clear(); + expectedUserAgent = "custom 2"; + profile.setHttpUserAgent(expectedUserAgent); + page.loadSpy.clear(); lastUserAgent = { }; page.setHtml(QString("<html><body><a id='link' target='_blank' href='") + server.url("/test2").toEncoded() + QString("'>link</a></body></html>")); - QTRY_COMPARE(spy.count(), 1); - QVERIFY(spy.takeFirst().value(0).toBool()); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, elementCenter(&page, "link")); + QTRY_COMPARE(page.loadSpy.size(), 1); + QVERIFY(page.loadSpy.takeFirst().value(0).toBool()); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, elementCenter(&page, "link")); QTRY_VERIFY(page.newPage); + QTRY_COMPARE(page.newPage->loadSpy.size(), 1); QTRY_VERIFY(!lastUserAgent.isEmpty()); - QCOMPARE(lastUserAgent, profile2.httpUserAgent().toUtf8()); + QCOMPARE(lastUserAgent, expectedUserAgent); + QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("navigator.userAgent")).toString(), expectedUserAgent); + QCOMPARE(evaluateJavaScriptSync(page.newPage.get(), QStringLiteral("navigator.userAgent")).toString(), expectedUserAgent); +} + +void tst_QWebEnginePage::openNewTabInDifferentProfile_data() +{ + QTest::addColumn<bool>("handleInSignal"); + QTest::addRow("handleInSignal") << true; + QTest::addRow("handleInOverride") << false; +} + +void tst_QWebEnginePage::openNewTabInDifferentProfile() +{ + QFETCH(bool, handleInSignal); + + HttpServer server; + QStringList receivedRequests; + connect(&server, &HttpServer::newRequest, [&] (HttpReqRep *r) { + receivedRequests.append(r->requestPath()); + r->setResponseBody("DUMMY"); + r->sendResponse(); + }); + QVERIFY(server.start()); + + QWebEngineProfile profile1, profile2; + PageWithNewWindowHandler page(&profile1, handleInSignal, &profile2); + QWebEngineView view; view.setPage(&page); view.resize(320, 240); view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + page.setHtml(QString("<html><body><a id='link' target='_blank' href='%1'>link</a></body></html>").arg(server.url("/first.html").toEncoded())); + QTRY_COMPARE(page.loadSpy.size(), 1); + QVERIFY(page.loadSpy.takeFirst().value(0).toBool()); + + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, elementCenter(&page, "link")); + QTRY_VERIFY(page.newPage); + QVERIFY(page.profile() == &profile1); + QVERIFY(page.newPage->profile() == &profile2); + // not load should occur or requests to server issued since web_contents is not expected to be adopted from other profile + QTRY_LOOP_IMPL(page.newPage->loadSpy.size() != 0, 1000, 100); + QVERIFY2(receivedRequests.isEmpty(), qPrintable(receivedRequests.join(", "))); +} + +void tst_QWebEnginePage::renderProcessCrashed() +{ + using Status = QWebEnginePage::RenderProcessTerminationStatus; + QWebEngineProfile profile; + QWebEnginePage page(&profile); + bool done = false; + Status status; + connect(&page, &QWebEnginePage::renderProcessTerminated, [&](Status newStatus) { + status = newStatus; + done = true; + }); + page.load(QUrl("chrome://crash")); + QTRY_VERIFY_WITH_TIMEOUT(done, 20000); + // The status depends on whether stack traces are enabled. With + // --disable-in-process-stack-traces we get an AbnormalTerminationStatus, + // otherwise a CrashedTerminationStatus. + QVERIFY(status == QWebEnginePage::CrashedTerminationStatus || + status == QWebEnginePage::AbnormalTerminationStatus); +} + +void tst_QWebEnginePage::renderProcessPid() +{ + QCOMPARE(m_page->renderProcessPid(), 0); + + m_page->load(QUrl("about:blank")); + QSignalSpy spyFinished(m_page, &QWebEnginePage::loadFinished); + QVERIFY(spyFinished.wait()); + + QVERIFY(m_page->renderProcessPid() > 1); + + bool crashed = false; + connect(m_page, &QWebEnginePage::renderProcessTerminated, [&]() { crashed = true; }); + m_page->load(QUrl("chrome://crash")); + QTRY_VERIFY_WITH_TIMEOUT(crashed, 20000); + + QCOMPARE(m_page->renderProcessPid(), 0); +} + +class FileSelectionTestPage : public QWebEnginePage { +public: + FileSelectionTestPage() : m_tempDir(QDir::tempPath() + "/tst_qwebenginepage-XXXXXX") { } + + QStringList chooseFiles(FileSelectionMode mode, const QStringList &oldFiles, const QStringList &acceptedMimeTypes) override + { + Q_UNUSED(oldFiles); + chosenFileSelectionMode = mode; + chosenAcceptedMimeTypes = acceptedMimeTypes; + return QStringList() << (m_tempDir.path() + "/file.txt"); + } + + QTemporaryDir m_tempDir; + int chosenFileSelectionMode = -1; + QStringList chosenAcceptedMimeTypes; +}; + +void tst_QWebEnginePage::testChooseFilesParameters_data() +{ + QTest::addColumn<QString>("uploadAttribute"); + QTest::addColumn<QString>("mimeTypeAttribute"); + QTest::addColumn<QWebEnginePage::FileSelectionMode>("expectedFileSelectionMode"); + QTest::addColumn<QStringList>("expectedMimeType"); + QStringList mimeTypes; + + QTest::addRow("Single file upload") << QString() << QString() + << QWebEnginePage::FileSelectOpen << QStringList(); + QTest::addRow("Multiple file upload") << QString("multiple") << QString() + << QWebEnginePage::FileSelectOpenMultiple << QStringList(); + QTest::addRow("Folder upload") << QString("multiple webkitdirectory") << QString() + << QWebEnginePage::FileSelectUploadFolder << QStringList(); + QTest::addRow("Save file") << QString("") << QString() + << QWebEnginePage::FileSelectSave << QStringList(); + mimeTypes = QStringList() << "audio/*"; + QTest::addRow("MIME type: audio") << QString() << QString("accept='%1'").arg(mimeTypes.join(',')) + << QWebEnginePage::FileSelectOpen << mimeTypes; + mimeTypes = QStringList() << "video/*"; + QTest::addRow("MIME type: video") << QString() << QString("accept='%1'").arg(mimeTypes.join(',')) + << QWebEnginePage::FileSelectOpen << mimeTypes; + mimeTypes = QStringList() << "image/*"; + QTest::addRow("MIME type: image") << QString() << QString("accept='%1'").arg(mimeTypes.join(',')) + << QWebEnginePage::FileSelectOpen << mimeTypes; + mimeTypes = QStringList() << ".txt" << ".html"; + QTest::addRow("MIME type: custom") << QString() << QString("accept='%1'").arg(mimeTypes.join(',')) + << QWebEnginePage::FileSelectOpen << mimeTypes; + mimeTypes = QStringList() << "audio/*" << "video/*" << "image/*" << ".txt" << ".html"; + QTest::addRow("Multiple MIME types") << QString() << QString("accept='%1'").arg(mimeTypes.join(',')) + << QWebEnginePage::FileSelectOpen << mimeTypes; +} + +void tst_QWebEnginePage::testChooseFilesParameters() +{ + QFETCH(QString, uploadAttribute); + QFETCH(QString, mimeTypeAttribute); + QFETCH(QWebEnginePage::FileSelectionMode, expectedFileSelectionMode); + QFETCH(QStringList, expectedMimeType); + + FileSelectionTestPage page; + QSignalSpy spyFinished(&page, &QWebEnginePage::loadFinished); + + QWebEngineView view; + view.resize(500, 500); + view.setPage(&page); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + if (expectedFileSelectionMode != QWebEnginePage::FileSelectSave) { + page.setHtml(QString("<html><body>" + "<input id='filePicker' type='file' name='filePicker' %1 %2 />" + "</body></html>").arg(uploadAttribute, mimeTypeAttribute)); + } else { + page.setHtml(QString("<html><body>" + "<button id='filePicker' value='trigger' " + "onclick='window.showSaveFilePicker()'" + "</body></html>"), QString("qrc:/")); + } + QVERIFY(spyFinished.wait()); + QTRY_COMPARE(spyFinished.size(), 1); + + evaluateJavaScriptSync(view.page(), "document.getElementById('filePicker').focus()"); + QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("filePicker")); + QTest::keyClick(view.focusProxy(), Qt::Key_Enter); + + QTRY_COMPARE(page.chosenFileSelectionMode, expectedFileSelectionMode); + QTRY_COMPARE(page.chosenAcceptedMimeTypes, expectedMimeType); +} + +void tst_QWebEnginePage::fileSystemAccessDialog() +{ + FileSelectionTestPage page; + QSignalSpy spyFinished(&page, &QWebEnginePage::loadFinished); + + QWebEngineView view; + view.resize(500, 500); + view.setPage(&page); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + connect(&page, &QWebEnginePage::fileSystemAccessRequested, + [](QWebEngineFileSystemAccessRequest request) { + QCOMPARE(request.accessFlags(), + QWebEngineFileSystemAccessRequest::Read + | QWebEngineFileSystemAccessRequest::Write); + request.accept(); + }); + + page.setHtml(QString("<html><head><script>" + "async function getTemporaryDir() {" + " const newHandle = await window.showSaveFilePicker();" + " const writable = await newHandle.createWritable();" + " await writable.write(new Blob(['New value']));" + " await writable.close();" + "" + " const fileData = await newHandle.getFile();" + " document.title = await fileData.text();" + "}" + "</script></head><body>" + "<button id='triggerDialog' value='trigger' " + "onclick='getTemporaryDir()'" + "</body></html>"), + QString("qrc:/")); + QVERIFY(spyFinished.wait()); + QTRY_COMPARE(spyFinished.size(), 1); + + evaluateJavaScriptSync(view.page(), "document.getElementById('triggerDialog').focus()"); + QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), + QStringLiteral("triggerDialog")); + QTest::keyClick(view.focusProxy(), Qt::Key_Enter); + + QTRY_COMPARE(page.title(), "New value"); + + QTRY_COMPARE(page.chosenFileSelectionMode, QWebEnginePage::FileSelectSave); + QTRY_COMPARE(page.chosenAcceptedMimeTypes, QStringList()); +} + +void tst_QWebEnginePage::backgroundColor() +{ + QWebEngineProfile profile; + QWebEngineView view; + QWebEnginePage *page = new QWebEnginePage(&profile, &view); + + view.resize(640, 480); + view.setStyleSheet("background: yellow"); + view.show(); + QPoint center(view.size().width() / 2, view.size().height() / 2); + + QCOMPARE(page->backgroundColor(), Qt::white); + QTRY_COMPARE(view.grab().toImage().pixelColor(center), Qt::white); + + page->setBackgroundColor(Qt::red); + view.setPage(page); + + QCOMPARE(page->backgroundColor(), Qt::red); + QTRY_COMPARE(view.grab().toImage().pixelColor(center), Qt::red); + + page->setHtml(QString("<html>" + "<head><style>html, body { margin:0; padding:0; }</style></head>" + "<body><div style=\"width:100%; height:10px; background-color:black\"/></body>" + "</html>")); + QSignalSpy spyFinished(page, &QWebEnginePage::loadFinished); + QVERIFY(spyFinished.wait()); + // Make sure the page is rendered and the test is not grabbing the color of the RenderWidgetHostViewQtDelegateWidget. + QTRY_COMPARE(view.grab().toImage().pixelColor(QPoint(5, 5)), Qt::black); + + QCOMPARE(page->backgroundColor(), Qt::red); + QCOMPARE(view.grab().toImage().pixelColor(center), Qt::red); + + page->setBackgroundColor(Qt::transparent); + + QCOMPARE(page->backgroundColor(), Qt::transparent); + QTRY_COMPARE(view.grab().toImage().pixelColor(center), Qt::yellow); + + page->setBackgroundColor(Qt::green); + + QCOMPARE(page->backgroundColor(), Qt::green); + QTRY_COMPARE(view.grab().toImage().pixelColor(center), Qt::green); +} + +void tst_QWebEnginePage::audioMuted() +{ + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QSignalSpy spy(&page, &QWebEnginePage::audioMutedChanged); + + QCOMPARE(page.isAudioMuted(), false); + page.setAudioMuted(true); + loadSync(&page, QUrl("about:blank")); + QCOMPARE(page.isAudioMuted(), true); + QCOMPARE(spy.size(), 1); + QCOMPARE(spy[0][0], QVariant(true)); + page.setAudioMuted(false); + QCOMPARE(page.isAudioMuted(), false); + QCOMPARE(spy.size(), 2); + QCOMPARE(spy[1][0], QVariant(false)); +} + +void tst_QWebEnginePage::closeContents() +{ + TestPage page; + QSignalSpy spyFinished(&page, &QWebEnginePage::loadFinished); + QSignalSpy windowCreatedSpy(&page, &TestPage::windowCreated); + page.setUrl(QUrl("about:blank")); + QTRY_COMPARE(spyFinished.size(), 1); + page.runJavaScript("var dialog = window.open('', '', 'width=100, height=100');"); + QTRY_COMPARE(windowCreatedSpy.size(), 1); + + QWebEngineView *dialogView = new QWebEngineView; + QWebEnginePage *dialogPage = page.createdWindows[0]; + dialogView->setPage(dialogPage); + QCOMPARE(dialogPage->lifecycleState(), QWebEnginePage::LifecycleState::Active); + + // This should not crash. + connect(dialogPage, &QWebEnginePage::windowCloseRequested, dialogView, &QWebEngineView::close); + page.runJavaScript("dialog.close();"); + + // QWebEngineView::closeEvent() sets the life cycle state to discarded. + QTRY_COMPARE(dialogPage->lifecycleState(), QWebEnginePage::LifecycleState::Discarded); + delete dialogView; +} + +// Based on QTBUG-84011 +void tst_QWebEnginePage::isSafeRedirect_data() +{ + QTest::addColumn<QUrl>("requestedUrl"); + QTest::addColumn<QUrl>("expectedUrl"); + QString fileScheme = "file://"; + +#ifdef Q_OS_WIN + fileScheme += "/"; +#endif + + QString tempDir(fileScheme + QDir::tempPath() + "/"); + QTest::newRow(qPrintable(tempDir)) << QUrl(tempDir) << QUrl(tempDir); + QTest::newRow(qPrintable(tempDir + QString("/foo/bar"))) << QUrl(tempDir + "/foo/bar") << QUrl(tempDir + "/foo/bar"); + QTest::newRow("filesystem:http://foo.com/bar") << QUrl("filesystem:http://foo.com/bar") << QUrl("filesystem:http://foo.com/bar/"); +} + +void tst_QWebEnginePage::isSafeRedirect() +{ + QFETCH(QUrl, requestedUrl); + QFETCH(QUrl, expectedUrl); + + TestPage page; + QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); + page.setUrl(requestedUrl); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 20000); + QCOMPARE(page.url(), expectedUrl); + spy.clear(); +} + +class LocalRemoteUrlSchemeHandler : public QWebEngineUrlSchemeHandler +{ +public: + LocalRemoteUrlSchemeHandler(QObject *parent = nullptr) + : QWebEngineUrlSchemeHandler(parent) + { + } + ~LocalRemoteUrlSchemeHandler() = default; + + void requestStarted(QWebEngineUrlRequestJob *job) override + { + QBuffer *buffer = new QBuffer(job); + buffer->setData("<html><body><a href='remote://test.html' id='link'>Click link</a></body></html>"); + job->reply("text/html", buffer); + loaded = true; + } + bool loaded = false; +}; + +void tst_QWebEnginePage::localToRemoteNavigation() +{ + LocalRemoteUrlSchemeHandler local; + LocalRemoteUrlSchemeHandler remote; + QWebEngineProfile profile; + profile.installUrlSchemeHandler("local", &local); + profile.installUrlSchemeHandler("remote", &remote); + + QWebEnginePage page(&profile); + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + QWebEngineView view; + view.resize(640, 480); + view.show(); + view.setPage(&page); + page.setUrl(QUrl("local://test.html")); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); + QVERIFY(local.loaded); + + // Should navigate: + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, elementCenter(&page, "link")); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 2, 20000); + QVERIFY(remote.loaded); + local.loaded = false; + remote.loaded = false; + + page.setUrl(QUrl("local://test.html")); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 3, 20000); + QVERIFY(local.loaded && !remote.loaded); + + // Should not navigate: + page.runJavaScript(QStringLiteral("document.getElementById(\"link\").click()")); + QTest::qWait(500); + QVERIFY(!remote.loaded); +} + +void tst_QWebEnginePage::clientHints_data() +{ + QTest::addColumn<bool>("clientHintsEnabled"); + QTest::addColumn<QString>("arch"); + QTest::addColumn<QString>("platform"); + QTest::addColumn<QString>("model"); + QTest::addColumn<bool>("isMobile"); + QTest::addColumn<QString>("fullVersion"); + QTest::addColumn<QString>("platformVersion"); + QTest::addColumn<QString>("bitness"); + QTest::addColumn<bool>("isWOW64"); + QTest::addColumn<QHash<QString, QString>>("fullVersionList"); + + QTest::newRow("Modify values") << true << "Abc" << "AmigaOS" << "Ultra" << true << "1.99" << "3" << "x64" << true << QHash<QString, QString>({{"APITest", "1.0.0"}, {"App", "5.0"}}); + QTest::newRow("Empty values") << true << "" << "" << "" << false << "" << "" << "" << false << QHash<QString, QString>(); + QTest::newRow("Disable headers") << false << "" << "" << "" << false << "" << "" << "" << false << QHash<QString, QString>(); +} + +void tst_QWebEnginePage::clientHints() +{ + QFETCH(bool, clientHintsEnabled); + QFETCH(QString, arch); + QFETCH(QString, platform); + QFETCH(QString, model); + QFETCH(bool, isMobile); + QFETCH(QString, fullVersion); + QFETCH(QString, platformVersion); + QFETCH(QString, bitness); + QFETCH(bool, isWOW64); + typedef QHash<QString, QString> brandVersionPairs; + QFETCH(brandVersionPairs, fullVersionList); + + QWebEnginePage page; + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + + QWebEngineClientHints *clientHints = page.profile()->clientHints(); + clientHints->setAllClientHintsEnabled(clientHintsEnabled); + + HttpServer server; + int requestCount = 0; + connect(&server, &HttpServer::newRequest, [&] (HttpReqRep *r) { + // Platform and Mobile hints are always sent and can't be disabled with this API + QVERIFY(r->hasRequestHeader("Sec-CH-UA-Platform")); + QVERIFY(r->hasRequestHeader("Sec-CH-UA-Mobile")); + if (!clientHintsEnabled) { + QVERIFY(!r->hasRequestHeader("Sec-CH-UA-Arch")); + QVERIFY(!r->hasRequestHeader("Sec-CH-UA-Model")); + QVERIFY(!r->hasRequestHeader("Sec-CH-UA-Full-Version")); + QVERIFY(!r->hasRequestHeader("Sec-CH-UA-Platform-Version")); + QVERIFY(!r->hasRequestHeader("Sec-CH-UA-Bitness")); + QVERIFY(!r->hasRequestHeader("Sec-CH-UA-Wow64")); + QVERIFY(!r->hasRequestHeader("Sec-CH-UA-Full-Version-List")); + } + + // The first request header won't contain any hints, only after a response with "Accept-CH" + if (requestCount > 1 && clientHintsEnabled) { + // All hint values are lower case in the headers + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Arch")).remove("\""), arch.toLower()); + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Platform")).remove("\""), platform.toLower()); + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Model")).remove("\""), model.toLower()); + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Mobile")).remove("\""), isMobile ? "?1" : "?0"); + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Full-Version")).remove("\""), fullVersion.toLower()); + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Platform-Version")).remove("\""), platformVersion.toLower()); + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Bitness")).remove("\""), bitness.toLower()); + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Wow64")).remove("\""), isWOW64 ? "?1" : "?0"); + for (auto i = fullVersionList.cbegin(), end = fullVersionList.cend(); i != end; ++i) + QVERIFY(QString(r->requestHeader("Sec-CH-UA-Full-Version-List")).contains(i.key().toLower())); + } + + r->setResponseHeader("Accept-CH", "Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform-Version, Sec-CH-UA-Platform, Sec-CH-UA-Wow64, Sec-CH-UA"); + r->sendResponse(); + requestCount++; + }); + QVERIFY(server.start()); + + clientHints->setArch(arch); + clientHints->setPlatform(platform); + clientHints->setModel(model); + clientHints->setIsMobile(isMobile); + clientHints->setFullVersion(fullVersion); + clientHints->setPlatformVersion(platformVersion); + clientHints->setBitness(bitness); + clientHints->setIsWow64(isWOW64); + clientHints->setFullVersionList(fullVersionList); + + page.setUrl(server.url()); + QTRY_COMPARE(loadSpy.size(), 1); + QVERIFY(loadSpy.takeFirst().value(0).toBool()); + + QCOMPARE(clientHints->arch(), arch); + QCOMPARE(clientHints->platform(), platform); + QCOMPARE(clientHints->model(), model); + QCOMPARE(clientHints->isMobile(), isMobile); + QCOMPARE(clientHints->fullVersion(), fullVersion); + QCOMPARE(clientHints->platformVersion(), platformVersion); + QCOMPARE(clientHints->bitness(), bitness); + QCOMPARE(clientHints->isWow64(), isWOW64); + for (auto i = fullVersionList.cbegin(), end = fullVersionList.cend(); i != end; ++i) + QCOMPARE(clientHints->fullVersionList()[i.key()], i.value()); + + // A new user agent string should not override/disable client hints + page.profile()->setHttpUserAgent(QStringLiteral("Custom user agent")); + page.triggerAction(QWebEnginePage::Reload); + QTRY_COMPARE(loadSpy.size(), 1); + + // Reset all to default values + clientHints->resetAll(); + QCOMPARE_NE(clientHints->arch(), arch); +#ifdef Q_OS_LINUX + QCOMPARE(clientHints->platform().toLower(), "linux"); +#elif defined (Q_OS_MACOS) + QCOMPARE(clientHints->platform().toLower(), "macos"); +#elif defined (Q_OS_WIN) + QCOMPARE(clientHints->platform().toLower(), "windows"); +#endif + QCOMPARE_NE(clientHints->fullVersion(), fullVersion); + QCOMPARE_NE(clientHints->platformVersion(), platformVersion); + QCOMPARE_NE(clientHints->bitness(), bitness); + for (auto i = fullVersionList.cbegin(), end = fullVersionList.cend(); i != end; ++i) + QVERIFY(!clientHints->fullVersionList().contains(i.key())); + QVERIFY(clientHints->fullVersionList().contains("Chromium")); +} + +void tst_QWebEnginePage::childFrameInput() +{ + HttpServer server; + server.setHostDomain("localhost"); + + // The cross-origin policy blocks scripting this frame with QWebEnginePage::runJavaScript. + // Use console messages to validate events. + QString innerHtml( + "<html><head><style>body{height:1200px;width:1200px;}</style></head><body>test<script>" + " let lastX, lastY = 0;" + " document.onscroll = (e) => {" + " if (window.scrollY > lastY) console.log(\"Down\");" + " if (window.scrollY < lastY) console.log(\"Up\");" + " if (window.scrollX > lastX) console.log(\"Right\");" + " if (window.scrollX < lastX) console.log(\"Left\");" + " lastX = window.scrollX;" + " lastY = window.scrollY;" + " };" + " window.onload = () => {console.log('loaded');};" + "</script></body></html>"); + + QVERIFY(server.start()); + connect(&server, &HttpServer::newRequest, [&](HttpReqRep *rr) { + if (rr->requestPath() == "/main.html") { + // the Origin-Agent-Cluster header enables dedicated processes for origins + rr->setResponseHeader("Origin-Agent-Cluster", "?1"); + // the same-site-cross-origin page forces to create the frame in a different process + server.setHostDomain("sub.localhost"); + rr->setResponseBody(("<html><body>" + "<iframe id=\"iframe\" width=90% height=90% src=\"" + + server.url().toString().toUtf8() + + "inner.html\"></iframe>" + "</body></html>")); + } + if (rr->requestPath() == "/inner.html") + rr->setResponseBody(innerHtml.toUtf8()); + rr->sendResponse(); + }); + + QWebEngineView view; + ConsolePage page; + view.setPage(&page); + view.resize(640, 480); + QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished); + page.load(server.url("/main.html")); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); + + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + QTRY_VERIFY(evaluateJavaScriptSync(&page, "window.originAgentCluster").toBool()); + + // make sure the frame is loaded + QTRY_COMPARE(page.messages.size(), 1); + QTRY_COMPARE(page.messages[0], QString("loaded")); + + // focus + evaluateJavaScriptSync(&page, "document.getElementById('iframe').contentWindow.focus()"); + QTRY_COMPARE(evaluateJavaScriptSync(&page, "document.activeElement.id").toString(), + QStringLiteral("iframe")); + + QPoint globalPos = view.windowHandle()->position(); + QPoint p = elementCenter(&page, QString("iframe")); + + // Even if the document is loaded, it is not necessarily drawn. + // Hit-testing (in Viz) for pointer events will be flacky in this scenario. + // Send keyClick events first so the target frame will be cached for wheel events. + QTest::keyClick(view.focusProxy(), Qt::Key_Down); + QTRY_COMPARE(page.messages.size(), 2); + QTRY_COMPARE(page.messages[1], QString("Down")); + + QTest::keyClick(view.focusProxy(), Qt::Key_Up); + QTRY_COMPARE(page.messages.size(), 3); + QTRY_COMPARE(page.messages[2], QString("Up")); + + QTest::keyClick(view.focusProxy(), Qt::Key_Right); + QTRY_COMPARE(page.messages.size(), 4); + QTRY_COMPARE(page.messages[3], QString("Right")); + + QTest::keyClick(view.focusProxy(), Qt::Key_Left); + QTRY_COMPARE(page.messages.size(), 5); + QTRY_COMPARE(page.messages[4], QString("Left")); + + makeScroll(view.focusProxy(), p, globalPos, QPoint(0, -120)); + QTRY_COMPARE(page.messages.size(), 6); + QTRY_COMPARE(page.messages[5], QString("Down")); + + makeScroll(view.focusProxy(), p, globalPos, QPoint(0, 120)); + QTRY_COMPARE(page.messages.size(), 7); + QTRY_COMPARE(page.messages[6], QString("Up")); + + makeScroll(view.focusProxy(), p, globalPos, QPoint(-120, 0)); + QTRY_COMPARE(page.messages.size(), 8); + QTRY_COMPARE(page.messages[7], QString("Right")); + + makeScroll(view.focusProxy(), p, globalPos, QPoint(120, 0)); + QTRY_COMPARE(page.messages.size(), 9); + QTRY_COMPARE(page.messages[8], QString("Left")); +} + +void tst_QWebEnginePage::openLinkInNewPageWithWebWindowType_data() +{ + QTest::addColumn<QWebEnginePage::WebWindowType>("webWindowType"); + QTest::addColumn<QString>("elementId"); + QTest::addColumn<Qt::MouseButton>("button"); + QTest::addColumn<Qt::KeyboardModifier>("keyboardModififer"); + QTest::newRow("webBrowserWindow") + << QWebEnginePage::WebBrowserWindow << "link" << Qt::LeftButton << Qt::ShiftModifier; + QTest::newRow("webBrowserTab") + << QWebEnginePage::WebBrowserTab << "link" << Qt::LeftButton << Qt::NoModifier; + QTest::newRow("webDialog") << QWebEnginePage::WebDialog << "openWindow" << Qt::LeftButton + << Qt::NoModifier; + QTest::newRow("webBrowserBackgroundTab") << QWebEnginePage::WebBrowserBackgroundTab << "link" + << Qt::MiddleButton << Qt::NoModifier; +} + +class WebWindowTypeTestPage : public QWebEnginePage +{ + Q_OBJECT + +public: + WebWindowType windowType; + +signals: + void windowCreated(); + +private: + QWebEnginePage *createWindow(WebWindowType type) override + { + windowType = type; + emit windowCreated(); + return nullptr; + } +}; + +void tst_QWebEnginePage::openLinkInNewPageWithWebWindowType() +{ + QFETCH(QWebEnginePage::WebWindowType, webWindowType); + QFETCH(QString, elementId); + QFETCH(Qt::MouseButton, button); + QFETCH(Qt::KeyboardModifier, keyboardModififer); + + WebWindowTypeTestPage page; + QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + QSignalSpy windowCreatedSpy(&page, &WebWindowTypeTestPage::windowCreated); + QWebEngineView view(&page); + view.resize(640, 480); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + page.settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true); + page.settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true); + QString html = "<html><body>" + "<a id='link' href='hello' target='_blank'>link</a>" + "<br><br>" + "<button id='openWindow' onclick='myFunction()'>Try it</button>" + "<script>" + "function myFunction() {" + " const myWindow = window.open('', '', 'width=300,height=300');" + "}" + "</script>" + "</body></html>"; + + page.setHtml(html); + QVERIFY(loadFinishedSpy.wait()); + + QTest::mouseClick(view.focusProxy(), button, keyboardModififer, + elementCenter(&page, elementId)); + QVERIFY(windowCreatedSpy.wait()); + QCOMPARE(page.windowType, webWindowType); +} + +class DoNothingInterceptor : public QWebEngineUrlRequestInterceptor +{ +public: + DoNothingInterceptor() { } + + void interceptRequest(QWebEngineUrlRequestInfo &) override + { + ran = true; + } + bool ran = false; +}; + +void tst_QWebEnginePage::keepInterceptorAfterNewWindowRequested() +{ + DoNothingInterceptor interceptor; + QWebEnginePage page; + page.setUrlRequestInterceptor(&interceptor); + connect(&page, &QWebEnginePage::newWindowRequested, [&](QWebEngineNewWindowRequest &request) { + request.openIn(&page); + }); + QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + + QWebEngineView view; + view.resize(500, 500); + view.setPage(&page); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + page.setHtml("<html><body>" + "<a id='link' href='hello' target='_blank'>link</a>" + "</body></html>"); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QVERIFY(loadFinishedSpy.takeFirst().value(0).toBool()); + QVERIFY(interceptor.ran); + interceptor.ran = false; + + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, elementCenter(&page, "link")); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QVERIFY(loadFinishedSpy.takeFirst().value(0).toBool()); + QVERIFY(!interceptor.ran); + + page.setHtml("<html><body></body></html>"); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QVERIFY(loadFinishedSpy.takeFirst().value(0).toBool()); + QVERIFY(interceptor.ran); +} + +void tst_QWebEnginePage::chooseDesktopMedia() +{ +#if QT_CONFIG(webengine_extensions) && QT_CONFIG(webengine_webrtc) + HttpServer server; + server.setHostDomain("localhost"); + connect(&server, &HttpServer::newRequest, &server, [&] (HttpReqRep *r) { + if (r->requestMethod() == "GET") + r->setResponseBody("<html></html>"); + }); + QVERIFY(server.start()); + + QWebEnginePage page; + QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + page.settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true); + page.profile()->setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions); + + bool desktopMediaRequested = false; + bool permissionRequested = false; + + connect(&page, &QWebEnginePage::desktopMediaRequested, + [&](const QWebEngineDesktopMediaRequest &) { + desktopMediaRequested = true; + }); + + connect(&page, &QWebEnginePage::featurePermissionRequested, + [&](const QUrl &securityOrigin, QWebEnginePage::Feature feature) { + permissionRequested = true; + // Handle permission to 'complete' the media request + page.setFeaturePermission(securityOrigin, feature, + QWebEnginePage::PermissionGrantedByUser); + }); + + page.load(QUrl(server.url())); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 20000); + + const QString extensionId("nkeimhogjdpnpccoofpliimaahmaaome"); + page.runJavaScript(QString("(() => {" + " let port = chrome.runtime.connect(\"%1\", {name: \"chooseDesktopMedia\"});" + " port.postMessage({method: \"chooseDesktopMedia\"});" + "})()").arg(extensionId)); + + QTRY_VERIFY(desktopMediaRequested); + QTRY_VERIFY(permissionRequested); +#endif // QT_CONFIG(webengine_extensions) && QT_CONFIG(webengine_webrtc) } static QByteArrayList params = {QByteArrayLiteral("--use-fake-device-for-media-stream")}; diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc deleted file mode 100644 index 013a307de..000000000 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc +++ /dev/null @@ -1,31 +0,0 @@ -<!DOCTYPE RCC><RCC version="1.0"> -<qresource> - <file>resources/content.html</file> - <file>resources/dynamicFrame.html</file> - <file>resources/index.html</file> - <file>resources/frame_a.html</file> - <file>resources/frame_c.html</file> - <file>resources/iframe.html</file> - <file>resources/iframe2.html</file> - <file>resources/iframe3.html</file> - <file>resources/framedindex.html</file> - <file>resources/fullscreen.html</file> - <file>resources/script.html</file> - <file>resources/user.css</file> - <file>resources/image.png</file> - <file>resources/pasteimage.html</file> - <file>resources/reload.html</file> - <file>resources/style.css</file> - <file>resources/test1.html</file> - <file>resources/test2.html</file> - <file>resources/testiframe.html</file> - <file>resources/testiframe2.html</file> - <file>resources/foo.txt</file> - <file>resources/bar.txt</file> - <file>resources/path with spaces.txt</file> - <file>resources/lifecycle.html</file> -</qresource> -<qresource prefix='/shared'> - <file alias='notification.html'>../../shared/data/notification.html</file> -</qresource> -</RCC> diff --git a/tests/auto/widgets/qwebengineprofile/BLACKLIST b/tests/auto/widgets/qwebengineprofile/BLACKLIST deleted file mode 100644 index 55806eec4..000000000 --- a/tests/auto/widgets/qwebengineprofile/BLACKLIST +++ /dev/null @@ -1,3 +0,0 @@ -[disableCache] -* - diff --git a/tests/auto/widgets/qwebengineprofile/CMakeLists.txt b/tests/auto/widgets/qwebengineprofile/CMakeLists.txt new file mode 100644 index 000000000..d7393eaef --- /dev/null +++ b/tests/auto/widgets/qwebengineprofile/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../httpserver/httpserver.cmake) +include(../../util/util.cmake) + +qt_internal_add_test(tst_qwebengineprofile + SOURCES + tst_qwebengineprofile.cpp + LIBRARIES + Qt::WebEngineWidgets + Test::HttpServer + Test::Util +) diff --git a/tests/auto/widgets/qwebengineprofile/qwebengineprofile.pro b/tests/auto/widgets/qwebengineprofile/qwebengineprofile.pro deleted file mode 100644 index e56bbe8f7..000000000 --- a/tests/auto/widgets/qwebengineprofile/qwebengineprofile.pro +++ /dev/null @@ -1,3 +0,0 @@ -include(../tests.pri) -exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc -QT *= core-private gui-private diff --git a/tests/auto/widgets/qwebengineprofile/resources/hedgehog.html b/tests/auto/widgets/qwebengineprofile/resources/hedgehog.html new file mode 100644 index 000000000..d8abbcd48 --- /dev/null +++ b/tests/auto/widgets/qwebengineprofile/resources/hedgehog.html @@ -0,0 +1,9 @@ +<!doctype html> +<html> + <head> + <title>BREAKING NEWS: 15 Hedgehogs With Things That Look Like Hedgehogs</title> + </head> + <body> + <img src="hedgehog.png"/> + </body> +</html> diff --git a/tests/auto/widgets/qwebengineprofile/resources/hedgehog.png b/tests/auto/widgets/qwebengineprofile/resources/hedgehog.png Binary files differnew file mode 100644 index 000000000..4d56d8633 --- /dev/null +++ b/tests/auto/widgets/qwebengineprofile/resources/hedgehog.png diff --git a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp index 0fc494a36..ef069ac1c 100644 --- a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp +++ b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp @@ -1,45 +1,29 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "../util.h" +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <util.h> #include <QtCore/qbuffer.h> +#include <QtCore/qmimedatabase.h> #include <QtTest/QtTest> #include <QtWebEngineCore/qwebengineurlrequestinterceptor.h> #include <QtWebEngineCore/qwebengineurlrequestjob.h> #include <QtWebEngineCore/qwebenginecookiestore.h> #include <QtWebEngineCore/qwebengineurlscheme.h> #include <QtWebEngineCore/qwebengineurlschemehandler.h> -#include <QtWebEngineWidgets/qwebengineprofile.h> -#include <QtWebEngineWidgets/qwebenginepage.h> -#include <QtWebEngineWidgets/qwebenginesettings.h> +#include <QtWebEngineCore/qwebenginesettings.h> +#include <QtWebEngineCore/qwebengineprofile.h> +#include <QtWebEngineCore/qwebenginepage.h> +#include <QtWebEngineCore/qwebenginedownloadrequest.h> #include <QtWebEngineWidgets/qwebengineview.h> -#include <QtWebEngineWidgets/qwebenginedownloaditem.h> +#if QT_CONFIG(webengine_webchannel) +#include <QWebChannel> +#endif + +#include <httpserver.h> +#include <httpreqrep.h> + +#include <map> #include <mutex> class tst_QWebEngineProfile : public QObject @@ -48,24 +32,32 @@ class tst_QWebEngineProfile : public QObject private Q_SLOTS: void initTestCase(); - void init(); - void cleanup(); - void privateProfile(); - void testProfile(); + void defaultProfile_data(); + void defaultProfile(); + void userDefinedProfile(); void clearDataFromCache(); void disableCache(); void urlSchemeHandlers(); void urlSchemeHandlerFailRequest(); void urlSchemeHandlerFailOnRead(); void urlSchemeHandlerStreaming(); + void urlSchemeHandlerStreaming2(); void urlSchemeHandlerRequestHeaders(); void urlSchemeHandlerInstallation(); + void urlSchemeHandlerXhrStatus(); + void urlSchemeHandlerScriptModule(); + void urlSchemeHandlerLongReply(); void customUserAgent(); void httpAcceptLanguage(); void downloadItem(); void changePersistentPath(); + void changeHttpUserAgent(); + void changeHttpAcceptLanguage(); + void changePersistentCookiesPolicy(); void initiator(); void badDeleteOrder(); + void permissionPersistence_data(); + void permissionPersistence(); void qtbug_71895(); // this should be the last test }; @@ -75,149 +67,184 @@ void tst_QWebEngineProfile::initTestCase() QWebEngineUrlScheme stream("stream"); QWebEngineUrlScheme letterto("letterto"); QWebEngineUrlScheme aviancarrier("aviancarrier"); + QWebEngineUrlScheme myscheme("myscheme"); foo.setSyntax(QWebEngineUrlScheme::Syntax::Host); stream.setSyntax(QWebEngineUrlScheme::Syntax::HostAndPort); stream.setDefaultPort(8080); letterto.setSyntax(QWebEngineUrlScheme::Syntax::Path); aviancarrier.setSyntax(QWebEngineUrlScheme::Syntax::Path); + aviancarrier.setFlags(QWebEngineUrlScheme::CorsEnabled); QWebEngineUrlScheme::registerScheme(foo); QWebEngineUrlScheme::registerScheme(stream); QWebEngineUrlScheme::registerScheme(letterto); QWebEngineUrlScheme::registerScheme(aviancarrier); + QWebEngineUrlScheme::registerScheme(myscheme); } -void tst_QWebEngineProfile::init() -{ - //make sure defualt global profile is 'default' across all the tests - QWebEngineProfile *profile = QWebEngineProfile::defaultProfile(); - QVERIFY(profile); - QVERIFY(!profile->isOffTheRecord()); - QCOMPARE(profile->storageName(), QStringLiteral("Default")); - QCOMPARE(profile->httpCacheType(), QWebEngineProfile::DiskHttpCache); - QCOMPARE(profile->persistentCookiesPolicy(), QWebEngineProfile::AllowPersistentCookies); - QCOMPARE(profile->cachePath(), QStandardPaths::writableLocation(QStandardPaths::CacheLocation) - + QStringLiteral("/QtWebEngine/Default")); - QCOMPARE(profile->persistentStoragePath(), QStandardPaths::writableLocation(QStandardPaths::DataLocation) - + QStringLiteral("/QtWebEngine/Default")); -} +static QString StandardCacheLocation() { static auto p = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); return p; } +static QString StandardAppDataLocation() { static auto p = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); return p; } -void tst_QWebEngineProfile::cleanup() +void tst_QWebEngineProfile::defaultProfile_data() { - QWebEngineProfile *profile = QWebEngineProfile::defaultProfile(); - profile->setCachePath(QString()); - profile->setPersistentStoragePath(QString()); - profile->setHttpCacheType(QWebEngineProfile::DiskHttpCache); - profile->removeAllUrlSchemeHandlers(); + QTest::addColumn<bool>("userCreated"); + QTest::addRow("global") << false; + QTest::addRow("user") << true; } -void tst_QWebEngineProfile::privateProfile() +void tst_QWebEngineProfile::defaultProfile() { - QWebEngineProfile otrProfile; - QVERIFY(otrProfile.isOffTheRecord()); - QCOMPARE(otrProfile.httpCacheType(), QWebEngineProfile::MemoryHttpCache); - QCOMPARE(otrProfile.persistentCookiesPolicy(), QWebEngineProfile::NoPersistentCookies); - QCOMPARE(otrProfile.cachePath(), QString()); - QCOMPARE(otrProfile.persistentStoragePath(), QString()); + QFETCH(bool, userCreated); + QScopedPointer<QWebEngineProfile> p(userCreated ? new QWebEngineProfile : nullptr); + QWebEngineProfile *profile = userCreated ? p.get() : QWebEngineProfile::defaultProfile(); + QVERIFY(profile); + QVERIFY(profile->isOffTheRecord()); + QCOMPARE(profile->storageName(), QString()); + QCOMPARE(profile->httpCacheType(), QWebEngineProfile::MemoryHttpCache); + QCOMPARE(profile->persistentCookiesPolicy(), QWebEngineProfile::NoPersistentCookies); + QCOMPARE(profile->cachePath(), QString()); + QCOMPARE(profile->persistentStoragePath(), StandardAppDataLocation() + QStringLiteral("/QtWebEngine/OffTheRecord")); // TBD: setters do not really work - otrProfile.setCachePath(QStringLiteral("/home/foo/bar")); - QCOMPARE(otrProfile.cachePath(), QString()); - otrProfile.setPersistentStoragePath(QStringLiteral("/home/foo/bar")); - QCOMPARE(otrProfile.persistentStoragePath(), QString()); - otrProfile.setHttpCacheType(QWebEngineProfile::DiskHttpCache); - QCOMPARE(otrProfile.httpCacheType(), QWebEngineProfile::MemoryHttpCache); - otrProfile.setPersistentCookiesPolicy(QWebEngineProfile::ForcePersistentCookies); - QCOMPARE(otrProfile.persistentCookiesPolicy(), QWebEngineProfile::NoPersistentCookies); + profile->setCachePath(QStringLiteral("/home/foo/bar")); + QCOMPARE(profile->cachePath(), QString()); + profile->setPersistentStoragePath(QStringLiteral("/home/foo/bar")); + QCOMPARE(profile->persistentStoragePath(), QStringLiteral("/home/foo/bar")); + profile->setHttpCacheType(QWebEngineProfile::DiskHttpCache); + QCOMPARE(profile->httpCacheType(), QWebEngineProfile::MemoryHttpCache); + profile->setPersistentCookiesPolicy(QWebEngineProfile::ForcePersistentCookies); + QCOMPARE(profile->persistentCookiesPolicy(), QWebEngineProfile::NoPersistentCookies); } - -void tst_QWebEngineProfile::testProfile() +void tst_QWebEngineProfile::userDefinedProfile() { QWebEngineProfile profile(QStringLiteral("Test")); QVERIFY(!profile.isOffTheRecord()); QCOMPARE(profile.storageName(), QStringLiteral("Test")); QCOMPARE(profile.httpCacheType(), QWebEngineProfile::DiskHttpCache); QCOMPARE(profile.persistentCookiesPolicy(), QWebEngineProfile::AllowPersistentCookies); - QCOMPARE(profile.cachePath(), QStandardPaths::writableLocation(QStandardPaths::CacheLocation) - + QStringLiteral("/QtWebEngine/Test")); - QCOMPARE(profile.persistentStoragePath(), QStandardPaths::writableLocation(QStandardPaths::DataLocation) - + QStringLiteral("/QtWebEngine/Test")); + QCOMPARE(profile.cachePath(), StandardCacheLocation() + QStringLiteral("/QtWebEngine/Test")); + QCOMPARE(profile.persistentStoragePath(), StandardAppDataLocation() + QStringLiteral("/QtWebEngine/Test")); } -void tst_QWebEngineProfile::clearDataFromCache() +class AutoDir : public QDir { - QWebEnginePage page; +public: + AutoDir(const QString &p) : QDir(p) + { + makeAbsolute(); + removeRecursively(); + } - QDir cacheDir("./tst_QWebEngineProfile_cacheDir"); - cacheDir.makeAbsolute(); - if (cacheDir.exists()) - cacheDir.removeRecursively(); - cacheDir.mkpath(cacheDir.path()); + ~AutoDir() { removeRecursively(); } +}; +qint64 totalSize(QDir dir) +{ + qint64 sum = 0; + const QDir::Filters filters{QDir::Dirs, QDir::Files, QDir::NoSymLinks, QDir::NoDotAndDotDot}; + for (const QFileInfo &entry : dir.entryInfoList(filters)) { + if (entry.isFile()) + sum += entry.size(); + else if (entry.isDir()) + sum += totalSize(entry.filePath()); + } + return sum; +} - QWebEngineProfile *profile = page.profile(); - profile->setCachePath(cacheDir.path()); - profile->setHttpCacheType(QWebEngineProfile::DiskHttpCache); +class TestServer : public HttpServer +{ +public: + TestServer() + { + connect(this, &HttpServer::newRequest, this, &TestServer::onNewRequest); + } - QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); - page.load(QUrl("http://qt-project.org")); - if (!loadFinishedSpy.wait(10000) || !loadFinishedSpy.at(0).at(0).toBool()) - QSKIP("Couldn't load page from network, skipping test."); +private: + void onNewRequest(HttpReqRep *rr) + { + const QDir resourceDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + "/resources"); + QString path = rr->requestPath(); + path.remove(0, 1); - cacheDir.refresh(); - QVERIFY(cacheDir.entryList().contains("Cache")); - cacheDir.cd("./Cache"); - int filesBeforeClear = cacheDir.entryList().count(); + if (rr->requestMethod() != "GET" || !resourceDir.exists(path)) { + rr->sendResponse(404); + return; + } - QFileSystemWatcher fileSystemWatcher; - fileSystemWatcher.addPath(cacheDir.path()); - QSignalSpy directoryChangedSpy(&fileSystemWatcher, SIGNAL(directoryChanged(const QString &))); + QFile file(resourceDir.filePath(path)); + file.open(QIODevice::ReadOnly); + QByteArray data = file.readAll(); + rr->setResponseBody(data); + QMimeDatabase db; + QMimeType mime = db.mimeTypeForFileNameAndData(file.fileName(), data); + rr->setResponseHeader(QByteArrayLiteral("content-type"), mime.name().toUtf8()); + if (!mime.inherits("text/html")) + rr->setResponseHeader(QByteArrayLiteral("cache-control"), + QByteArrayLiteral("public, max-age=31536000")); + rr->sendResponse(); + } +}; - // It deletes most of the files, but not all of them. - profile->clearHttpCache(); - QTest::qWait(1000); - QTRY_VERIFY(directoryChangedSpy.count() > 0); +void tst_QWebEngineProfile::clearDataFromCache() +{ + TestServer server; + QSignalSpy serverSpy(&server, &HttpServer::newRequest); + QVERIFY(server.start()); - cacheDir.refresh(); - QVERIFY(filesBeforeClear > cacheDir.entryList().count()); + AutoDir cacheDir("./tst_QWebEngineProfile_clearDataFromCache"); + QVERIFY(!cacheDir.exists("Cache")); - cacheDir.removeRecursively(); + QWebEngineProfile profile(QStringLiteral("clearDataFromCache")); + QSignalSpy cacheSpy(&profile, &QWebEngineProfile::clearHttpCacheCompleted); + profile.setCachePath(cacheDir.path()); + profile.setHttpCacheType(QWebEngineProfile::DiskHttpCache); + + QWebEnginePage page(&profile); + QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); + // Wait for GET /favicon.ico + QTRY_COMPARE(serverSpy.size(), 3); + + QVERIFY(cacheDir.exists("Cache")); + qint64 sizeBeforeClear = totalSize(cacheDir); + QCOMPARE_GT(sizeBeforeClear, 0); + profile.clearHttpCache(); + QTRY_COMPARE(cacheSpy.size(), 1); +#if defined(Q_OS_WIN) + QTRY_COMPARE_GT(sizeBeforeClear, totalSize(cacheDir)); +#else + QCOMPARE_GT(sizeBeforeClear, totalSize(cacheDir)); +#endif + + (void)server.stop(); } void tst_QWebEngineProfile::disableCache() { - QWebEnginePage page; - QDir cacheDir("./tst_QWebEngineProfile_cacheDir"); - if (cacheDir.exists()) - cacheDir.removeRecursively(); - cacheDir.mkpath(cacheDir.path()); + TestServer server; + QVERIFY(server.start()); - QWebEngineProfile *profile = page.profile(); - profile->setCachePath(cacheDir.path()); - QVERIFY(!cacheDir.entryList().contains("Cache")); - - profile->setHttpCacheType(QWebEngineProfile::NoCache); - QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); - page.load(QUrl("http://qt-project.org")); - if (!loadFinishedSpy.wait(10000) || !loadFinishedSpy.at(0).at(0).toBool()) - QSKIP("Couldn't load page from network, skipping test."); + AutoDir cacheDir("./tst_QWebEngineProfile_disableCache"); - cacheDir.refresh(); - QVERIFY(!cacheDir.entryList().contains("Cache")); + QWebEngineProfile profile("disableCache"); + QWebEnginePage page(&profile); + profile.setCachePath(cacheDir.path()); + QVERIFY(!cacheDir.exists("Cache")); - profile->setHttpCacheType(QWebEngineProfile::DiskHttpCache); - page.load(QUrl("http://qt-project.org")); - if (!loadFinishedSpy.wait(10000) || !loadFinishedSpy.at(1).at(0).toBool()) - QSKIP("Couldn't load page from network, skipping test."); + profile.setHttpCacheType(QWebEngineProfile::NoCache); + // Wait for cache to be cleared. + QTest::qWait(1000); + QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); + QVERIFY(!cacheDir.exists("Cache")); - cacheDir.refresh(); - QVERIFY(cacheDir.entryList().contains("Cache")); + profile.setHttpCacheType(QWebEngineProfile::DiskHttpCache); + QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); + QVERIFY(cacheDir.exists("Cache")); - cacheDir.removeRecursively(); + (void)server.stop(); } class RedirectingUrlSchemeHandler : public QWebEngineUrlSchemeHandler { public: - void requestStarted(QWebEngineUrlRequestJob *job) + void requestStarted(QWebEngineUrlRequestJob *job) override { job->redirect(QUrl(QStringLiteral("data:text/plain;charset=utf-8,") + job->requestUrl().fileName())); @@ -235,7 +262,7 @@ public: { } - void requestStarted(QWebEngineUrlRequestJob *job) + void requestStarted(QWebEngineUrlRequestJob *job) override { QBuffer *buffer = new QBuffer(job); buffer->setData(job->requestUrl().toString().toUtf8()); @@ -246,10 +273,12 @@ public: QList<QPointer<QBuffer>> m_buffers; }; -class StreamingIODevice : public QIODevice { +// an evil version constantly claiming to be at end, similar to QNetworkReply +class StreamingIODeviceBasic : public QIODevice +{ Q_OBJECT public: - StreamingIODevice(QObject *parent) : QIODevice(parent), m_bytesRead(0), m_bytesAvailable(0) + StreamingIODeviceBasic(QObject *parent) : QIODevice(parent), m_bytesRead(0), m_bytesAvailable(0) { setOpenMode(QIODevice::ReadOnly); m_timer.start(100, this); @@ -260,12 +289,11 @@ public: const std::lock_guard<QRecursiveMutex> lock(m_mutex); return m_bytesAvailable; } - bool atEnd() const override +protected: + bool internalAtEnd() const { - const std::lock_guard<QRecursiveMutex> lock(m_mutex); return (m_data.size() >= 1000 && m_bytesRead >= 1000); } -protected: void timerEvent(QTimerEvent *) override { const std::lock_guard<QRecursiveMutex> lock(m_mutex); @@ -286,7 +314,7 @@ protected: memcpy(data, m_data.constData() + m_bytesRead, len); m_bytesAvailable -= len; m_bytesRead += len; - } else if (m_data.size() > 0) + } else if (internalAtEnd()) return -1; return len; @@ -296,19 +324,26 @@ protected: return 0; } -private: -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - mutable QMutex m_mutex{QMutex::Recursive}; - using QRecursiveMutex = QMutex; -#else mutable QRecursiveMutex m_mutex; -#endif +private: QByteArray m_data; QBasicTimer m_timer; int m_bytesRead; int m_bytesAvailable; }; +// A nicer version implementing atEnd +class StreamingIODevice : public StreamingIODeviceBasic +{ +public: + StreamingIODevice(QObject *parent) : StreamingIODeviceBasic(parent) {} + bool atEnd() const override + { + const std::lock_guard<QRecursiveMutex> lock(m_mutex); + return internalAtEnd(); + } +}; + class StreamingUrlSchemeHandler : public QWebEngineUrlSchemeHandler { public: @@ -317,26 +352,25 @@ public: { } - void requestStarted(QWebEngineUrlRequestJob *job) + void requestStarted(QWebEngineUrlRequestJob *job) override { job->reply("text/plain;charset=utf-8", new StreamingIODevice(job)); } }; -static bool loadSync(QWebEngineView *view, const QUrl &url, int timeout = 5000) +class StreamingUrlSchemeHandler2 : public QWebEngineUrlSchemeHandler { - // Ripped off QTRY_VERIFY. - QSignalSpy loadFinishedSpy(view, SIGNAL(loadFinished(bool))); - view->load(url); - if (loadFinishedSpy.isEmpty()) - QTest::qWait(0); - for (int i = 0; i < timeout; i += 50) { - if (!loadFinishedSpy.isEmpty()) - return true; - QTest::qWait(50); +public: + StreamingUrlSchemeHandler2(QObject *parent = nullptr) + : QWebEngineUrlSchemeHandler(parent) + { } - return false; -} + + void requestStarted(QWebEngineUrlRequestJob *job) override + { + job->reply("text/plain;charset=utf-8", new StreamingIODeviceBasic(job)); + } +}; void tst_QWebEngineProfile::urlSchemeHandlers() { @@ -347,42 +381,42 @@ void tst_QWebEngineProfile::urlSchemeHandlers() view.setPage(new QWebEnginePage(&profile, &view)); view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); QString emailAddress = QStringLiteral("egon@olsen-banden.dk"); - QVERIFY(loadSync(&view, QUrl(QStringLiteral("letterto:") + emailAddress))); + QVERIFY(loadSync(view.page(), QUrl(QStringLiteral("letterto:") + emailAddress))); QCOMPARE(toPlainTextSync(view.page()), emailAddress); // Install a gopher handler after the view has been fully initialized. ReplyingUrlSchemeHandler gopherHandler; profile.installUrlSchemeHandler("gopher", &gopherHandler); QUrl url = QUrl(QStringLiteral("gopher://olsen-banden.dk/benny")); - QVERIFY(loadSync(&view, url)); + QVERIFY(loadSync(view.page(), url)); QCOMPARE(toPlainTextSync(view.page()), url.toString()); // Remove the letterto scheme, and check whether it is not handled anymore. profile.removeUrlScheme("letterto"); emailAddress = QStringLiteral("kjeld@olsen-banden.dk"); - QVERIFY(loadSync(&view, QUrl(QStringLiteral("letterto:") + emailAddress))); + QVERIFY(loadSync(view.page(), QUrl(QStringLiteral("letterto:") + emailAddress), false)); QVERIFY(toPlainTextSync(view.page()) != emailAddress); // Check if gopher is still working after removing letterto. url = QUrl(QStringLiteral("gopher://olsen-banden.dk/yvonne")); - QVERIFY(loadSync(&view, url)); + QVERIFY(loadSync(view.page(), url)); QCOMPARE(toPlainTextSync(view.page()), url.toString()); // Does removeAll work? profile.removeAllUrlSchemeHandlers(); url = QUrl(QStringLiteral("gopher://olsen-banden.dk/harry")); - QVERIFY(loadSync(&view, url)); + QVERIFY(loadSync(view.page(), url, false)); QVERIFY(toPlainTextSync(view.page()) != url.toString()); // Install a handler that is owned by the view. Make sure this doesn't crash on shutdown. profile.installUrlSchemeHandler("aviancarrier", new ReplyingUrlSchemeHandler(&view)); url = QUrl(QStringLiteral("aviancarrier:inspector.mortensen@politistyrke.dk")); - QVERIFY(loadSync(&view, url)); + QVERIFY(loadSync(view.page(), url)); QCOMPARE(toPlainTextSync(view.page()), url.toString()); // Check that all buffers got deleted - QCOMPARE(gopherHandler.m_buffers.count(), 2); - for (int i = 0; i < gopherHandler.m_buffers.count(); ++i) + QCOMPARE(gopherHandler.m_buffers.size(), 2); + for (int i = 0; i < gopherHandler.m_buffers.size(); ++i) QVERIFY(gopherHandler.m_buffers.at(i).isNull()); } @@ -442,7 +476,8 @@ void tst_QWebEngineProfile::urlSchemeHandlerFailRequest() view.setPage(new QWebEnginePage(&profile, &view)); view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); view.load(QUrl(QStringLiteral("foo://bar"))); - QVERIFY(loadFinishedSpy.wait()); + view.show(); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); QCOMPARE(toPlainTextSync(view.page()), QString()); } @@ -456,7 +491,8 @@ void tst_QWebEngineProfile::urlSchemeHandlerFailOnRead() view.setPage(new QWebEnginePage(&profile, &view)); view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); view.load(QUrl(QStringLiteral("foo://bar"))); - QVERIFY(loadFinishedSpy.wait()); + view.show(); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); QCOMPARE(toPlainTextSync(view.page()), QString()); } @@ -470,7 +506,25 @@ void tst_QWebEngineProfile::urlSchemeHandlerStreaming() view.setPage(new QWebEnginePage(&profile, &view)); view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); view.load(QUrl(QStringLiteral("stream://whatever"))); - QVERIFY(loadFinishedSpy.wait()); + view.show(); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); + QByteArray result; + result.append(1000, 'c'); + QCOMPARE(toPlainTextSync(view.page()), QString::fromLatin1(result)); +} + +void tst_QWebEngineProfile::urlSchemeHandlerStreaming2() +{ + StreamingUrlSchemeHandler2 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"))); + view.show(); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); QByteArray result; result.append(1000, 'c'); QCOMPARE(toPlainTextSync(view.page()), QString::fromLatin1(result)); @@ -526,12 +580,12 @@ void tst_QWebEngineProfile::urlSchemeHandlerRequestHeaders() QWebEngineProfile profile; profile.installUrlSchemeHandler("myscheme", &handler); - profile.setRequestInterceptor(&interceptor); + profile.setUrlRequestInterceptor(&interceptor); QWebEnginePage page(&profile); QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); page.load(QUrl(QStringLiteral("myscheme://whatever"))); - QVERIFY(loadFinishedSpy.wait()); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); } void tst_QWebEngineProfile::urlSchemeHandlerInstallation() @@ -575,13 +629,169 @@ void tst_QWebEngineProfile::urlSchemeHandlerInstallation() profile.removeUrlScheme("tst"); } +#if QT_CONFIG(webengine_webchannel) +class XhrStatusHost : public QObject +{ + Q_OBJECT +public: + std::map<QUrl, int> requests; + + bool isReady() + { + static const auto sig = QMetaMethod::fromSignal(&XhrStatusHost::load); + return isSignalConnected(sig); + } + +Q_SIGNALS: + void load(QUrl url); + +public Q_SLOTS: + void loadFinished(QUrl url, int status) + { + requests[url] = status; + } + +private: +}; + +class XhrStatusUrlSchemeHandler : public QWebEngineUrlSchemeHandler +{ +public: + void requestStarted(QWebEngineUrlRequestJob *job) override + { + QString path = job->requestUrl().path(); + if (path == "/") { + QBuffer *buffer = new QBuffer(job); + buffer->open(QBuffer::ReadWrite); + buffer->write(QByteArrayLiteral(R"( +<html> + <body> + <script src="qwebchannel.js"></script> + <script> + new QWebChannel(qt.webChannelTransport, (channel) => { + const host = channel.objects.host; + host.load.connect((url) => { + const xhr = new XMLHttpRequest(); + xhr.onload = () => { host.loadFinished(url, xhr.status); }; + xhr.onerror = () => { host.loadFinished(url, -1); }; + xhr.open("GET", url, true); + xhr.send(); + }); + }); + </script> + </body> +</html> +)")); + buffer->seek(0); + job->reply("text/html", buffer); + } else if (path == "/qwebchannel.js") { + QFile *file = new QFile(":/qtwebchannel/qwebchannel.js", job); + file->open(QFile::ReadOnly); + job->reply("application/javascript", file); + } else if (path == "/ok") { + QBuffer *buffer = new QBuffer(job); + buffer->buffer() = QByteArrayLiteral("ok"); + job->reply("text/plain", buffer); + } else if (path == "/redirect") { + QUrl url = job->requestUrl(); + url.setPath("/ok"); + job->redirect(url); + } else if (path == "/fail") { + job->fail(QWebEngineUrlRequestJob::RequestFailed); + } else { + job->fail(QWebEngineUrlRequestJob::UrlNotFound); + } + } +}; +#endif + +void tst_QWebEngineProfile::urlSchemeHandlerXhrStatus() +{ +#if QT_CONFIG(webengine_webchannel) + XhrStatusUrlSchemeHandler handler; + XhrStatusHost host; + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QWebChannel channel; + channel.registerObject("host", &host); + profile.installUrlSchemeHandler("aviancarrier", &handler); + page.setWebChannel(&channel); + page.load(QUrl("aviancarrier:/")); + QTRY_VERIFY_WITH_TIMEOUT(host.isReady(), 30000); + host.load(QUrl("aviancarrier:/ok")); + host.load(QUrl("aviancarrier:/redirect")); + host.load(QUrl("aviancarrier:/fail")); + host.load(QUrl("aviancarrier:/notfound")); + QTRY_COMPARE(host.requests.size(), 4u); + QCOMPARE(host.requests[QUrl("aviancarrier:/ok")], 200); + QCOMPARE(host.requests[QUrl("aviancarrier:/redirect")], 200); + QCOMPARE(host.requests[QUrl("aviancarrier:/fail")], -1); + QCOMPARE(host.requests[QUrl("aviancarrier:/notfound")], -1); +#else + QSKIP("No QtWebChannel"); +#endif +} + +class ScriptsUrlSchemeHandler : public QWebEngineUrlSchemeHandler +{ +public: + void requestStarted(QWebEngineUrlRequestJob *job) override + { + auto *script = new QBuffer(job); + script->setData(QByteArrayLiteral("window.test = 'SUCCESS';")); + job->reply("text/javascript", script); + } +}; + +void tst_QWebEngineProfile::urlSchemeHandlerScriptModule() +{ + ScriptsUrlSchemeHandler handler; + QWebEngineProfile profile; + profile.installUrlSchemeHandler("aviancarrier", &handler); + QWebEnginePage page(&profile); + QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + page.setHtml(QStringLiteral("<html><head><script src=\"aviancarrier:///\"></script></head><body>Test1</body></html>")); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("test")).toString(), QStringLiteral("SUCCESS")); + + loadFinishedSpy.clear(); + page.setHtml(QStringLiteral("<html><head><script type=\"module\" src=\"aviancarrier:///\"></script></head><body>Test2</body></html>")); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("test")).toString(), QStringLiteral("SUCCESS")); +} + +class LongReplyUrlSchemeHandler : public QWebEngineUrlSchemeHandler +{ +public: + LongReplyUrlSchemeHandler(QObject *parent = nullptr) : QWebEngineUrlSchemeHandler(parent) {} + ~LongReplyUrlSchemeHandler() {} + + void requestStarted(QWebEngineUrlRequestJob *job) override + { + QBuffer *buffer = new QBuffer(job); + buffer->setData(QByteArray(128 * 1024, ' ') + + "<html><head><title>Minify this!</title></head></html>"); + job->reply("text/html", buffer); + } +}; + +void tst_QWebEngineProfile::urlSchemeHandlerLongReply() +{ + LongReplyUrlSchemeHandler handler; + QWebEngineProfile profile; + profile.installUrlSchemeHandler("aviancarrier", &handler); + QWebEnginePage page(&profile); + page.load(QUrl("aviancarrier:/")); + QTRY_COMPARE(page.title(), QString("Minify this!")); +} + void tst_QWebEngineProfile::customUserAgent() { QString defaultUserAgent = QWebEngineProfile::defaultProfile()->httpUserAgent(); QWebEnginePage page; QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); page.setHtml(QStringLiteral("<html><body>Hello world!</body></html>")); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); // First test the user-agent is default QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("navigator.userAgent")).toString(), defaultUserAgent); @@ -594,7 +804,7 @@ void tst_QWebEngineProfile::customUserAgent() QWebEnginePage page2(&testProfile); QSignalSpy loadFinishedSpy2(&page2, SIGNAL(loadFinished(bool))); page2.setHtml(QStringLiteral("<html><body>Hello again!</body></html>")); - QTRY_COMPARE(loadFinishedSpy2.count(), 1); + QTRY_COMPARE(loadFinishedSpy2.size(), 1); QCOMPARE(evaluateJavaScriptSync(&page2, QStringLiteral("navigator.userAgent")).toString(), testUserAgent); QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("navigator.userAgent")).toString(), defaultUserAgent); @@ -608,7 +818,7 @@ void tst_QWebEngineProfile::httpAcceptLanguage() QWebEnginePage page; QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); page.setHtml(QStringLiteral("<html><body>Hello world!</body></html>")); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); QStringList defaultLanguages = evaluateJavaScriptSync(&page, QStringLiteral("navigator.languages")).toStringList(); @@ -620,7 +830,7 @@ void tst_QWebEngineProfile::httpAcceptLanguage() QWebEnginePage page2(&testProfile); QSignalSpy loadFinishedSpy2(&page2, SIGNAL(loadFinished(bool))); page2.setHtml(QStringLiteral("<html><body>Hello again!</body></html>")); - QTRY_COMPARE(loadFinishedSpy2.count(), 1); + QTRY_COMPARE(loadFinishedSpy2.size(), 1); QCOMPARE(evaluateJavaScriptSync(&page2, QStringLiteral("navigator.languages")).toStringList(), QStringList(testLang)); // Test the old one wasn't affected QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("navigator.languages")).toStringList(), defaultLanguages); @@ -632,32 +842,116 @@ void tst_QWebEngineProfile::httpAcceptLanguage() void tst_QWebEngineProfile::downloadItem() { - qRegisterMetaType<QWebEngineDownloadItem *>(); + qRegisterMetaType<QWebEngineDownloadRequest *>(); QWebEngineProfile testProfile; QWebEnginePage page(&testProfile); - QSignalSpy downloadSpy(&testProfile, SIGNAL(downloadRequested(QWebEngineDownloadItem *))); - connect(&testProfile, &QWebEngineProfile::downloadRequested, this, [=] (QWebEngineDownloadItem *item) { item->accept(); }); + QSignalSpy downloadSpy(&testProfile, SIGNAL(downloadRequested(QWebEngineDownloadRequest *))); page.load(QUrl::fromLocalFile(QCoreApplication::applicationFilePath())); - QTRY_COMPARE(downloadSpy.count(), 1); + QTRY_COMPARE(downloadSpy.size(), 1); } void tst_QWebEngineProfile::changePersistentPath() { - QWebEngineProfile testProfile(QStringLiteral("Test")); - const QString oldPath = testProfile.persistentStoragePath(); - QVERIFY(oldPath.endsWith(QStringLiteral("Test"))); + TestServer server; + QVERIFY(server.start()); + + AutoDir dataDir1(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + + QStringLiteral("/QtWebEngine/changePersistentPath1")); + AutoDir dataDir2(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + + QStringLiteral("/QtWebEngine/changePersistentPath2")); + + QWebEngineProfile testProfile(QStringLiteral("changePersistentPath1")); + QCOMPARE(testProfile.persistentStoragePath(), dataDir1.path()); - // Make sure the profile has been used and the url-request-context-getter instantiated: + // Make sure the profile has been used: QWebEnginePage page(&testProfile); - QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); - page.load(QUrl("http://qt-project.org")); - if (!loadFinishedSpy.wait(10000) || !loadFinishedSpy.at(0).at(0).toBool()) - QSKIP("Couldn't load page from network, skipping test."); + QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); // Test we do not crash (QTBUG-55322): - testProfile.setPersistentStoragePath(oldPath + QLatin1Char('2')); - const QString newPath = testProfile.persistentStoragePath(); - QVERIFY(newPath.endsWith(QStringLiteral("Test2"))); + testProfile.setPersistentStoragePath(dataDir2.path()); + QCOMPARE(testProfile.persistentStoragePath(), dataDir2.path()); + QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); + QVERIFY(dataDir2.exists()); + + (void)server.stop(); +} + +void tst_QWebEngineProfile::changeHttpUserAgent() +{ + TestServer server; + QVERIFY(server.start()); + + QList<QByteArray> userAgents; + connect(&server, &HttpServer::newRequest, [&](HttpReqRep *rr) { + if (rr->requestPath() == "/hedgehog.html") + userAgents.push_back(rr->requestHeader(QByteArrayLiteral("user-agent"))); + }); + + QWebEngineProfile profile(QStringLiteral("changeHttpUserAgent")); + std::unique_ptr<QWebEnginePage> page; + page.reset(new QWebEnginePage(&profile)); + QVERIFY(loadSync(page.get(), server.url("/hedgehog.html"))); + page.reset(); + profile.setHttpUserAgent("webturbine/42"); + page.reset(new QWebEnginePage(&profile)); + QVERIFY(loadSync(page.get(), server.url("/hedgehog.html"))); + + QCOMPARE(userAgents.size(), 2); + QCOMPARE(userAgents[1], "webturbine/42"); + QVERIFY(userAgents[0] != userAgents[1]); + + QVERIFY(server.stop()); +} + +void tst_QWebEngineProfile::changeHttpAcceptLanguage() +{ + TestServer server; + QVERIFY(server.start()); + + QList<QByteArray> languages; + connect(&server, &HttpServer::newRequest, [&](HttpReqRep *rr) { + if (rr->requestPath() == "/hedgehog.html") + languages.push_back(rr->requestHeader(QByteArrayLiteral("accept-language"))); + }); + + QWebEngineProfile profile(QStringLiteral("changeHttpAcceptLanguage")); + std::unique_ptr<QWebEnginePage> page; + page.reset(new QWebEnginePage(&profile)); + QVERIFY(loadSync(page.get(), server.url("/hedgehog.html"))); + page.reset(); + profile.setHttpAcceptLanguage("fi"); + page.reset(new QWebEnginePage(&profile)); + QVERIFY(loadSync(page.get(), server.url("/hedgehog.html"))); + + QCOMPARE(languages.size(), 2); + QCOMPARE(languages[1], "fi"); + QVERIFY(languages[0] != languages[1]); + + QVERIFY(server.stop()); +} + +void tst_QWebEngineProfile::changePersistentCookiesPolicy() +{ + TestServer server; + QVERIFY(server.start()); + + AutoDir dataDir("./tst_QWebEngineProfile_dataDir"); + + QWebEngineProfile profile(QStringLiteral("changePersistentCookiesPolicy")); + QWebEnginePage page(&profile); + + profile.setPersistentStoragePath(dataDir.path()); + profile.setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); + + QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); + QVERIFY(!dataDir.exists("Cookies")); + + profile.setPersistentCookiesPolicy(QWebEngineProfile::ForcePersistentCookies); + + QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); + QVERIFY(dataDir.exists("Cookies")); + + (void)server.stop(); } class InitiatorSpy : public QWebEngineUrlSchemeHandler @@ -676,26 +970,32 @@ void tst_QWebEngineProfile::initiator() InitiatorSpy handler; QWebEngineProfile profile; profile.installUrlSchemeHandler("foo", &handler); - QWebEnginePage page(&profile); + QWebEnginePage page(&profile, nullptr); QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + page.load(QUrl("about:blank")); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + loadFinishedSpy.clear(); // about:blank has a unique origin, so initiator should be QUrl("null") evaluateJavaScriptSync(&page, "window.location = 'foo:bar'"); - QVERIFY(loadFinishedSpy.wait()); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + loadFinishedSpy.clear(); QCOMPARE(handler.initiator, QUrl("null")); page.setHtml("", QUrl("http://test:123/foo%20bar")); - QVERIFY(loadFinishedSpy.wait()); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + loadFinishedSpy.clear(); // baseUrl determines the origin, so QUrl("http://test:123") evaluateJavaScriptSync(&page, "window.location = 'foo:bar'"); - QVERIFY(loadFinishedSpy.wait()); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + loadFinishedSpy.clear(); QCOMPARE(handler.initiator, QUrl("http://test:123")); // Directly calling load/setUrl should have initiator QUrl(), meaning // browser-initiated, trusted. page.load(QUrl("foo:bar")); - QVERIFY(loadFinishedSpy.wait()); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 10000); QCOMPARE(handler.initiator, QUrl()); } @@ -711,12 +1011,88 @@ void tst_QWebEngineProfile::badDeleteOrder() QSignalSpy spyLoadFinished(page, SIGNAL(loadFinished(bool))); page->setHtml(QStringLiteral("<html><body><h1>Badly handled page!</h1></body></html>")); - QTRY_COMPARE(spyLoadFinished.count(), 1); + QTRY_COMPARE(spyLoadFinished.size(), 1); delete profile; delete view; } +void tst_QWebEngineProfile::permissionPersistence_data() +{ + QTest::addColumn<QWebEngineProfile::PersistentPermissionsPolicy>("policy"); + QTest::addColumn<bool>("granted"); + + QTest::newRow("noPersistenceNotificationsNoGrant") << QWebEngineProfile::NoPersistentPermissions << false; + QTest::newRow("noPersistenceNotificationsGrant") << QWebEngineProfile::NoPersistentPermissions << true; + QTest::newRow("memoryPersistenceNotificationsNoGrant") << QWebEngineProfile::PersistentPermissionsInMemory << false; + QTest::newRow("diskPersistenceNotificationsGrant") << QWebEngineProfile::PersistentPermissionsOnDisk << true; +} + +void tst_QWebEngineProfile::permissionPersistence() +{ + QFETCH(QWebEngineProfile::PersistentPermissionsPolicy, policy); + QFETCH(bool, granted); + + TestServer server; + QVERIFY(server.start()); + + std::unique_ptr<QWebEngineProfile> profile(new QWebEngineProfile("tst_persistence")); + profile->setPersistentPermissionsPolicy(policy); + + std::unique_ptr<QWebEnginePage> page(new QWebEnginePage(profile.get())); + std::unique_ptr<QSignalSpy> loadSpy(new QSignalSpy(page.get(), &QWebEnginePage::loadFinished)); + QDir storageDir = QDir(profile->persistentStoragePath()); + + // Delete permissions file if it somehow survived on disk + storageDir.remove("permissions.json"); + + page->load(server.url("/hedgehog.html")); + QTRY_COMPARE(loadSpy->size(), 1); + + QVariant variant = granted ? "granted" : "denied"; + QVariant defaultVariant = "default"; + page->setFeaturePermission(server.url("/hedgehog.html"), QWebEnginePage::Notifications, + granted ? QWebEnginePage::PermissionGrantedByUser : QWebEnginePage::PermissionDeniedByUser); + QCOMPARE(evaluateJavaScriptSync(page.get(), "Notification.permission"), variant); + + page.reset(); + profile.reset(); + loadSpy.reset(); + + bool expectSame = false; + if (policy == QWebEngineProfile::PersistentPermissionsOnDisk) { + expectSame = true; + + // File is written asynchronously, wait for it to be created + QTRY_COMPARE(storageDir.exists("permissions.json"), true); + } + + profile.reset(new QWebEngineProfile("tst_persistence")); + profile->setPersistentPermissionsPolicy(policy); + + page.reset(new QWebEnginePage(profile.get())); + loadSpy.reset(new QSignalSpy(page.get(), &QWebEnginePage::loadFinished)); + page->load(server.url("/hedgehog.html")); + QTRY_COMPARE(loadSpy->size(), 1); + QTRY_COMPARE(evaluateJavaScriptSync(page.get(), "Notification.permission"), + expectSame ? variant : defaultVariant); + + page->setFeaturePermission(server.url("/hedgehog.html"), QWebEnginePage::Notifications, QWebEnginePage::PermissionUnknown); + QCOMPARE(evaluateJavaScriptSync(page.get(), "Notification.permission"), defaultVariant); + + page.reset(); + profile.reset(); + loadSpy.reset(); + + if (policy == QWebEngineProfile::PersistentPermissionsOnDisk) { + // Wait for file to be written to before deleting + QTest::qWait(1000); + storageDir.remove("permissions.json"); + } + + QVERIFY(server.stop()); +} + void tst_QWebEngineProfile::qtbug_71895() { QWebEngineView view; @@ -727,7 +1103,7 @@ void tst_QWebEngineProfile::qtbug_71895() view.page()->profile()->setHttpCacheType(QWebEngineProfile::NoCache); view.page()->profile()->cookieStore()->deleteAllCookies(); view.page()->profile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); - bool gotSignal = loadSpy.count() || loadSpy.wait(20000); + bool gotSignal = loadSpy.size() || loadSpy.wait(20000); if (!gotSignal) QSKIP("Couldn't load page from network, skipping test."); } diff --git a/tests/auto/widgets/qwebenginescript/CMakeLists.txt b/tests/auto/widgets/qwebenginescript/CMakeLists.txt new file mode 100644 index 000000000..d0d499b84 --- /dev/null +++ b/tests/auto/widgets/qwebenginescript/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../util/util.cmake) + +qt_internal_add_test(tst_qwebenginescript + SOURCES + tst_qwebenginescript.cpp + LIBRARIES + Qt::WebEngineWidgets + Test::Util +) + +set(tst_qwebenginescript_resource_files + "resources/test_iframe_inner.html" + "resources/test_iframe_main.html" + "resources/test_iframe_outer.html" + "resources/test_window_open.html" + "resources/title_a.html" + "resources/title_b.html" + "resources/webChannelWithBadString.html" +) + +qt_internal_add_resource(tst_qwebenginescript "tst_qwebenginescript" + PREFIX + "/" + FILES + ${tst_qwebenginescript_resource_files} +) diff --git a/tests/auto/widgets/qwebenginescript/qwebenginescript.pro b/tests/auto/widgets/qwebenginescript/qwebenginescript.pro deleted file mode 100644 index e99c7f493..000000000 --- a/tests/auto/widgets/qwebenginescript/qwebenginescript.pro +++ /dev/null @@ -1 +0,0 @@ -include(../tests.pri) diff --git a/tests/auto/widgets/qwebenginescript/resources/test_window_open.html b/tests/auto/widgets/qwebenginescript/resources/test_window_open.html index 3f72d176d..3ceafc49d 100644 --- a/tests/auto/widgets/qwebenginescript/resources/test_window_open.html +++ b/tests/auto/widgets/qwebenginescript/resources/test_window_open.html @@ -3,7 +3,7 @@ <head> <title>window.open</title> <script> - window.open("qrc:/resource/test_iframe_main.html", "iframe_main"); + window.open("qrc:/resources/test_iframe_main.html", "iframe_main"); </script> </head> <body></body> diff --git a/tests/auto/widgets/qwebenginescript/resources/title_a.html b/tests/auto/widgets/qwebenginescript/resources/title_a.html new file mode 100644 index 000000000..d1ca96eaa --- /dev/null +++ b/tests/auto/widgets/qwebenginescript/resources/title_a.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <title>A</title> + </head> + <body> + <p>Page A</p> + </body> +</html> diff --git a/tests/auto/widgets/qwebenginescript/resources/title_b.html b/tests/auto/widgets/qwebenginescript/resources/title_b.html new file mode 100644 index 000000000..fd1fda99e --- /dev/null +++ b/tests/auto/widgets/qwebenginescript/resources/title_b.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <title>B</title> + </head> + <body> + <p>Page B</p> + </body> +</html> diff --git a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp index 90361f2c9..9ba13589f 100644 --- a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp +++ b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp @@ -25,7 +25,7 @@ #include <qwebenginescriptcollection.h> #include <qwebenginesettings.h> #include <qwebengineview.h> -#include "../util.h" +#include <util.h> #if QT_CONFIG(webengine_webchannel) #include <QWebChannel> #endif @@ -40,6 +40,8 @@ static bool verifyOrder(QStringList orderList) "Deferred" }; + if (orderList.size() != 5) + return false; if (orderList.at(4) == "load (timeout)") { if (orderList.at(3) != "Deferred") return false; @@ -68,9 +70,13 @@ private Q_SLOTS: void webChannelWithExistingQtObject(); void navigation(); void webChannelWithBadString(); + void webChannelWithJavaScriptDisabled(); #endif void noTransportWithoutWebChannel(); void scriptsInNestedIframes(); + void matchQrcUrl(); + void injectionOrder(); + void reloadWithSubframes(); }; void tst_QWebEngineScript::domEditing() @@ -179,23 +185,23 @@ void tst_QWebEngineScript::loadEvents() // Single frame / setHtml page.setHtml(QStringLiteral("<!DOCTYPE html><html><head><title>mr</title></head><body></body></html>")); - QTRY_COMPARE(page.spy.count(), 1); - QCOMPARE(page.spy.takeFirst().value(0).toBool(), true); + QTRY_COMPARE_WITH_TIMEOUT(page.spy.size(), 1, 20000); + QVERIFY(page.spy.takeFirst().value(0).toBool()); QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::MainWorld).toStringList())); QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::ApplicationWorld).toStringList())); // After discard page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); page.setLifecycleState(QWebEnginePage::LifecycleState::Active); - QTRY_COMPARE(page.spy.count(), 1); - QCOMPARE(page.spy.takeFirst().value(0).toBool(), true); + QTRY_COMPARE_WITH_TIMEOUT(page.spy.size(), 1, 20000); + QVERIFY(page.spy.takeFirst().value(0).toBool()); QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::MainWorld).toStringList())); QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::ApplicationWorld).toStringList())); // Multiple frames page.load(QUrl("qrc:/resources/test_iframe_main.html")); - QTRY_COMPARE(page.spy.count(), 1); - QCOMPARE(page.spy.takeFirst().value(0).toBool(), true); + QTRY_COMPARE_WITH_TIMEOUT(page.spy.size(), 1, 20000); + QVERIFY(page.spy.takeFirst().value(0).toBool()); QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::MainWorld).toStringList())); QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::ApplicationWorld).toStringList())); QVERIFY(verifyOrder(page.eval("window[0].log", QWebEngineScript::MainWorld).toStringList())); @@ -205,17 +211,17 @@ void tst_QWebEngineScript::loadEvents() // Cross-process navigation page.load(QUrl("chrome://gpu")); - QTRY_COMPARE_WITH_TIMEOUT(page.spy.count(), 1, 20000); - QCOMPARE(page.spy.takeFirst().value(0).toBool(), true); + QTRY_COMPARE_WITH_TIMEOUT(page.spy.size(), 1, 20000); + QVERIFY(page.spy.takeFirst().value(0).toBool()); QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::MainWorld).toStringList())); QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::ApplicationWorld).toStringList())); // Using window.open from JS QVERIFY(profile.pages.size() == 1); page.load(QUrl("qrc:/resources/test_window_open.html")); - QTRY_VERIFY(profile.pages.size() == 2); - QTRY_COMPARE(profile.pages.front().spy.count(), 1); - QTRY_COMPARE(profile.pages.back().spy.count(), 1); + QTRY_COMPARE(profile.pages.size(), 2u); + QTRY_COMPARE(profile.pages.front().spy.size(), 1); + QTRY_COMPARE(profile.pages.back().spy.size(), 1); QVERIFY(verifyOrder(profile.pages.front().eval("window.log", QWebEngineScript::MainWorld).toStringList())); QVERIFY(verifyOrder(profile.pages.front().eval("window.log", QWebEngineScript::ApplicationWorld).toStringList())); QVERIFY(verifyOrder(profile.pages.back().eval("window.log", QWebEngineScript::MainWorld).toStringList())); @@ -266,7 +272,7 @@ void tst_QWebEngineScript::scriptDisabled() page.scripts().insert(script); page.load(QUrl("about:blank")); QSignalSpy spy(&page, &QWebEnginePage::loadFinished); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().value(0).toBool(), true); // MainWorld scripts are disabled by the setting... QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "foo", QWebEngineScript::MainWorld), QVariant()); @@ -275,7 +281,7 @@ void tst_QWebEngineScript::scriptDisabled() page.scripts().clear(); page.scripts().insert(script); page.load(QUrl("about:blank")); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().value(0).toBool(), true); // ...but ApplicationWorld scripts should still work QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "foo", QWebEngineScript::MainWorld), QVariant()); @@ -293,7 +299,7 @@ void tst_QWebEngineScript::viewSource() page.scripts().insert(script); page.load(QUrl("view-source:about:blank")); QSignalSpy spy(&page, &QWebEnginePage::loadFinished); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().value(0).toBool(), true); QCOMPARE(evaluateJavaScriptSync(&page, "foo"), QVariant(42)); } @@ -319,8 +325,8 @@ void tst_QWebEngineScript::scriptModifications() QVERIFY(spyFinished.wait()); QCOMPARE(evaluateJavaScriptSync(&page, "document.body.innerText"), QVariant::fromValue(QStringLiteral("SUCCESS"))); QVERIFY(page.scripts().count() == 1); - QWebEngineScript s = page.scripts().findScript(QStringLiteral("String1")); - QVERIFY(page.scripts().remove(s)); + QList<QWebEngineScript> s = page.scripts().find(QStringLiteral("String1")); + QVERIFY(page.scripts().remove(s.first())); QVERIFY(page.scripts().count() == 0); } @@ -412,7 +418,7 @@ void tst_QWebEngineScript::webChannel() QCOMPARE(testObject.text(), QStringLiteral("test")); if (worldId != QWebEngineScript::MainWorld) - QCOMPARE(evaluateJavaScriptSync(&page, "qt.webChannelTransport"), QVariant(QVariant::Invalid)); + QCOMPARE(evaluateJavaScriptSync(&page, "qt.webChannelTransport"), QVariant()); } #endif void tst_QWebEngineScript::noTransportWithoutWebChannel() @@ -420,11 +426,11 @@ void tst_QWebEngineScript::noTransportWithoutWebChannel() QWebEnginePage page; page.setHtml(QStringLiteral("<html><body></body></html>")); - QCOMPARE(evaluateJavaScriptSync(&page, "qt.webChannelTransport"), QVariant(QVariant::Invalid)); + QCOMPARE(evaluateJavaScriptSync(&page, "qt.webChannelTransport"), QVariant()); page.triggerAction(QWebEnginePage::Reload); QSignalSpy spyFinished(&page, &QWebEnginePage::loadFinished); QVERIFY(spyFinished.wait()); - QCOMPARE(evaluateJavaScriptSync(&page, "qt.webChannelTransport"), QVariant(QVariant::Invalid)); + QCOMPARE(evaluateJavaScriptSync(&page, "qt.webChannelTransport"), QVariant()); } void tst_QWebEngineScript::scriptsInNestedIframes() @@ -452,7 +458,7 @@ void tst_QWebEngineScript::scriptsInNestedIframes() QSignalSpy spyFinished(&page, &QWebEnginePage::loadFinished); page.load(QUrl("qrc:/resources/test_iframe_main.html")); view.show(); - QVERIFY(spyFinished.wait()); + QTRY_VERIFY_WITH_TIMEOUT(spyFinished.size() > 0, 20000); // Check that main frame has modified content. QCOMPARE( @@ -485,9 +491,9 @@ void tst_QWebEngineScript::webChannelResettingAndUnsetting() // There should be no webChannelTransport yet. QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::MainWorld), - QVariant(QVariant::Invalid)); + QVariant()); QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::ApplicationWorld), - QVariant(QVariant::Invalid)); + QVariant()); QWebChannel channel; page.setWebChannel(&channel, QWebEngineScript::MainWorld); @@ -496,13 +502,13 @@ void tst_QWebEngineScript::webChannelResettingAndUnsetting() QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::MainWorld), QVariant(QVariantMap())); QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::ApplicationWorld), - QVariant(QVariant::Invalid)); + QVariant()); page.setWebChannel(&channel, QWebEngineScript::ApplicationWorld); // Now it should have moved to ApplicationWorld. QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::MainWorld), - QVariant(QVariant::Invalid)); + QVariant()); QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::ApplicationWorld), QVariant(QVariantMap())); @@ -510,9 +516,9 @@ void tst_QWebEngineScript::webChannelResettingAndUnsetting() // And now it should be gone again. QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::MainWorld), - QVariant(QVariant::Invalid)); + QVariant()); QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::ApplicationWorld), - QVariant(QVariant::Invalid)); + QVariant()); } void tst_QWebEngineScript::webChannelWithExistingQtObject() @@ -520,7 +526,7 @@ void tst_QWebEngineScript::webChannelWithExistingQtObject() QWebEnginePage page; evaluateJavaScriptSync(&page, "qt = 42"); - QCOMPARE(evaluateJavaScriptSync(&page, "qt.webChannelTransport"), QVariant(QVariant::Invalid)); + QCOMPARE(evaluateJavaScriptSync(&page, "qt.webChannelTransport"), QVariant()); QWebChannel channel; page.setWebChannel(&channel); @@ -552,22 +558,22 @@ void tst_QWebEngineScript::navigation() QString url1 = QStringLiteral("about:blank"); page.setUrl(url1); - QTRY_COMPARE(spyTextChanged.count(), 1); + QTRY_COMPARE(spyTextChanged.size(), 1); QCOMPARE(testObject.text(), url1); QString url2 = QStringLiteral("chrome://gpu/"); page.setUrl(url2); - QTRY_COMPARE(spyTextChanged.count(), 2); + QTRY_COMPARE(spyTextChanged.size(), 2); QCOMPARE(testObject.text(), url2); QString url3 = QStringLiteral("qrc:/resources/test_iframe_main.html"); page.setUrl(url3); - QTRY_COMPARE(spyTextChanged.count(), 3); + QTRY_COMPARE(spyTextChanged.size(), 3); QCOMPARE(testObject.text(), url3); page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); page.setUrl(url1); - QTRY_COMPARE(spyTextChanged.count(), 4); + QTRY_COMPARE(spyTextChanged.size(), 4); QCOMPARE(testObject.text(), url1); } @@ -588,7 +594,139 @@ void tst_QWebEngineScript::webChannelWithBadString() QChar data(0xd800); QCOMPARE(host.text(), data); } + +void tst_QWebEngineScript::webChannelWithJavaScriptDisabled() +{ + QWebEnginePage page; + QSignalSpy spyFinished(&page, &QWebEnginePage::loadFinished); + // JavaScript disabled in main world + page.settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false); + + TestObject testObject; + QScopedPointer<QWebChannel> channel(new QWebChannel(this)); + channel->registerObject(QStringLiteral("object"), &testObject); + page.setWebChannel(channel.data(), QWebEngineScript::ApplicationWorld); + + QWebEngineScript script = webChannelScript(); + script.setWorldId(QWebEngineScript::ApplicationWorld); + page.scripts().insert(script); + + page.setHtml(QStringLiteral("<html><body></body></html>")); + QVERIFY(spyFinished.wait()); + + QSignalSpy spyTextChanged(&testObject, &TestObject::textChanged); + page.runJavaScript(QLatin1String( + "new QWebChannel(qt.webChannelTransport," + " function(channel) {" + " channel.objects.object.text = 'test';" + " }" + ");"), QWebEngineScript::ApplicationWorld); + QVERIFY(spyTextChanged.wait()); + QCOMPARE(testObject.text(), QStringLiteral("test")); +} #endif + +void tst_QWebEngineScript::matchQrcUrl() +{ + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QWebEngineScript s; + s.setInjectionPoint(QWebEngineScript::DocumentReady); + s.setWorldId(QWebEngineScript::MainWorld); + s.setSourceCode(QStringLiteral(R"( +// ==UserScript== +// @match qrc:/*title_b.html +// ==/UserScript== + +document.title = 'New title'; + )")); + page.scripts().insert(s); + loadSync(&page, QUrl("qrc:/resources/title_a.html")); + QCOMPARE(page.title(), "A"); + loadSync(&page, QUrl("qrc:/resources/title_b.html")); + QCOMPARE(page.title(), "New title"); +} + +// Add many scripts and check order of execution. +void tst_QWebEngineScript::injectionOrder() +{ + QWebEngineProfile profile; + class Page : public QWebEnginePage + { + public: + Page(QWebEngineProfile *profile) : QWebEnginePage(profile) {} + QVector<QString> log; + + protected: + void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel, const QString &message, int, + const QString &) override + { + log.append(message); + } + } page(&profile); + QWebEngineScript::InjectionPoint points[] = { + QWebEngineScript::DocumentCreation, + QWebEngineScript::DocumentReady, + QWebEngineScript::Deferred, + }; + int nPoints = 3; + int nCollections = 2; + int nScripts = 5; + + QVector<QString> expected; + for (int iPoint = 0; iPoint != nPoints; ++iPoint) { + for (int iCollection = 0; iCollection != nCollections; ++iCollection) { + for (int iScript = 0; iScript != nScripts; ++iScript) { + QWebEngineScript script; + script.setName(QString("%1%2%3").arg(iPoint).arg(iCollection).arg(iScript)); + expected.append(script.name()); + script.setInjectionPoint(points[iPoint]); + script.setWorldId(QWebEngineScript::MainWorld); + script.setSourceCode(QStringLiteral("console.error('%1');").arg(script.name())); + if (iCollection == 0) + profile.scripts()->insert(script); + else + page.scripts().insert(script); + } + } + } + + page.load(QUrl("qrc:/resources/test_iframe_inner.html")); + QTRY_COMPARE(page.log, expected); +} + +void tst_QWebEngineScript::reloadWithSubframes() +{ + class Page : public QWebEnginePage + { + public: + Page() : QWebEnginePage() {} + QVector<QString> log; + + protected: + void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel, const QString &message, int, + const QString &) override + { + log.append(message); + } + } page; + + QWebEngineScript s; + s.setInjectionPoint(QWebEngineScript::DocumentCreation); + s.setSourceCode(QStringLiteral("console.log('Hello');")); + page.scripts().insert(s); + + page.setHtml(QStringLiteral("<body>" + " <h1>Test scripts working on reload </h1>" + " <iframe src='about://blank'>" + " </iframe>" + "</body>")); + QTRY_COMPARE(page.log.size(), 1); + + page.triggerAction(QWebEnginePage::Reload); + QTRY_COMPARE(page.log.size(), 2); +} + 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 deleted file mode 100644 index ada06119a..000000000 --- a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.qrc +++ /dev/null @@ -1,9 +0,0 @@ -<!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> - <file>resources/test_window_open.html</file> - <file>resources/webChannelWithBadString.html</file> -</qresource> -</RCC> diff --git a/tests/auto/widgets/qwebenginesettings/qwebenginesettings.pro b/tests/auto/widgets/qwebenginesettings/qwebenginesettings.pro deleted file mode 100644 index 70786e70f..000000000 --- a/tests/auto/widgets/qwebenginesettings/qwebenginesettings.pro +++ /dev/null @@ -1,2 +0,0 @@ -include(../tests.pri) -QT *= core-private gui-private diff --git a/tests/auto/widgets/qwebenginesettings/tst_qwebenginesettings.cpp b/tests/auto/widgets/qwebenginesettings/tst_qwebenginesettings.cpp deleted file mode 100644 index 0704cf383..000000000 --- a/tests/auto/widgets/qwebenginesettings/tst_qwebenginesettings.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/* - Copyright (C) 2015 The Qt Company Ltd. - - 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 "../util.h" - -#include <QtTest/QtTest> - -#include <qwebenginepage.h> -#include <qwebengineprofile.h> -#include <qwebenginesettings.h> - -#include <QtGui/qclipboard.h> -#include <QtGui/qguiapplication.h> - -class tst_QWebEngineSettings: public QObject { - Q_OBJECT - -private Q_SLOTS: - void resetAttributes(); - void defaultFontFamily_data(); - void defaultFontFamily(); - void javascriptClipboard_data(); - void javascriptClipboard(); - void setInAcceptNavigationRequest(); -}; - -void tst_QWebEngineSettings::resetAttributes() -{ - QWebEngineProfile profile; - QWebEngineSettings *settings = profile.settings(); - - // Attribute - bool defaultValue = settings->testAttribute(QWebEngineSettings::FullScreenSupportEnabled); - settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, !defaultValue); - QCOMPARE(!defaultValue, settings->testAttribute(QWebEngineSettings::FullScreenSupportEnabled)); - settings->resetAttribute(QWebEngineSettings::FullScreenSupportEnabled); - QCOMPARE(defaultValue, settings->testAttribute(QWebEngineSettings::FullScreenSupportEnabled)); - - // Font family - QString defaultFamily = settings->fontFamily(QWebEngineSettings::StandardFont); - QString newFontFamily("PugDog"); - settings->setFontFamily(QWebEngineSettings::StandardFont, newFontFamily); - QCOMPARE(newFontFamily, settings->fontFamily(QWebEngineSettings::StandardFont)); - settings->resetFontFamily(QWebEngineSettings::StandardFont); - QCOMPARE(defaultFamily, settings->fontFamily(QWebEngineSettings::StandardFont)); - - // Font size - int defaultSize = settings->fontSize(QWebEngineSettings::MinimumFontSize); - int newSize = defaultSize + 10; - settings->setFontSize(QWebEngineSettings::MinimumFontSize, newSize); - QCOMPARE(newSize, settings->fontSize(QWebEngineSettings::MinimumFontSize)); - settings->resetFontSize(QWebEngineSettings::MinimumFontSize); - QCOMPARE(defaultSize, settings->fontSize(QWebEngineSettings::MinimumFontSize)); -} - -void tst_QWebEngineSettings::defaultFontFamily_data() -{ - QTest::addColumn<int>("fontFamily"); - - QTest::newRow("StandardFont") << static_cast<int>(QWebEngineSettings::StandardFont); - QTest::newRow("FixedFont") << static_cast<int>(QWebEngineSettings::FixedFont); - QTest::newRow("SerifFont") << static_cast<int>(QWebEngineSettings::SerifFont); - QTest::newRow("SansSerifFont") << static_cast<int>(QWebEngineSettings::SansSerifFont); - QTest::newRow("CursiveFont") << static_cast<int>(QWebEngineSettings::CursiveFont); - QTest::newRow("FantasyFont") << static_cast<int>(QWebEngineSettings::FantasyFont); -} - -void tst_QWebEngineSettings::defaultFontFamily() -{ - QWebEngineProfile profile; - QWebEngineSettings *settings = profile.settings(); - - QFETCH(int, fontFamily); - QVERIFY(!settings->fontFamily(static_cast<QWebEngineSettings::FontFamily>(fontFamily)).isEmpty()); -} - -void tst_QWebEngineSettings::javascriptClipboard_data() -{ - QTest::addColumn<bool>("javascriptCanAccessClipboard"); - QTest::addColumn<bool>("javascriptCanPaste"); - QTest::addColumn<bool>("copyResult"); - QTest::addColumn<bool>("pasteResult"); - - QTest::newRow("default") << false << false << false << false; - QTest::newRow("canCopy") << true << false << true << false; - // paste command requires both permissions - QTest::newRow("canPaste") << false << true << false << false; - QTest::newRow("canCopyAndPaste") << true << true << true << true; -} - -void tst_QWebEngineSettings::javascriptClipboard() -{ - QFETCH(bool, javascriptCanAccessClipboard); - QFETCH(bool, javascriptCanPaste); - QFETCH(bool, copyResult); - QFETCH(bool, pasteResult); - - QWebEnginePage page; - - // check defaults - QCOMPARE(page.settings()->testAttribute(QWebEngineSettings::JavascriptCanAccessClipboard), - false); - QCOMPARE(page.settings()->testAttribute(QWebEngineSettings::JavascriptCanPaste), false); - - // check accessors - page.settings()->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, - javascriptCanAccessClipboard); - page.settings()->setAttribute(QWebEngineSettings::JavascriptCanPaste, - javascriptCanPaste); - QCOMPARE(page.settings()->testAttribute(QWebEngineSettings::JavascriptCanAccessClipboard), - javascriptCanAccessClipboard); - QCOMPARE(page.settings()->testAttribute(QWebEngineSettings::JavascriptCanPaste), - javascriptCanPaste); - - QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); - page.setHtml("<html><body>" - "<input type='text' value='OriginalText' id='myInput'/>" - "</body></html>"); - QVERIFY(loadFinishedSpy.wait()); - - // make sure that 'OriginalText' is selected - evaluateJavaScriptSync(&page, "document.getElementById('myInput').select()"); - QCOMPARE(evaluateJavaScriptSync(&page, "window.getSelection().toString()").toString(), - QStringLiteral("OriginalText")); - - // Check that the actual settings work by the - // - return value of queryCommandEnabled and - // - return value of execCommand - // - comparing the clipboard / input field - QGuiApplication::clipboard()->clear(); - QCOMPARE(evaluateJavaScriptSync(&page, "document.queryCommandEnabled('copy')").toBool(), - copyResult); - QCOMPARE(evaluateJavaScriptSync(&page, "document.execCommand('copy')").toBool(), copyResult); - QTRY_COMPARE(QApplication::clipboard()->text(), - (copyResult ? QString("OriginalText") : QString())); - - - QGuiApplication::clipboard()->setText("AnotherText"); - QCOMPARE(evaluateJavaScriptSync(&page, "document.queryCommandEnabled('paste')").toBool(), - pasteResult); - QCOMPARE(evaluateJavaScriptSync(&page, "document.execCommand('paste')").toBool(), pasteResult); - QCOMPARE(evaluateJavaScriptSync(&page, "document.getElementById('myInput').value").toString(), - (pasteResult ? QString("AnotherText") : QString("OriginalText"))); -} - -class NavigationRequestOverride : public QWebEnginePage -{ -protected: - virtual bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) - { - Q_UNUSED(type); - - if (isMainFrame && url.scheme().startsWith("data")) - settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true); - - return true; - } -}; - -void tst_QWebEngineSettings::setInAcceptNavigationRequest() -{ - NavigationRequestOverride page; - QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); - QWebEngineSettings::globalSettings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false); - QVERIFY(!page.settings()->testAttribute(QWebEngineSettings::JavascriptEnabled)); - - page.load(QUrl("about:blank")); - QVERIFY(loadFinishedSpy.wait()); - QVERIFY(!page.settings()->testAttribute(QWebEngineSettings::JavascriptEnabled)); - - page.setHtml("<html><body>" - "<script>document.write('PASS')</script>" - "<noscript>FAIL</noscript>" - "</body></html>"); - QVERIFY(loadFinishedSpy.wait()); - QVERIFY(page.settings()->testAttribute(QWebEngineSettings::JavascriptEnabled)); - QCOMPARE(toPlainTextSync(&page), QStringLiteral("PASS")); -} - -QTEST_MAIN(tst_QWebEngineSettings) - -#include "tst_qwebenginesettings.moc" diff --git a/tests/auto/widgets/qwebengineview/BLACKLIST b/tests/auto/widgets/qwebengineview/BLACKLIST index 9087067f5..26f2da4bb 100644 --- a/tests/auto/widgets/qwebengineview/BLACKLIST +++ b/tests/auto/widgets/qwebengineview/BLACKLIST @@ -1,5 +1,12 @@ -[microFocusCoordinates] -osx - -[textSelectionOutOfInputField] +[mixLangLocale:eu_ES] * + +[navigateOnDrop:file] +windows + +[navigateOnDrop:file_no_navigate] +windows + +[horizontalScrollbarTest] +macos +rhel # flaky diff --git a/tests/auto/widgets/qwebengineview/CMakeLists.txt b/tests/auto/widgets/qwebengineview/CMakeLists.txt new file mode 100644 index 000000000..9583184d0 --- /dev/null +++ b/tests/auto/widgets/qwebengineview/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../util/util.cmake) + +qt_internal_add_test(tst_qwebengineview + SOURCES + tst_qwebengineview.cpp + LIBRARIES + Qt::WebEngineWidgets + Qt::WebEngineCorePrivate + Qt::GuiPrivate + Qt::QuickWidgets + Qt::TestPrivate + Test::Util +) + +set(tst_qwebengineview_resource_files + "resources/dummy.html" + "resources/frame_a.html" + "resources/image2.png" + "resources/index.html" + "resources/input_types.html" + "resources/keyboardEvents.html" + "resources/scrolltest_page.html" +) + +qt_internal_add_resource(tst_qwebengineview "tst_qwebengineview" + PREFIX + "/" + FILES + ${tst_qwebengineview_resource_files} +) diff --git a/tests/auto/widgets/qwebengineview/qwebengineview.pro b/tests/auto/widgets/qwebengineview/qwebengineview.pro deleted file mode 100644 index d91c0074b..000000000 --- a/tests/auto/widgets/qwebengineview/qwebengineview.pro +++ /dev/null @@ -1,2 +0,0 @@ -include(../tests.pri) -QT *= gui-private diff --git a/tests/auto/widgets/qwebengineview/resources/dummy.html b/tests/auto/widgets/qwebengineview/resources/dummy.html new file mode 100644 index 000000000..f5ba1963a --- /dev/null +++ b/tests/auto/widgets/qwebengineview/resources/dummy.html @@ -0,0 +1,6 @@ +<html><head> +<title>Dummy simple page without real content</title> +<link rel='icon' href='qrc:///resources/image2.png'/> +</head><body> +<a>This is test content</a> +</body></html> diff --git a/tests/auto/widgets/resources/test.swf b/tests/auto/widgets/qwebengineview/resources/test.swf Binary files differindex 895298271..895298271 100644 --- a/tests/auto/widgets/resources/test.swf +++ b/tests/auto/widgets/qwebengineview/resources/test.swf diff --git a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp index fa179f2f8..f4ed06e14 100644 --- a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp +++ b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp @@ -19,35 +19,42 @@ Boston, MA 02110-1301, USA. */ -#include <qtest.h> -#include "../util.h" +#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses +#include <QtWebEngineCore/private/qtwebenginecore-config_p.h> +#include <qtest.h> +#include <util.h> #include <private/qinputmethod_p.h> #include <qpainter.h> #include <qpagelayout.h> -#include <qpa/qplatforminputcontext.h> #include <qwebengineview.h> #include <qwebenginepage.h> #include <qwebenginesettings.h> -#include <qnetworkrequest.h> +#include <qaction.h> #include <qdiriterator.h> +#include <qnetworkrequest.h> #include <qstackedlayout.h> #include <qtemporarydir.h> #include <QClipboard> #include <QCompleter> +#include <QDropEvent> #include <QLabel> #include <QLineEdit> +#include <QListView> #include <QHBoxLayout> #include <QMenu> +#include <QMimeData> #include <QQuickItem> #include <QQuickWidget> #include <QtWebEngineCore/qwebenginehttprequest.h> +#include <QScopeGuard> +#include <QStringListModel> #include <QTcpServer> #include <QTcpSocket> #include <QStyle> -#include <QtWidgets/qaction.h> #include <QWebEngineProfile> #include <QtCore/qregularexpression.h> +#include <QtTest/private/qemulationdetector_p.h> #define VERIFY_INPUTMETHOD_HINTS(actual, expect) \ QVERIFY(actual == (expect | Qt::ImhNoPredictiveText | Qt::ImhNoTextHandles | Qt::ImhNoEditMenu)); @@ -60,42 +67,6 @@ do { \ QCOMPARE((__expr), __expected); \ } while (0) -static QPoint elementCenter(QWebEnginePage *page, const QString &id) -{ - const QString jsCode( - "(function(){" - " var elem = document.getElementById('" + id + "');" - " var rect = elem.getBoundingClientRect();" - " return [(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2];" - "})()"); - QVariantList rectList = evaluateJavaScriptSync(page, jsCode).toList(); - - if (rectList.count() != 2) { - qWarning("elementCenter failed."); - return QPoint(); - } - - return QPoint(rectList.at(0).toInt(), rectList.at(1).toInt()); -} - -static QRect elementGeometry(QWebEnginePage *page, const QString &id) -{ - const QString jsCode( - "(function() {" - " var elem = document.getElementById('" + id + "');" - " var rect = elem.getBoundingClientRect();" - " return [rect.left, rect.top, rect.right, rect.bottom];" - "})()"); - QVariantList coords = evaluateJavaScriptSync(page, jsCode).toList(); - - if (coords.count() != 4) { - qWarning("elementGeometry faield."); - return QRect(); - } - - return QRect(coords[0].toInt(), coords[1].toInt(), coords[2].toInt(), coords[3].toInt()); -} - QT_BEGIN_NAMESPACE namespace QTest { int Q_TESTLIB_EXPORT defaultMouseDelay(); @@ -104,7 +75,8 @@ namespace QTest { { QTest::qWait(QTest::defaultMouseDelay()); lastMouseTimestamp += QTest::defaultMouseDelay(); - QMouseEvent me(type, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QMouseEvent me(type, pos, widget->mapToGlobal(pos), Qt::LeftButton, Qt::LeftButton, + Qt::NoModifier); me.setTimestamp(++lastMouseTimestamp); QSpontaneKeyEvent::setSpontaneous(&me); qApp->sendEvent(widget, &me); @@ -140,6 +112,7 @@ private Q_SLOTS: void changePage(); void reusePage_data(); void reusePage(); + void setLoadedPage(); void microFocusCoordinates(); void focusInputTypes(); void unhandledKeyEventPropagation(); @@ -160,6 +133,8 @@ private Q_SLOTS: void doNotBreakLayout(); void changeLocale(); + void mixLangLocale_data(); + void mixLangLocale(); void inputMethodsTextFormat_data(); void inputMethodsTextFormat(); void keyboardEvents(); @@ -183,7 +158,7 @@ private Q_SLOTS: void mouseLeave(); -#ifndef QT_NO_CLIPBOARD +#if QT_CONFIG(clipboard) void globalMouseSelection(); #endif void noContextMenu(); @@ -192,8 +167,12 @@ private Q_SLOTS: void webUIURLs_data(); void webUIURLs(); void visibilityState(); + void visibilityState2(); + void visibilityState3(); + void jsKeyboardEvent_data(); void jsKeyboardEvent(); void deletePage(); + void autoDeleteOnExternalPageDelete(); void closeOpenerTab(); void switchPage(); void setPageDeletesImplicitPage(); @@ -202,6 +181,14 @@ private Q_SLOTS: void setPagePreservesExplicitPage(); void setViewPreservesExplicitPage(); void closeDiscardsPage(); + void loadAfterRendererCrashed(); + void inspectElement(); + void navigateOnDrop_data(); + void navigateOnDrop(); + void emptyUriListOnDrop(); + void datalist(); + void longKeyEventText(); + void pageWithPaintListeners(); }; // This will be called before the first test function is executed. @@ -224,6 +211,96 @@ void tst_QWebEngineView::init() // This will be called after every test function. void tst_QWebEngineView::cleanup() { + QTRY_COMPARE(QApplication::topLevelWidgets().size(), 0); +} + +class PageWithPaintListeners : public QWebEnginePage +{ + Q_OBJECT +public: + PageWithPaintListeners(QObject *parent = nullptr) : QWebEnginePage(parent) + { + addFirstContentfulPaintListener(); + addLargestContentfulPaintListener(); + } + + void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString &message, + int lineNumber, const QString &sourceID) override + { + Q_UNUSED(level) + Q_UNUSED(lineNumber) + Q_UNUSED(sourceID) + if (message.contains("firstContentfulPaint")) + emit firstContentfulPaint(); + if (message.contains("largestContentfulPaint")) + emit largestContentfulPaint(); + } + + // https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver + void addFirstContentfulPaintListener() + { + QObject::connect(this, &QWebEnginePage::loadFinished, [this]() { + runJavaScript(QStringLiteral( + "new PerformanceObserver((entryList) => {" + " if (entryList.getEntriesByType('first-contentful-paint'))" + " console.log('firstContentfulPaint');" + "}).observe({type: 'paint', buffered: true});")); + }); + } + + void addLargestContentfulPaintListener() + { + QObject::connect(this, &QWebEnginePage::loadFinished, [this]() { + runJavaScript(QStringLiteral( + "new PerformanceObserver((entryList) => {" + " console.log('largestContentfulPaint');" + "}).observe({type: 'largest-contentful-paint', buffered: true});")); + }); + } + +signals: + void firstContentfulPaint(); // https://web.dev/articles/fcp + void largestContentfulPaint(); // https://web.dev/articles/lcp +}; + +void tst_QWebEngineView::pageWithPaintListeners() +{ + PageWithPaintListeners page; + + QSignalSpy firstContentfulPaintSpy(&page, &PageWithPaintListeners::firstContentfulPaint); + QSignalSpy largestContentfulPaintSpy(&page, &PageWithPaintListeners::largestContentfulPaint); + + const QString empty = + QStringLiteral("<html><body style='width:100x;height:100px;'></body></html>"); + const QString scrollBars = + QStringLiteral("<html><body style='width:1000px;height:1000px;'></body></html>"); + const QString backgroundColor = + QStringLiteral("<html><body style='background-color:green'></body></html>"); + const QString text = QStringLiteral("<html><body>text</body></html>"); + + QWebEngineView view; + view.setPage(&page); + view.resize(600, 600); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + page.setHtml(empty); + QTest::qWait(500); // empty page should not trigger + QVERIFY(firstContentfulPaintSpy.size() == 0); + QVERIFY(largestContentfulPaintSpy.size() == 0); + + page.setHtml(backgroundColor); + QTRY_VERIFY(firstContentfulPaintSpy.size() == 1); + + page.setHtml(text); + QTRY_VERIFY(firstContentfulPaintSpy.size() == 2); + QTRY_VERIFY(largestContentfulPaintSpy.size() == 1); + +#if !QT_CONFIG(webengine_embedded_build) + // Embedded builds have different scrollbars that are only painted on hover + page.setHtml(scrollBars); + QTRY_VERIFY(firstContentfulPaintSpy.size() == 3); +#endif } void tst_QWebEngineView::renderHints() @@ -237,31 +314,46 @@ void tst_QWebEngineView::renderHints() QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); +#if QT_DEPRECATED_SINCE(5, 14) QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); +#endif + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); webView.setRenderHint(QPainter::Antialiasing, true); QVERIFY(webView.renderHints() & QPainter::Antialiasing); QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); +#if QT_DEPRECATED_SINCE(5, 14) QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); +#endif + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); webView.setRenderHint(QPainter::Antialiasing, false); QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); +#if QT_DEPRECATED_SINCE(5, 14) QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); +#endif + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); webView.setRenderHint(QPainter::SmoothPixmapTransform, true); QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); +#if QT_DEPRECATED_SINCE(5, 14) QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); +#endif + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); webView.setRenderHint(QPainter::SmoothPixmapTransform, false); QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); QVERIFY(!(webView.renderHints() & QPainter::SmoothPixmapTransform)); +#if QT_DEPRECATED_SINCE(5, 14) QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); #endif + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); +#endif } void tst_QWebEngineView::getWebKitVersion() @@ -307,64 +399,71 @@ void tst_QWebEngineView::changePage() QSignalSpy pageFromLoadSpy(pageFrom.get(), &QWebEnginePage::loadFinished); QSignalSpy pageFromIconLoadSpy(pageFrom.get(), &QWebEnginePage::iconChanged); pageFrom->load(urlFrom); - QTRY_COMPARE(pageFromLoadSpy.count(), 1); + QTRY_COMPARE(pageFromLoadSpy.size(), 1); QCOMPARE(pageFromLoadSpy.last().value(0).toBool(), true); if (!fromIsNullPage) { - QTRY_COMPARE(pageFromIconLoadSpy.count(), 1); + QTRY_COMPARE(pageFromIconLoadSpy.size(), 1); QVERIFY(!pageFromIconLoadSpy.last().value(0).isNull()); } view->setPage(pageFrom.get()); + QCOMPARE(view->page(), pageFrom.get()); + QCOMPARE(QWebEngineView::forPage(pageFrom.get()), view.get()); - QTRY_COMPARE(spyUrl.count(), 1); + QTRY_COMPARE(spyUrl.size(), 1); QCOMPARE(spyUrl.last().value(0).toUrl(), pageFrom->url()); - QTRY_COMPARE(spyTitle.count(), 1); + QTRY_COMPARE(spyTitle.size(), 1); QCOMPARE(spyTitle.last().value(0).toString(), pageFrom->title()); - QTRY_COMPARE(spyIconUrl.count(), fromIsNullPage ? 0 : 1); - QTRY_COMPARE(spyIcon.count(), fromIsNullPage ? 0 : 1); + QTRY_COMPARE(spyIconUrl.size(), fromIsNullPage ? 0 : 1); + QTRY_COMPARE(spyIcon.size(), fromIsNullPage ? 0 : 1); if (!fromIsNullPage) { QVERIFY(!pageFrom->iconUrl().isEmpty()); QCOMPARE(spyIconUrl.last().value(0).toUrl(), pageFrom->iconUrl()); - QCOMPARE(spyIcon.last().value(0), QVariant::fromValue(pageFrom->icon())); + QCOMPARE(spyIcon.last().value(0).value<QIcon>().availableSizes(), + pageFrom->icon().availableSizes()); } QScopedPointer<QWebEnginePage> pageTo(new QWebEnginePage); QSignalSpy pageToLoadSpy(pageTo.get(), &QWebEnginePage::loadFinished); QSignalSpy pageToIconLoadSpy(pageTo.get(), &QWebEnginePage::iconChanged); pageTo->load(urlTo); - QTRY_COMPARE(pageToLoadSpy.count(), 1); + QTRY_COMPARE(pageToLoadSpy.size(), 1); QCOMPARE(pageToLoadSpy.last().value(0).toBool(), true); if (!toIsNullPage) { - QTRY_COMPARE(pageToIconLoadSpy.count(), 1); + QTRY_COMPARE(pageToIconLoadSpy.size(), 1); QVERIFY(!pageToIconLoadSpy.last().value(0).isNull()); } view->setPage(pageTo.get()); + QCOMPARE(view->page(), pageTo.get()); + QCOMPARE(QWebEngineView::forPage(pageTo.get()), view.get()); + QCOMPARE(QWebEngineView::forPage(pageFrom.get()), nullptr); - QTRY_COMPARE(spyUrl.count(), 2); + QTRY_COMPARE(spyUrl.size(), 2); QCOMPARE(spyUrl.last().value(0).toUrl(), pageTo->url()); - QTRY_COMPARE(spyTitle.count(), 2); + QTRY_COMPARE(spyTitle.size(), 2); QCOMPARE(spyTitle.last().value(0).toString(), pageTo->title()); bool iconIsSame = fromIsNullPage == toIsNullPage; int iconChangeNotifyCount = fromIsNullPage ? (iconIsSame ? 0 : 1) : (iconIsSame ? 1 : 2); - QTRY_COMPARE(spyIconUrl.count(), iconChangeNotifyCount); - QTRY_COMPARE(spyIcon.count(), iconChangeNotifyCount); + QTRY_COMPARE(spyIconUrl.size(), iconChangeNotifyCount); + QTRY_COMPARE(spyIcon.size(), iconChangeNotifyCount); QCOMPARE(pageFrom->iconUrl() == pageTo->iconUrl(), iconIsSame); if (!iconIsSame) { QCOMPARE(spyIconUrl.last().value(0).toUrl(), pageTo->iconUrl()); - QCOMPARE(spyIcon.last().value(0), QVariant::fromValue(pageTo->icon())); + QCOMPARE(spyIcon.last().value(0).value<QIcon>().availableSizes(), + pageTo->icon().availableSizes()); } // verify no emits on destroy with the same number of signals in spy view.reset(); qApp->processEvents(); - QTRY_COMPARE(spyUrl.count(), 2); - QTRY_COMPARE(spyTitle.count(), 2); - QTRY_COMPARE(spyIconUrl.count(), iconChangeNotifyCount); - QTRY_COMPARE(spyIcon.count(), iconChangeNotifyCount); + QTRY_COMPARE(spyUrl.size(), 2); + QTRY_COMPARE(spyTitle.size(), 2); + QTRY_COMPARE(spyIconUrl.size(), iconChangeNotifyCount); + QTRY_COMPARE(spyIcon.size(), iconChangeNotifyCount); } void tst_QWebEngineView::reusePage_data() @@ -377,27 +476,31 @@ void tst_QWebEngineView::reusePage_data() void tst_QWebEngineView::reusePage() { - if (!QDir(TESTS_SOURCE_DIR).exists()) - W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + if (!QDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'") + .arg(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()) + .toLatin1() + .constData(), + SkipAll); - QDir::setCurrent(TESTS_SOURCE_DIR); + QDir::setCurrent(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()); QFETCH(QString, html); QWebEngineView* view1 = new QWebEngineView; QPointer<QWebEnginePage> page = new QWebEnginePage; view1->setPage(page.data()); page.data()->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); - page->setHtml(html, QUrl::fromLocalFile(TESTS_SOURCE_DIR)); + page->setHtml(html, QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath())); if (html.contains("</embed>")) { // some reasonable time for the PluginStream to feed test.swf to flash and start painting QSignalSpy spyFinished(view1, &QWebEngineView::loadFinished); - QVERIFY(spyFinished.wait(2000)); + QVERIFY(spyFinished.wait(20000)); } view1->show(); 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 + QVERIFY(page != nullptr); // deleting view must not have deleted the page, since it's not a child of view QWebEngineView *view2 = new QWebEngineView; view2->setPage(page.data()); @@ -410,6 +513,23 @@ void tst_QWebEngineView::reusePage() QDir::setCurrent(QApplication::applicationDirPath()); } +void tst_QWebEngineView::setLoadedPage() +{ + // MEMO load page first to make sure that just simple attach to view would draw its content + QWebEnginePage page; + QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished); + page.setHtml(QString("<html><body bgcolor=\"%1\"></body></html>").arg(QColor(Qt::yellow).name())); + QTRY_VERIFY(loadSpy.size() == 1 && loadSpy.first().first().toBool()); + + QWebEngineView view; + view.resize(480, 320); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + view.setPage(&page); + QTRY_COMPARE(view.grab().toImage().pixelColor(QPoint(view.width() / 2, view.height() / 2)), Qt::yellow); +} + // Class used in crashTests class WebViewCrashTest : public QObject { Q_OBJECT @@ -480,22 +600,22 @@ void tst_QWebEngineView::microFocusCoordinates() evaluateJavaScriptSync(webView.page(), "document.getElementById('input1').focus()"); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); - QTRY_VERIFY(webView.focusProxy()->inputMethodQuery(Qt::ImMicroFocus).isValid()); - QVariant initialMicroFocus = webView.focusProxy()->inputMethodQuery(Qt::ImMicroFocus); + QTRY_VERIFY(webView.focusProxy()->inputMethodQuery(Qt::ImCursorRectangle).isValid()); + QVariant initialMicroFocus = webView.focusProxy()->inputMethodQuery(Qt::ImCursorRectangle); evaluateJavaScriptSync(webView.page(), "window.scrollBy(0, 50)"); - QTRY_VERIFY(scrollSpy.count() > 0); + QTRY_VERIFY(scrollSpy.size() > 0); - QTRY_VERIFY(webView.focusProxy()->inputMethodQuery(Qt::ImMicroFocus).isValid()); - QVariant currentMicroFocus = webView.focusProxy()->inputMethodQuery(Qt::ImMicroFocus); + QTRY_VERIFY(webView.focusProxy()->inputMethodQuery(Qt::ImCursorRectangle).isValid()); + QVariant currentMicroFocus = webView.focusProxy()->inputMethodQuery(Qt::ImCursorRectangle); QCOMPARE(initialMicroFocus.toRect().translated(QPoint(0,-50)), currentMicroFocus.toRect()); } void tst_QWebEngineView::focusInputTypes() { - const QPlatformInputContext *context = QGuiApplicationPrivate::platformIntegration()->inputContext(); - bool imeHasHiddenTextCapability = context && context->hasCapability(QPlatformInputContext::HiddenTextCapability); + const QPlatformInputContext *platformInputContext = QGuiApplicationPrivate::platformIntegration()->inputContext(); + bool imeHasHiddenTextCapability = platformInputContext && platformInputContext->hasCapability(QPlatformInputContext::HiddenTextCapability); QWebEngineView webView; webView.resize(640, 480); @@ -514,7 +634,7 @@ void tst_QWebEngineView::focusInputTypes() // 'text' field QPoint textInputCenter = elementCenter(webView.page(), "textInput"); - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, 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)); @@ -522,15 +642,16 @@ void tst_QWebEngineView::focusInputTypes() // 'password' field QPoint passwordInputCenter = elementCenter(webView.page(), "passwordInput"); - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, passwordInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, 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)); - QTRY_COMPARE(inputMethodQuery(Qt::ImEnabled).toBool(), imeHasHiddenTextCapability); + QTRY_VERIFY(inputMethodQuery(Qt::ImEnabled).toBool()); + QTRY_COMPARE(platformInputContext->inputMethodAccepted(), imeHasHiddenTextCapability); // 'tel' field QPoint telInputCenter = elementCenter(webView.page(), "telInput"); - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, telInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, telInputCenter); 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)); @@ -538,7 +659,7 @@ void tst_QWebEngineView::focusInputTypes() // 'number' field QPoint numberInputCenter = elementCenter(webView.page(), "numberInput"); - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, numberInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, numberInputCenter); 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)); @@ -546,7 +667,7 @@ void tst_QWebEngineView::focusInputTypes() // 'email' field QPoint emailInputCenter = elementCenter(webView.page(), "emailInput"); - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, emailInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, emailInputCenter); 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)); @@ -554,36 +675,38 @@ void tst_QWebEngineView::focusInputTypes() // 'url' field QPoint urlInputCenter = elementCenter(webView.page(), "urlInput"); - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, urlInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, urlInputCenter); 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); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, 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)); - QTRY_COMPARE(inputMethodQuery(Qt::ImEnabled).toBool(), imeHasHiddenTextCapability); + QTRY_VERIFY(inputMethodQuery(Qt::ImEnabled).toBool()); + QTRY_COMPARE(platformInputContext->inputMethodAccepted(), imeHasHiddenTextCapability); // 'text' type - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, 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); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, 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)); - QTRY_COMPARE(inputMethodQuery(Qt::ImEnabled).toBool(), imeHasHiddenTextCapability); + QTRY_VERIFY(inputMethodQuery(Qt::ImEnabled).toBool()); + QTRY_COMPARE(platformInputContext->inputMethodAccepted(), imeHasHiddenTextCapability); // 'text area' field QPoint textAreaCenter = elementCenter(webView.page(), "textArea"); - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, textAreaCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, textAreaCenter); 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)); @@ -592,10 +715,11 @@ void tst_QWebEngineView::focusInputTypes() class KeyEventRecordingWidget : public QWidget { public: - QList<QKeyEvent> pressEvents; - QList<QKeyEvent> releaseEvents; - void keyPressEvent(QKeyEvent *e) override { pressEvents << *e; } - void keyReleaseEvent(QKeyEvent *e) override { releaseEvents << *e; } + ~KeyEventRecordingWidget() { qDeleteAll(pressEvents); qDeleteAll(releaseEvents); } + QList<QKeyEvent *> pressEvents; + QList<QKeyEvent *> releaseEvents; + void keyPressEvent(QKeyEvent *e) override { pressEvents << e->clone(); } + void keyReleaseEvent(QKeyEvent *e) override { releaseEvents << e->clone(); } }; void tst_QWebEngineView::unhandledKeyEventPropagation() @@ -608,7 +732,7 @@ void tst_QWebEngineView::unhandledKeyEventPropagation() QSignalSpy loadFinishedSpy(&webView, SIGNAL(loadFinished(bool))); webView.load(QUrl("qrc:///resources/keyboardEvents.html")); - QVERIFY(loadFinishedSpy.wait()); + QTRY_VERIFY_WITH_TIMEOUT(loadFinishedSpy.size() > 0, 20000); evaluateJavaScriptSync(webView.page(), "document.getElementById('first_div').focus()"); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("first_div")); @@ -641,43 +765,53 @@ void tst_QWebEngineView::unhandledKeyEventPropagation() // The page will consume the Tab key to change focus between elements while the arrow // keys won't be used. QCOMPARE(parentWidget.pressEvents.size(), 3); - QCOMPARE(parentWidget.pressEvents[0].key(), (int)Qt::Key_Right); - QCOMPARE(parentWidget.pressEvents[1].key(), (int)Qt::Key_Left); - QCOMPARE(parentWidget.pressEvents[2].key(), (int)Qt::Key_Y); + QCOMPARE(parentWidget.pressEvents[0]->key(), (int)Qt::Key_Right); + QCOMPARE(parentWidget.pressEvents[1]->key(), (int)Qt::Key_Left); + QCOMPARE(parentWidget.pressEvents[2]->key(), (int)Qt::Key_Y); // Key releases will all come back unconsumed. - QCOMPARE(parentWidget.releaseEvents[0].key(), (int)Qt::Key_Right); - QCOMPARE(parentWidget.releaseEvents[1].key(), (int)Qt::Key_Tab); - QCOMPARE(parentWidget.releaseEvents[2].key(), (int)Qt::Key_Left); - QCOMPARE(parentWidget.releaseEvents[3].key(), (int)Qt::Key_Y); + QCOMPARE(parentWidget.releaseEvents[0]->key(), (int)Qt::Key_Right); + QCOMPARE(parentWidget.releaseEvents[1]->key(), (int)Qt::Key_Tab); + QCOMPARE(parentWidget.releaseEvents[2]->key(), (int)Qt::Key_Left); + QCOMPARE(parentWidget.releaseEvents[3]->key(), (int)Qt::Key_Y); } void tst_QWebEngineView::horizontalScrollbarTest() { +#if QT_CONFIG(webengine_embedded_build) + // Embedded builds enable the OverlayScrollbar and Viewport features (see 'useEmbeddedSwitches' in web_engine_context.cpp). + // These features make the scrollbar simpler assuming we are on a device with small (usually touch) display. + // These scrollbars behave differently on mouse events. + QSKIP("Embedded builds have different scrollbar, skipping test."); +#endif QString html("<html><body>" "<div style='width: 1000px; height: 1000px; background-color: green' />" "</body></html>"); QWebEngineView view; + PageWithPaintListeners page; + view.setPage(&page); view.setFixedSize(600, 600); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); + QSignalSpy firstPaintSpy(&page, &PageWithPaintListeners::firstContentfulPaint); QSignalSpy loadSpy(view.page(), SIGNAL(loadFinished(bool))); view.setHtml(html); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); + QTRY_COMPARE(firstPaintSpy.size(), 1); QVERIFY(view.page()->scrollPosition() == QPoint(0, 0)); QSignalSpy scrollSpy(view.page(), SIGNAL(scrollPositionChanged(QPointF))); // Note: The test below assumes that the layout direction is Qt::LeftToRight. - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, QPoint(550, 595)); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, QPoint(550, 595)); scrollSpy.wait(); QVERIFY(view.page()->scrollPosition().x() > 0); // Note: The test below assumes that the layout direction is Qt::LeftToRight. - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, QPoint(20, 595)); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, QPoint(20, 595)); scrollSpy.wait(); QVERIFY(view.page()->scrollPosition() == QPoint(0, 0)); } @@ -903,7 +1037,7 @@ public: case QEvent::ContextMenu: case QEvent::KeyPress: case QEvent::KeyRelease: -#ifndef QT_NO_WHEELEVENT +#if QT_CONFIG(wheelevent) case QEvent::Wheel: #endif ++m_eventCounter; @@ -937,7 +1071,7 @@ public: private: int m_eventCounter; - QVector<QString> m_eventHistory; + QList<QString> m_eventHistory; }; void tst_QWebEngineView::doNotSendMouseKeyboardEventsWhenDisabled() @@ -958,7 +1092,7 @@ void tst_QWebEngineView::doNotSendMouseKeyboardEventsWhenDisabled() QSignalSpy loadSpy(&webView, SIGNAL(loadFinished(bool))); webView.setHtml("<html><head><title>Title</title></head><body>Hello" "<input id=\"input\" type=\"text\"></body></html>"); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); // When the webView is enabled, the events are swallowed by it, and the parent widget // does not receive any events, otherwise all events are processed by the parent widget. @@ -1005,7 +1139,7 @@ void tst_QWebEngineView::stopSettingFocusWhenDisabled() QSignalSpy loadSpy(&webView, SIGNAL(loadFinished(bool))); webView.setHtml("<html><head><title>Title</title></head><body>Hello" "<input id=\"input\" type=\"text\"></body></html>"); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QTRY_COMPARE_WITH_TIMEOUT(webView.hasFocus(), focusResult, 1000); evaluateJavaScriptSync(webView.page(), "document.getElementById(\"input\").focus()"); @@ -1118,21 +1252,23 @@ void tst_QWebEngineView::focusInternalRenderWidgetHostViewQuickItem() QWebEngineView *webView = new QWebEngineView; QWebEngineSettings *settings = webView->page()->settings(); settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false); - webView->resize(300, 300); + webView->resize(300, 100); - QHBoxLayout *layout = new QHBoxLayout; + QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(label); layout->addWidget(webView); + containerWidget->resize(300, 200); containerWidget->setLayout(layout); containerWidget->show(); QVERIFY(QTest::qWaitForWindowExposed(containerWidget.data())); // Load the content, and check that focus is not set. QSignalSpy loadSpy(webView, SIGNAL(loadFinished(bool))); - webView->setHtml("<html><head><title>Title</title></head><body>Hello" - "<input id=\"input\" type=\"text\"></body></html>"); - QTRY_COMPARE(loadSpy.count(), 1); + webView->setHtml("<html><body>" + " <input id='input1' type='text'/>" + "</body></html>"); + QTRY_COMPARE(loadSpy.size(), 1); QTRY_COMPARE(webView->hasFocus(), false); // Manually trigger focus. @@ -1141,15 +1277,43 @@ void tst_QWebEngineView::focusInternalRenderWidgetHostViewQuickItem() // Check that focus is set in QWebEngineView and all internal classes. QTRY_COMPARE(webView->hasFocus(), true); - QQuickWidget *renderWidgetHostViewQtDelegateWidget = - qobject_cast<QQuickWidget *>(webView->focusProxy()); - QVERIFY(renderWidgetHostViewQtDelegateWidget); - QTRY_COMPARE(renderWidgetHostViewQtDelegateWidget->hasFocus(), true); + QQuickWidget *webEngineQuickWidget = qobject_cast<QQuickWidget *>(webView->focusProxy()); + QVERIFY(webEngineQuickWidget); + QTRY_COMPARE(webEngineQuickWidget->hasFocus(), true); + + QQuickItem *root = webEngineQuickWidget->rootObject(); + // The root item should not has focus, otherwise it would handle input events + // instead of the RenderWidgetHostViewQtDelegateItem. + QVERIFY(!root->hasFocus()); + + QCOMPARE(root->childItems().size(), 1); + QQuickItem *renderWidgetHostViewQtDelegateItem = root->childItems().at(0); + QVERIFY(renderWidgetHostViewQtDelegateItem); + QTRY_COMPARE(renderWidgetHostViewQtDelegateItem->hasFocus(), true); + // Test if QWebEngineView handles key events. + QTRY_COMPARE(renderWidgetHostViewQtDelegateItem->hasActiveFocus(), true); + + // Key events should not be forwarded to the unfocused input field. + QTRY_COMPARE(evaluateJavaScriptSync(webView->page(), + "document.getElementById('input1').value").toString(), + QStringLiteral("")); + QTest::keyClick(webView->focusProxy(), Qt::Key_X); + QTest::qWait(100); + QTRY_COMPARE(evaluateJavaScriptSync(webView->page(), + "document.getElementById('input1').value").toString(), + QStringLiteral("")); + + // Focus the input field. Focus rectangle is expected to appear around the input field. + evaluateJavaScriptSync(webView->page(), "document.getElementById('input1').focus()"); + QTRY_COMPARE(evaluateJavaScriptSync(webView->page(), + "document.activeElement.id").toString(), + QStringLiteral("input1")); - QQuickItem *renderWidgetHostViewQuickItem = - renderWidgetHostViewQtDelegateWidget->rootObject(); - QVERIFY(renderWidgetHostViewQuickItem); - QTRY_COMPARE(renderWidgetHostViewQuickItem->hasFocus(), true); + // Test the focused input field with a key event. + QTest::keyClick(webView->focusProxy(), Qt::Key_X); + QTRY_COMPARE(evaluateJavaScriptSync(webView->page(), + "document.getElementById('input1').value").toString(), + QStringLiteral("x")); } void tst_QWebEngineView::doNotBreakLayout() @@ -1179,6 +1343,9 @@ void tst_QWebEngineView::doNotBreakLayout() void tst_QWebEngineView::changeLocale() { + if (QTestPrivate::isRunningArmOnX86()) + QSKIP("Does not work with QEMU. (QTBUG-94911)"); + QStringList errorLines; QUrl url("http://non.existent/"); @@ -1186,20 +1353,20 @@ void tst_QWebEngineView::changeLocale() QWebEngineView viewDE; QSignalSpy loadFinishedSpyDE(&viewDE, SIGNAL(loadFinished(bool))); viewDE.load(url); - QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpyDE.count(), 1, 20000); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpyDE.size(), 1, 20000); QTRY_VERIFY(!toPlainTextSync(viewDE.page()).isEmpty()); - errorLines = toPlainTextSync(viewDE.page()).split(QRegularExpression("[\r\n]"), QString::SkipEmptyParts); + errorLines = toPlainTextSync(viewDE.page()).split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts); QCOMPARE(errorLines.first().toUtf8(), QByteArrayLiteral("Die Website ist nicht erreichbar")); QLocale::setDefault(QLocale("en")); QWebEngineView viewEN; QSignalSpy loadFinishedSpyEN(&viewEN, SIGNAL(loadFinished(bool))); viewEN.load(url); - QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpyEN.count(), 1, 20000); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpyEN.size(), 1, 20000); QTRY_VERIFY(!toPlainTextSync(viewEN.page()).isEmpty()); - errorLines = toPlainTextSync(viewEN.page()).split(QRegularExpression("[\r\n]"), QString::SkipEmptyParts); + errorLines = toPlainTextSync(viewEN.page()).split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts); QCOMPARE(errorLines.first().toUtf8(), QByteArrayLiteral("This site can\xE2\x80\x99t be reached")); // Reset error page @@ -1209,13 +1376,56 @@ void tst_QWebEngineView::changeLocale() // Check whether an existing QWebEngineView keeps the language settings after changing the default locale viewDE.load(url); - QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpyDE.count(), 1, 20000); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpyDE.size(), 1, 20000); QTRY_VERIFY(!toPlainTextSync(viewDE.page()).isEmpty()); - errorLines = toPlainTextSync(viewDE.page()).split(QRegularExpression("[\r\n]"), QString::SkipEmptyParts); + errorLines = toPlainTextSync(viewDE.page()).split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts); QCOMPARE(errorLines.first().toUtf8(), QByteArrayLiteral("Die Website ist nicht erreichbar")); } +void tst_QWebEngineView::mixLangLocale_data() +{ + QTest::addColumn<QString>("locale"); + QTest::addColumn<QByteArray>("formattedNumber"); + QTest::newRow("en_DK") << "en-DK" << QByteArray("1.234.567.890"); + QTest::newRow("de") << "de" << QByteArray("1.234.567.890"); + QTest::newRow("de_CH") << "de-CH" << QByteArray("1’234’567’890"); + QTest::newRow("eu_ES") << "eu-ES" << QByteArray("1.234.567.890"); + QTest::newRow("hu_HU") << "hu-HU" << QByteArray("1\xC2\xA0""234\xC2\xA0""567\xC2\xA0""890"); // no-break spaces +} + +void tst_QWebEngineView::mixLangLocale() +{ + QFETCH(QString, locale); + QFETCH(QByteArray, formattedNumber); + + QLocale::setDefault(QLocale(locale)); + + QWebEngineView view; + QSignalSpy loadSpy(&view, &QWebEngineView::loadFinished); + + bool terminated = false; + auto sc = connect(view.page(), &QWebEnginePage::renderProcessTerminated, [&] () { terminated = true; }); + + view.load(QUrl("qrc:///resources/dummy.html")); + QTRY_VERIFY(terminated || loadSpy.size() == 1); + + QVERIFY2(!terminated, + qPrintable(QString("Locale [%1] terminated: %2, loaded: %3").arg(locale).arg(terminated).arg(loadSpy.size()))); + QVERIFY(loadSpy.first().first().toBool()); + + QString content = toPlainTextSync(view.page()); + QVERIFY2(!content.isEmpty() && content.contains("test content"), qPrintable(content)); + + QCOMPARE(evaluateJavaScriptSync(view.page(), "navigator.language").toString(), QLocale().bcp47Name()); + + if (locale == "eu-ES") + QEXPECT_FAIL("", "Basque number formatting is somehow dependent on environment", Continue); + QCOMPARE(evaluateJavaScriptSync(view.page(), "Number(1234567890).toLocaleString()").toByteArray(), formattedNumber); + + QLocale::setDefault(QLocale("en")); +} + void tst_QWebEngineView::inputMethodsTextFormat_data() { QTest::addColumn<QString>("string"); @@ -1248,17 +1458,19 @@ void tst_QWebEngineView::inputMethodsTextFormat_data() void tst_QWebEngineView::inputMethodsTextFormat() { - QWebEngineView view; - view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); - QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); + QWebEnginePage page; + QWebEngineView view(&page); + page.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); - view.setHtml("<html><body>" + page.setHtml("<html><body>" " <input type='text' id='input1' style='font-family: serif' value='' maxlength='20'/>" "</body></html>"); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); - evaluateJavaScriptSync(view.page(), "document.getElementById('input1').focus()"); view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + evaluateJavaScriptSync(&page, "document.getElementById('input1').focus()"); QFETCH(QString, string); QFETCH(int, start); @@ -1282,8 +1494,8 @@ void tst_QWebEngineView::inputMethodsTextFormat() attrs.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, start, length, format)); QInputMethodEvent im(string, attrs); - QVERIFY(QApplication::sendEvent(view.focusProxy(), &im)); - QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString(), string); + QApplication::sendEvent(view.focusProxy(), &im); + QTRY_COMPARE_WITH_TIMEOUT(evaluateJavaScriptSync(&page, "document.getElementById('input1').value").toString(), string, 20000); } void tst_QWebEngineView::keyboardEvents() @@ -1292,7 +1504,7 @@ void tst_QWebEngineView::keyboardEvents() view.show(); QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); view.load(QUrl("qrc:///resources/keyboardEvents.html")); - QVERIFY(loadFinishedSpy.wait()); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 30000); QStringList elements; elements << "first_div" << "second_div"; @@ -1411,17 +1623,21 @@ void tst_QWebEngineView::keyboardFocusAfterPopup() QTRY_COMPARE(QApplication::focusWidget(), window.lineEdit); // Trigger QCompleter's popup and select the first suggestion. - QTest::keyClick(QApplication::focusWindow(), Qt::Key_T); + QTest::keyPress(QApplication::focusWindow(), Qt::Key_T); + QTest::keyRelease(QApplication::focusWindow(), Qt::Key_T); QTRY_VERIFY(QApplication::activePopupWidget()); - QTest::keyClick(QApplication::focusWindow(), Qt::Key_Down); - QTest::keyClick(QApplication::focusWindow(), Qt::Key_Enter); + QTest::keyPress(QApplication::focusWindow(), Qt::Key_Down); + QTest::keyRelease(QApplication::focusWindow(), Qt::Key_Down); + QTest::keyPress(QApplication::focusWindow(), Qt::Key_Enter); + QTest::keyRelease(QApplication::focusWindow(), Qt::Key_Enter); // Due to FocusOnNavigationEnabled, focus should now move to the webView. QTRY_COMPARE(QApplication::focusWidget(), window.webView->focusProxy()); // Keyboard events sent to the window should go to the <input> element. - QVERIFY(loadFinishedSpy.count() || loadFinishedSpy.wait()); - QTest::keyClick(QApplication::focusWindow(), Qt::Key_X); + QVERIFY(loadFinishedSpy.size() || loadFinishedSpy.wait()); + QTest::keyPress(QApplication::focusWindow(), Qt::Key_X); + QTest::keyRelease(QApplication::focusWindow(), Qt::Key_X); QTRY_COMPARE(evaluateJavaScriptSync(window.webView->page(), "document.getElementById('input1').value").toString(), QStringLiteral("x")); } @@ -1448,9 +1664,9 @@ void tst_QWebEngineView::mouseClick() QVERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty()); textInputCenter = elementCenter(view.page(), "input"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input")); - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 0); QVERIFY(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString().isEmpty()); // Double click @@ -1465,13 +1681,13 @@ void tst_QWebEngineView::mouseClick() textInputCenter = elementCenter(view.page(), "input"); QTest::mouseMultiClick(view.focusProxy(), textInputCenter, 2); QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 1); + QCOMPARE(selectionChangedSpy.size(), 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); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 2); + QCOMPARE(selectionChangedSpy.size(), 2); QVERIFY(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString().isEmpty()); // Triple click @@ -1486,13 +1702,13 @@ void tst_QWebEngineView::mouseClick() textInputCenter = elementCenter(view.page(), "input"); QTest::mouseMultiClick(view.focusProxy(), textInputCenter, 3); QVERIFY(selectionChangedSpy.wait()); - QTRY_COMPARE(selectionChangedSpy.count(), 2); + QTRY_COMPARE(selectionChangedSpy.size(), 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); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 3); + QCOMPARE(selectionChangedSpy.size(), 3); QVERIFY(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString().isEmpty()); } @@ -1514,18 +1730,18 @@ void tst_QWebEngineView::postData() eventloop.quit(); }); - connect(socket, &QIODevice::readyRead, this, [this, socket, &server, &postData](){ + connect(socket, &QIODevice::readyRead, this, [socket, &server, &postData](){ QByteArray rawData = socket->readAll(); QStringList lines = QString::fromLocal8Bit(rawData).split("\r\n"); // examine request - QStringList request = lines[0].split(" ", QString::SkipEmptyParts); - bool requestOk = request.length() > 2 + QStringList request = lines[0].split(" ", Qt::SkipEmptyParts); + bool requestOk = request.size() > 2 && request[2].toUpper().startsWith("HTTP/") && request[0].toUpper() == "POST" && request[1] == "/"; if (!requestOk) // POST and HTTP/... can be switched(?) - requestOk = request.length() > 2 + requestOk = request.size() > 2 && request[0].toUpper().startsWith("HTTP/") && request[2].toUpper() == "POST" && request[1] == "/"; @@ -1533,16 +1749,16 @@ void tst_QWebEngineView::postData() // examine headers int line = 1; bool headersOk = true; - for (; headersOk && line < lines.length(); line++) { + for (; headersOk && line < lines.size(); line++) { QStringList headerParts = lines[line].split(":"); - if (headerParts.length() < 2) + if (headerParts.size() < 2) break; QString headerKey = headerParts[0].trimmed().toLower(); QString headerValue = headerParts[1].trimmed().toLower(); if (headerKey == "host") headersOk = headersOk && (headerValue == "127.0.0.1") - && (headerParts.length() == 3) + && (headerParts.size() == 3) && (headerParts[2].trimmed() == QString::number(server.serverPort())); if (headerKey == "content-type") @@ -1551,12 +1767,12 @@ void tst_QWebEngineView::postData() // examine body bool bodyOk = true; - if (lines.length() == line+2) { + if (lines.size() == line+2) { QStringList postedFields = lines[line+1].split("&"); QMap<QString, QString> postedData; - for (int i = 0; bodyOk && i < postedFields.length(); i++) { + for (int i = 0; bodyOk && i < postedFields.size(); i++) { QStringList postedField = postedFields[i].split("="); - if (postedField.length() == 2) + if (postedField.size() == 2) postedData[QUrl::fromPercentEncoding(postedField[0].toLocal8Bit())] = QUrl::fromPercentEncoding(postedField[1].toLocal8Bit()); else @@ -1629,17 +1845,17 @@ void tst_QWebEngineView::postData() void tst_QWebEngineView::inputFieldOverridesShortcuts() { + QWebEngineView view; bool actionTriggered = false; - QAction *action = new QAction; + QAction *action = new QAction(&view); connect(action, &QAction::triggered, [&actionTriggered] () { actionTriggered = true; }); - - QWebEngineView view; view.addAction(action); QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); view.setHtml(QString("<html><body>" "<button id=\"btn1\" type=\"button\">push it real good</button>" "<input id=\"input1\" type=\"text\" value=\"x\">" + "<input id=\"pass1\" type=\"password\" value=\"x\">" "</body></html>")); QVERIFY(loadFinishedSpy.wait()); @@ -1651,8 +1867,13 @@ void tst_QWebEngineView::inputFieldOverridesShortcuts() "document.getElementById('input1').value").toString(); }; + auto passwordFieldValue = [&view] () -> QString { + return evaluateJavaScriptSync(view.page(), + "document.getElementById('pass1').value").toString(); + }; + // The input form is not focused. The action is triggered on pressing Shift+Delete. - action->setShortcut(Qt::SHIFT + Qt::Key_Delete); + action->setShortcut(Qt::SHIFT | Qt::Key_Delete); QTest::keyClick(view.windowHandle(), Qt::Key_Delete, Qt::ShiftModifier); QTRY_VERIFY(actionTriggered); QCOMPARE(inputFieldValue(), QString("x")); @@ -1674,9 +1895,21 @@ void tst_QWebEngineView::inputFieldOverridesShortcuts() QTRY_COMPARE(inputFieldValue(), QString("yxx")); QVERIFY(!actionTriggered); + // The password input form is focused. The action is not triggered, and the form's text changed. + evaluateJavaScriptSync(view.page(), "document.getElementById('pass1').focus();"); + QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("pass1")); + actionTriggered = false; + QTest::keyClick(view.windowHandle(), Qt::Key_Y); + QTRY_COMPARE(passwordFieldValue(), QString("yx")); + QTest::keyClick(view.windowHandle(), Qt::Key_X); + QTRY_COMPARE(passwordFieldValue(), QString("yxx")); + QVERIFY(!actionTriggered); + // The input form is focused. Make sure we don't override all short cuts. // A Ctrl-1 action is no default Qt key binding and should be triggerable. - action->setShortcut(Qt::CTRL + Qt::Key_1); + evaluateJavaScriptSync(view.page(), "document.getElementById('input1').focus();"); + QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); + action->setShortcut(Qt::CTRL | Qt::Key_1); QTest::keyClick(view.windowHandle(), Qt::Key_1, Qt::ControlModifier); QTRY_VERIFY(actionTriggered); QCOMPARE(inputFieldValue(), QString("yxx")); @@ -1733,20 +1966,11 @@ public: inputMethodPrivate->testContext = 0; } - virtual void showInputPanel() - { - m_visible = true; - } - virtual void hideInputPanel() - { - m_visible = false; - } - virtual bool isInputPanelVisible() const - { - return m_visible; - } + void showInputPanel() override { m_visible = true; } + void hideInputPanel() override { m_visible = false; } + bool isInputPanelVisible() const override { return m_visible; } - virtual void update(Qt::InputMethodQueries queries) + void update(Qt::InputMethodQueries queries) override { if (!qApp->focusObject()) return; @@ -1783,7 +2007,7 @@ void tst_QWebEngineView::softwareInputPanel() QVERIFY(loadFinishedSpy.wait()); QPoint textInputCenter = elementCenter(view.page(), "input1"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); // This part of the test checks if the SIP (Software Input Panel) is triggered, @@ -1802,7 +2026,7 @@ void tst_QWebEngineView::softwareInputPanel() QTRY_VERIFY(!testContext.isInputPanelVisible()); testContext.hideInputPanel(); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QTRY_VERIFY(testContext.isInputPanelVisible()); view.setHtml("<html><body><p id='para'>nothing to input here</p></body></html>"); @@ -1810,7 +2034,7 @@ void tst_QWebEngineView::softwareInputPanel() testContext.hideInputPanel(); QPoint paraCenter = elementCenter(view.page(), "para"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, paraCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, paraCenter); QVERIFY(!testContext.isInputPanelVisible()); @@ -1822,7 +2046,7 @@ void tst_QWebEngineView::softwareInputPanel() QVERIFY(loadFinishedSpy.wait()); QPoint btnDivCenter = elementCenter(view.page(), "btnDiv"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, btnDivCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, btnDivCenter); QVERIFY(!testContext.isInputPanelVisible()); } @@ -1842,13 +2066,14 @@ void tst_QWebEngineView::inputContextQueryInput() view.setHtml("<html><body>" " <input type='text' id='input1' value='' size='50'/>" "</body></html>"); - QTRY_COMPARE(loadFinishedSpy.count(), 1); - QCOMPARE(testContext.infos.count(), 0); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + QCOMPARE(testContext.infos.size(), 0); // Set focus on an input field. QPoint textInputCenter = elementCenter(view.page(), "input1"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); - QTRY_COMPARE(testContext.infos.count(), 2); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); + QTRY_COMPARE(testContext.infos.size(), 2); QCOMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); foreach (const InputMethodInfo &info, testContext.infos) { QCOMPARE(info.cursorPosition, 0); @@ -1860,7 +2085,7 @@ void tst_QWebEngineView::inputContextQueryInput() // Change content of an input field from JavaScript. evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value='QtWebEngine';"); - QTRY_COMPARE(testContext.infos.count(), 1); + QTRY_COMPARE(testContext.infos.size(), 1); QCOMPARE(testContext.infos[0].cursorPosition, 11); QCOMPARE(testContext.infos[0].anchorPosition, 11); QCOMPARE(testContext.infos[0].surroundingText, QStringLiteral("QtWebEngine")); @@ -1869,7 +2094,7 @@ void tst_QWebEngineView::inputContextQueryInput() // Change content of an input field by key press. QTest::keyClick(view.focusProxy(), Qt::Key_Exclam); - QTRY_COMPARE(testContext.infos.count(), 1); + QTRY_COMPARE(testContext.infos.size(), 1); QCOMPARE(testContext.infos[0].cursorPosition, 12); QCOMPARE(testContext.infos[0].anchorPosition, 12); QCOMPARE(testContext.infos[0].surroundingText, QStringLiteral("QtWebEngine!")); @@ -1878,7 +2103,7 @@ void tst_QWebEngineView::inputContextQueryInput() // Change cursor position. QTest::keyClick(view.focusProxy(), Qt::Key_Left); - QTRY_COMPARE(testContext.infos.count(), 1); + QTRY_COMPARE(testContext.infos.size(), 1); QCOMPARE(testContext.infos[0].cursorPosition, 11); QCOMPARE(testContext.infos[0].anchorPosition, 11); QCOMPARE(testContext.infos[0].surroundingText, QStringLiteral("QtWebEngine!")); @@ -1893,8 +2118,8 @@ void tst_QWebEngineView::inputContextQueryInput() QInputMethodEvent event("", attributes); QApplication::sendEvent(view.focusProxy(), &event); } - QTRY_COMPARE(testContext.infos.count(), 2); - QTRY_COMPARE(selectionChangedSpy.count(), 1); + QTRY_COMPARE(testContext.infos.size(), 2); + QTRY_COMPARE(selectionChangedSpy.size(), 1); // As a first step, Chromium moves the cursor to the start of the selection. // We don't filter this in QtWebEngine because we don't know yet if this is part of a selection. @@ -1919,8 +2144,8 @@ void tst_QWebEngineView::inputContextQueryInput() QInputMethodEvent event("", attributes); QApplication::sendEvent(view.focusProxy(), &event); } - QTRY_COMPARE(testContext.infos.count(), 1); - QTRY_COMPARE(selectionChangedSpy.count(), 1); + QTRY_COMPARE(testContext.infos.size(), 1); + QTRY_COMPARE(selectionChangedSpy.size(), 1); QCOMPARE(testContext.infos[0].cursorPosition, 0); QCOMPARE(testContext.infos[0].anchorPosition, 0); QCOMPARE(testContext.infos[0].surroundingText, QStringLiteral("QtWebEngine!")); @@ -1934,9 +2159,9 @@ void tst_QWebEngineView::inputContextQueryInput() QInputMethodEvent event("123", attributes); QApplication::sendEvent(view.focusProxy(), &event); } - QTRY_COMPARE(testContext.infos.count(), 1); - QCOMPARE(testContext.infos[0].cursorPosition, 3); - QCOMPARE(testContext.infos[0].anchorPosition, 3); + QTRY_COMPARE(testContext.infos.size(), 1); + QCOMPARE(testContext.infos[0].cursorPosition, 0); + QCOMPARE(testContext.infos[0].anchorPosition, 0); QCOMPARE(testContext.infos[0].surroundingText, QStringLiteral("QtWebEngine!")); QCOMPARE(testContext.infos[0].selectedText, QStringLiteral("")); QCOMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString(), QStringLiteral("123QtWebEngine!")); @@ -1948,7 +2173,7 @@ void tst_QWebEngineView::inputContextQueryInput() QInputMethodEvent event("", attributes); QApplication::sendEvent(view.focusProxy(), &event); } - QTRY_COMPARE(testContext.infos.count(), 2); + QTRY_COMPARE(testContext.infos.size(), 2); foreach (const InputMethodInfo &info, testContext.infos) { QCOMPARE(info.cursorPosition, 0); QCOMPARE(info.anchorPosition, 0); @@ -1965,7 +2190,7 @@ void tst_QWebEngineView::inputContextQueryInput() event.setCommitString(QStringLiteral("123"), 0, 0); QApplication::sendEvent(view.focusProxy(), &event); } - QTRY_COMPARE(testContext.infos.count(), 1); + QTRY_COMPARE(testContext.infos.size(), 1); QCOMPARE(testContext.infos[0].cursorPosition, 3); QCOMPARE(testContext.infos[0].anchorPosition, 3); QCOMPARE(testContext.infos[0].surroundingText, QStringLiteral("123QtWebEngine!")); @@ -1975,7 +2200,7 @@ void tst_QWebEngineView::inputContextQueryInput() // Focus out. QTest::keyPress(view.focusProxy(), Qt::Key_Tab); - QTRY_COMPARE(testContext.infos.count(), 1); + QTRY_COMPARE(testContext.infos.size(), 1); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("")); testContext.infos.clear(); } @@ -1994,9 +2219,10 @@ void tst_QWebEngineView::inputMethods() " <input type='text' id='input1' style='font-family: serif' value='' maxlength='20' size='50'/>" "</body></html>"); QTRY_COMPARE(loadFinishedSpy.size(), 1); + QVERIFY(QTest::qWaitForWindowExposed(&view)); QPoint textInputCenter = elementCenter(view.page(), "input1"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); // ImCursorRectangle @@ -2018,7 +2244,7 @@ void tst_QWebEngineView::inputMethods() QInputMethodEvent eventText(text, inputAttributes); QApplication::sendEvent(view.focusProxy(), &eventText); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString(), text); - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 0); } { @@ -2027,7 +2253,7 @@ void tst_QWebEngineView::inputMethods() eventText.setCommitString(text, 0, 0); QApplication::sendEvent(view.focusProxy(), &eventText); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString(), text); - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 0); } // ImMaximumTextLength @@ -2091,35 +2317,36 @@ void tst_QWebEngineView::textSelectionInInputField() " <input type='text' id='input1' value='QtWebEngine' size='50'/>" "</body></html>"); QVERIFY(loadFinishedSpy.wait()); + QVERIFY(QTest::qWaitForWindowExposed(&view)); // Tests for Selection when the Editor is NOT in Composition mode // LEFT to RIGHT selection // Mouse click event moves the current cursor to the end of the text QPoint textInputCenter = elementCenter(view.page(), "input1"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 11); QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 11); // There was no selection to be changed by the click - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 0); QList<QInputMethodEvent::Attribute> attributes; QInputMethodEvent event(QString(), attributes); event.setCommitString("XXX", 0, 0); QApplication::sendEvent(view.focusProxy(), &event); QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngineXXX")); - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 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); + QCOMPARE(selectionChangedSpy.size(), 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); + QCOMPARE(selectionChangedSpy.size(), 0); // Move to the start of the line QTest::keyClick(view.focusProxy(), Qt::Key_Home); @@ -2131,7 +2358,7 @@ void tst_QWebEngineView::textSelectionInInputField() // Select to the end of the line QTest::keyClick(view.focusProxy(), Qt::Key_End, Qt::ShiftModifier); QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 1); + QCOMPARE(selectionChangedSpy.size(), 1); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 2); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 11); @@ -2139,9 +2366,9 @@ void tst_QWebEngineView::textSelectionInInputField() // RIGHT to LEFT selection // Deselect the selection (this moves the current cursor to the end of the text) - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 2); + QCOMPARE(selectionChangedSpy.size(), 2); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 11); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 11); @@ -2154,7 +2381,7 @@ void tst_QWebEngineView::textSelectionInInputField() // Select to the start of the line QTest::keyClick(view.focusProxy(), Qt::Key_Home, Qt::ShiftModifier); QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 3); + QCOMPARE(selectionChangedSpy.size(), 3); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 9); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 0); @@ -2164,6 +2391,7 @@ void tst_QWebEngineView::textSelectionInInputField() void tst_QWebEngineView::textSelectionOutOfInputField() { QWebEngineView view; + view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); view.resize(640, 480); view.show(); @@ -2173,35 +2401,33 @@ void tst_QWebEngineView::textSelectionOutOfInputField() " This is a text" "</body></html>"); QVERIFY(loadFinishedSpy.wait()); + QVERIFY(QTest::qWaitForWindowExposed(&view)); - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 0); QVERIFY(!view.hasSelection()); QVERIFY(view.page()->selectedText().isEmpty()); // Simple click should not update text selection, however it updates selection bounds in Chromium - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, view.geometry().center()); - QCOMPARE(selectionChangedSpy.count(), 0); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, view.geometry().center()); + QCOMPARE(selectionChangedSpy.size(), 0); QVERIFY(!view.hasSelection()); QVERIFY(view.page()->selectedText().isEmpty()); // Select text by ctrl+a QTest::keyClick(view.windowHandle(), Qt::Key_A, Qt::ControlModifier); - QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 1); + QTRY_COMPARE(selectionChangedSpy.size(), 1); QVERIFY(view.hasSelection()); QCOMPARE(view.page()->selectedText(), QString("This is a text")); // Deselect text by mouse click - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, view.geometry().center()); - QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 2); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, view.geometry().center()); + QTRY_COMPARE(selectionChangedSpy.size(), 2); QVERIFY(!view.hasSelection()); QVERIFY(view.page()->selectedText().isEmpty()); // Select text by ctrl+a QTest::keyClick(view.windowHandle(), Qt::Key_A, Qt::ControlModifier); - QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 3); + QTRY_COMPARE(selectionChangedSpy.size(), 3); QVERIFY(view.hasSelection()); QCOMPARE(view.page()->selectedText(), QString("This is a text")); @@ -2209,8 +2435,7 @@ void tst_QWebEngineView::textSelectionOutOfInputField() view.hide(); view.page()->setLifecycleState(QWebEnginePage::LifecycleState::Discarded); view.show(); - QVERIFY(loadFinishedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 4); + QTRY_COMPARE(selectionChangedSpy.size(), 4); QVERIFY(!view.hasSelection()); QVERIFY(view.page()->selectedText().isEmpty()); @@ -2221,8 +2446,9 @@ void tst_QWebEngineView::textSelectionOutOfInputField() " <input type='text' id='input1' value='QtWebEngine' size='50'/>" "</body></html>"); QVERIFY(loadFinishedSpy.wait()); + QVERIFY(QTest::qWaitForWindowExposed(&view)); - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 0); QVERIFY(!view.hasSelection()); QVERIFY(view.page()->selectedText().isEmpty()); @@ -2232,31 +2458,27 @@ void tst_QWebEngineView::textSelectionOutOfInputField() // Select the whole page by ctrl+a QTest::keyClick(view.windowHandle(), Qt::Key_A, Qt::ControlModifier); - QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 1); + QTRY_COMPARE(selectionChangedSpy.size(), 1); QVERIFY(view.hasSelection()); QVERIFY(view.page()->selectedText().startsWith(QString("This is a text"))); // Remove selection by clicking into an input field QPoint textInputCenter = elementCenter(view.page(), "input1"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); - QVERIFY(selectionChangedSpy.wait()); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); + QTRY_COMPARE(selectionChangedSpy.size(), 2); QCOMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); - QCOMPARE(selectionChangedSpy.count(), 2); QVERIFY(!view.hasSelection()); QVERIFY(view.page()->selectedText().isEmpty()); // Select the content of the input field by ctrl+a QTest::keyClick(view.windowHandle(), Qt::Key_A, Qt::ControlModifier); - QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 3); + QTRY_COMPARE(selectionChangedSpy.size(), 3); QVERIFY(view.hasSelection()); QCOMPARE(view.page()->selectedText(), QString("QtWebEngine")); // Deselect input field's text by mouse click - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, view.geometry().center()); - QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 4); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, view.geometry().center()); + QTRY_COMPARE(selectionChangedSpy.size(), 4); QVERIFY(!view.hasSelection()); QVERIFY(view.page()->selectedText().isEmpty()); } @@ -2275,14 +2497,14 @@ void tst_QWebEngineView::hiddenText() QVERIFY(loadFinishedSpy.wait()); QPoint passwordInputCenter = elementCenter(view.page(), "password1"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, passwordInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, passwordInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("password1")); QVERIFY(!view.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); QVERIFY(view.focusProxy()->inputMethodHints() & Qt::ImhHiddenText); QPoint textInputCenter = elementCenter(view.page(), "input1"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); QVERIFY(!(view.focusProxy()->inputMethodHints() & Qt::ImhHiddenText)); } @@ -2300,9 +2522,10 @@ void tst_QWebEngineView::emptyInputMethodEvent() " <input type='text' id='input1' value='QtWebEngine'/>" "</body></html>"); QVERIFY(loadFinishedSpy.wait()); + QVERIFY(QTest::qWaitForWindowExposed(&view)); evaluateJavaScriptSync(view.page(), "var inputEle = document.getElementById('input1'); inputEle.focus(); inputEle.select();"); - QTRY_COMPARE(selectionChangedSpy.count(), 1); + QTRY_COMPARE(selectionChangedSpy.size(), 1); // 1. Empty input method event does not clear text QInputMethodEvent emptyEvent; @@ -2348,9 +2571,10 @@ void tst_QWebEngineView::imeComposition() " <input type='text' id='input1' value='QtWebEngine inputMethod'/>" "</body></html>"); QVERIFY(loadFinishedSpy.wait()); + QVERIFY(QTest::qWaitForWindowExposed(&view)); evaluateJavaScriptSync(view.page(), "var inputEle = document.getElementById('input1'); inputEle.focus(); inputEle.select();"); - QTRY_COMPARE(selectionChangedSpy.count(), 1); + QTRY_COMPARE(selectionChangedSpy.size(), 1); // Clear the selection, also cancel the ongoing composition if there is one. { @@ -2360,7 +2584,7 @@ void tst_QWebEngineView::imeComposition() QInputMethodEvent event("", attributes); QApplication::sendEvent(view.focusProxy(), &event); selectionChangedSpy.wait(); - QCOMPARE(selectionChangedSpy.count(), 2); + QCOMPARE(selectionChangedSpy.size(), 2); } QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("QtWebEngine inputMethod")); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 0); @@ -2381,7 +2605,7 @@ void tst_QWebEngineView::imeComposition() QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 0); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 0); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("")); - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 0); // Send temporary text, which makes the editor has composition 'n'. { @@ -2393,7 +2617,7 @@ void tst_QWebEngineView::imeComposition() QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 0); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 0); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("")); - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 0); // Send commit text, which makes the editor conforms composition. { @@ -2406,7 +2630,7 @@ void tst_QWebEngineView::imeComposition() QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 1); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 1); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("")); - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 0); // 2. insert a character to the middle of the line. @@ -2420,7 +2644,7 @@ void tst_QWebEngineView::imeComposition() QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 1); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 1); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("")); - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 0); // Send commit text, which makes the editor conforms composition. { @@ -2433,7 +2657,7 @@ void tst_QWebEngineView::imeComposition() QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 2); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 2); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("")); - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 0); // 3. Insert a character to the end of the line. @@ -2451,7 +2675,7 @@ void tst_QWebEngineView::imeComposition() QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 25); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 25); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("")); - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 0); // Send commit text, which makes the editor conforms composition. { @@ -2464,7 +2688,7 @@ void tst_QWebEngineView::imeComposition() QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 26); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 26); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("")); - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 0); // 4. Replace the selection. @@ -2474,7 +2698,7 @@ void tst_QWebEngineView::imeComposition() QTest::keyClick(view.focusProxy(), Qt::Key_Left, Qt::ShiftModifier | Qt::AltModifier); #endif QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 1); + QCOMPARE(selectionChangedSpy.size(), 1); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("oeQtWebEngine inputMethodt")); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 14); @@ -2488,7 +2712,7 @@ void tst_QWebEngineView::imeComposition() QApplication::sendEvent(view.focusProxy(), &event); // The new composition should clear the previous selection QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 2); + QCOMPARE(selectionChangedSpy.size(), 2); } QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("oeQtWebEngine ")); // The cursor should be positioned at the end of the composition text @@ -2508,7 +2732,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); + QCOMPARE(selectionChangedSpy.size(), 2); selectionChangedSpy.clear(); @@ -2533,8 +2757,8 @@ void tst_QWebEngineView::imeComposition() QApplication::sendEvent(view.focusProxy(), &event); } QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("")); - QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 11); - QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 11); + QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 0); + QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 0); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("")); QCOMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString(), QString("QtWebEngine")); @@ -2550,7 +2774,7 @@ void tst_QWebEngineView::imeComposition() QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 11); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QString("")); QCOMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString(), QString("QtWebEngine")); - QCOMPARE(selectionChangedSpy.count(), 0); + QCOMPARE(selectionChangedSpy.size(), 0); } void tst_QWebEngineView::newlineInTextarea() @@ -2565,6 +2789,7 @@ void tst_QWebEngineView::newlineInTextarea() " <textarea rows='5' cols='1' id='input1'></textarea>" "</body></html>"); QVERIFY(loadFinishedSpy.wait()); + QVERIFY(QTest::qWaitForWindowExposed(&view)); evaluateJavaScriptSync(view.page(), "var inputEle = document.getElementById('input1'); inputEle.focus(); inputEle.select();"); QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString().isEmpty()); @@ -2689,6 +2914,7 @@ void tst_QWebEngineView::imeJSInputEvents() " <pre id='log'></pre>" "</body></html>"); QVERIFY(loadFinishedSpy.wait()); + QVERIFY(QTest::qWaitForWindowExposed(&view)); evaluateJavaScriptSync(view.page(), "document.getElementById('input').focus()"); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input")); @@ -2703,7 +2929,7 @@ void tst_QWebEngineView::imeJSInputEvents() } // Simply committing text should not trigger any JS composition event. - QTRY_COMPARE(logLines().count(), 3); + QTRY_COMPARE(logLines().size(), 3); QCOMPARE(logLines()[0], QStringLiteral("[object InputEvent] beforeinput commit")); QCOMPARE(logLines()[1], QStringLiteral("[object TextEvent] textInput commit")); QCOMPARE(logLines()[2], QStringLiteral("[object InputEvent] input commit")); @@ -2719,7 +2945,7 @@ void tst_QWebEngineView::imeJSInputEvents() qApp->processEvents(); } - QTRY_COMPARE(logLines().count(), 4); + QTRY_COMPARE(logLines().size(), 4); QCOMPARE(logLines()[0], QStringLiteral("[object CompositionEvent] compositionstart ")); QCOMPARE(logLines()[1], QStringLiteral("[object InputEvent] beforeinput preedit")); QCOMPARE(logLines()[2], QStringLiteral("[object CompositionEvent] compositionupdate preedit")); @@ -2733,7 +2959,7 @@ void tst_QWebEngineView::imeJSInputEvents() qApp->processEvents(); } - QTRY_COMPARE(logLines().count(), 9); + QTRY_COMPARE(logLines().size(), 9); QCOMPARE(logLines()[4], QStringLiteral("[object InputEvent] beforeinput commit")); QCOMPARE(logLines()[5], QStringLiteral("[object CompositionEvent] compositionupdate commit")); QCOMPARE(logLines()[6], QStringLiteral("[object TextEvent] textInput commit")); @@ -2751,7 +2977,7 @@ void tst_QWebEngineView::imeJSInputEvents() qApp->processEvents(); } - QTRY_COMPARE(logLines().count(), 4); + QTRY_COMPARE(logLines().size(), 4); QCOMPARE(logLines()[0], QStringLiteral("[object CompositionEvent] compositionstart ")); QCOMPARE(logLines()[1], QStringLiteral("[object InputEvent] beforeinput preedit")); QCOMPARE(logLines()[2], QStringLiteral("[object CompositionEvent] compositionupdate preedit")); @@ -2764,7 +2990,7 @@ void tst_QWebEngineView::imeJSInputEvents() qApp->processEvents(); } - QTRY_COMPARE(logLines().count(), 9); + QTRY_COMPARE(logLines().size(), 9); QCOMPARE(logLines()[4], QStringLiteral("[object InputEvent] beforeinput ")); QCOMPARE(logLines()[5], QStringLiteral("[object CompositionEvent] compositionupdate ")); QCOMPARE(logLines()[6], QStringLiteral("[object TextEvent] textInput ")); @@ -2811,6 +3037,7 @@ void tst_QWebEngineView::imeCompositionQueryEvent() " <input type='text' id='input1' />" "</body></html>"); QVERIFY(loadFinishedSpy.wait()); + QVERIFY(QTest::qWaitForWindowExposed(&view)); evaluateJavaScriptSync(view.page(), "document.getElementById('input1').focus()"); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); @@ -2830,6 +3057,7 @@ void tst_QWebEngineView::imeCompositionQueryEvent() } QInputMethodQueryEvent srrndTextQuery(Qt::ImSurroundingText); + QInputMethodQueryEvent absolutePosQuery(Qt::ImAbsolutePosition); QInputMethodQueryEvent cursorPosQuery(Qt::ImCursorPosition); QInputMethodQueryEvent anchorPosQuery(Qt::ImAnchorPosition); @@ -2841,16 +3069,18 @@ void tst_QWebEngineView::imeCompositionQueryEvent() qApp->processEvents(); } QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString(), QString("composition")); - QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 11); + QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 0); QApplication::sendEvent(input, &srrndTextQuery); + QApplication::sendEvent(input, &absolutePosQuery); QApplication::sendEvent(input, &cursorPosQuery); QApplication::sendEvent(input, &anchorPosQuery); qApp->processEvents(); QTRY_COMPARE(srrndTextQuery.value(Qt::ImSurroundingText).toString(), QString("")); - QTRY_COMPARE(cursorPosQuery.value(Qt::ImCursorPosition).toInt(), 11); - QTRY_COMPARE(anchorPosQuery.value(Qt::ImAnchorPosition).toInt(), 11); + QTRY_COMPARE(absolutePosQuery.value(Qt::ImAbsolutePosition).toInt(), 0); + QTRY_COMPARE(cursorPosQuery.value(Qt::ImCursorPosition).toInt(), 0); + QTRY_COMPARE(anchorPosQuery.value(Qt::ImAnchorPosition).toInt(), 0); // Send commit { @@ -2864,16 +3094,67 @@ void tst_QWebEngineView::imeCompositionQueryEvent() QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("composition")); QApplication::sendEvent(input, &srrndTextQuery); + QApplication::sendEvent(input, &absolutePosQuery); QApplication::sendEvent(input, &cursorPosQuery); QApplication::sendEvent(input, &anchorPosQuery); qApp->processEvents(); QTRY_COMPARE(srrndTextQuery.value(Qt::ImSurroundingText).toString(), QString("composition")); + QTRY_COMPARE(absolutePosQuery.value(Qt::ImAbsolutePosition).toInt(), 11); QTRY_COMPARE(cursorPosQuery.value(Qt::ImCursorPosition).toInt(), 11); QTRY_COMPARE(anchorPosQuery.value(Qt::ImAnchorPosition).toInt(), 11); + + // Test another composition to ensure that the cursor position is set correctly. + // In this case cursor will be at position 11 during input composition. + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("123", attributes); + QApplication::sendEvent(input, &event); + qApp->processEvents(); + } + QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value") + .toString(), + QString("composition123")); + QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 11); + + QApplication::sendEvent(input, &srrndTextQuery); + QApplication::sendEvent(input, &absolutePosQuery); + QApplication::sendEvent(input, &cursorPosQuery); + QApplication::sendEvent(input, &anchorPosQuery); + qApp->processEvents(); + + QTRY_COMPARE(srrndTextQuery.value(Qt::ImSurroundingText).toString(), QString("composition")); + QTRY_COMPARE(absolutePosQuery.value(Qt::ImAbsolutePosition).toInt(), 11); + QTRY_COMPARE(cursorPosQuery.value(Qt::ImCursorPosition).toInt(), 11); + QTRY_COMPARE(anchorPosQuery.value(Qt::ImAnchorPosition).toInt(), 11); + + // Send commit + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("", attributes); + event.setCommitString("123"); + QApplication::sendEvent(input, &event); + qApp->processEvents(); + } + + QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value") + .toString(), + QString("composition123")); + QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 14); + + QApplication::sendEvent(input, &srrndTextQuery); + QApplication::sendEvent(input, &absolutePosQuery); + QApplication::sendEvent(input, &cursorPosQuery); + QApplication::sendEvent(input, &anchorPosQuery); + qApp->processEvents(); + + QTRY_COMPARE(srrndTextQuery.value(Qt::ImSurroundingText).toString(), QString("composition123")); + QTRY_COMPARE(absolutePosQuery.value(Qt::ImAbsolutePosition).toInt(), 14); + QTRY_COMPARE(cursorPosQuery.value(Qt::ImCursorPosition).toInt(), 14); + QTRY_COMPARE(anchorPosQuery.value(Qt::ImAnchorPosition).toInt(), 14); } -#ifndef QT_NO_CLIPBOARD +#if QT_CONFIG(clipboard) void tst_QWebEngineView::globalMouseSelection() { if (!QApplication::clipboard()->supportsSelection()) { @@ -2895,20 +3176,20 @@ void tst_QWebEngineView::globalMouseSelection() // Select text via JavaScript evaluateJavaScriptSync(view.page(), "var inputEle = document.getElementById('input1'); inputEle.focus(); inputEle.select();"); - QTRY_COMPARE(selectionChangedSpy.count(), 1); + QTRY_COMPARE(selectionChangedSpy.size(), 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); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 2); + QCOMPARE(selectionChangedSpy.size(), 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(selectionChangedSpy.size(), 3); QCOMPARE(QApplication::clipboard()->text(QClipboard::Selection), QStringLiteral("QtWebEngine")); } #endif @@ -2934,43 +3215,62 @@ void tst_QWebEngineView::noContextMenu() QTest::mouseMove(wrapper.windowHandle(), QPoint(10,10)); QTest::mouseClick(wrapper.windowHandle(), Qt::RightButton); - QTRY_COMPARE(wrapper.findChildren<QMenu *>().count(), 1); + QTRY_COMPARE(wrapper.findChildren<QMenu *>().size(), 1); QVERIFY(view.findChildren<QMenu *>().isEmpty()); } void tst_QWebEngineView::contextMenu_data() { QTest::addColumn<int>("childrenCount"); + QTest::addColumn<bool>("isCustomMenu"); QTest::addColumn<Qt::ContextMenuPolicy>("contextMenuPolicy"); - QTest::newRow("defaultContextMenu") << 1 << Qt::DefaultContextMenu; - QTest::newRow("customContextMenu") << 1 << Qt::CustomContextMenu; - QTest::newRow("preventContextMenu") << 0 << Qt::PreventContextMenu; + QTest::newRow("defaultContextMenu") << 1 << false << Qt::DefaultContextMenu; + QTest::newRow("customContextMenu") << 1 << true << Qt::CustomContextMenu; + QTest::newRow("preventContextMenu") << 0 << false << Qt::PreventContextMenu; } void tst_QWebEngineView::contextMenu() { QFETCH(int, childrenCount); + QFETCH(bool, isCustomMenu); QFETCH(Qt::ContextMenuPolicy, contextMenuPolicy); QWebEngineView view; + QMenu *customMenu = nullptr; 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); + connect(&view, &QWebEngineView::customContextMenuRequested, [&view, &customMenu] (const QPoint &pt) { + Q_ASSERT(!customMenu); + customMenu = new QMenu(&view); + customMenu->addAction("Action1"); + customMenu->addAction("Action2"); + customMenu->popup(pt); }); } view.setContextMenuPolicy(contextMenuPolicy); + + // input is supposed to be skipped before first real navigation in >= 79 + QSignalSpy loadSpy(&view, &QWebEngineView::loadFinished); + view.load(QUrl("about:blank")); view.resize(640, 480); view.show(); + QTRY_COMPARE(loadSpy.size(), 1); 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); + + // verify for zero children will always succeed, so should be tested with at least minor timeout + if (childrenCount <= 0) { + QVERIFY(!QTest::qWaitFor([&view] () { return view.findChildren<QMenu *>().size() > 0; }, 500)); + } else { + QTRY_COMPARE(view.findChildren<QMenu *>().size(), childrenCount); + if (isCustomMenu) { + QCOMPARE(view.findChildren<QMenu *>().first(), customMenu); + } + } + QCOMPARE(!!customMenu, isCustomMenu); } void tst_QWebEngineView::mouseLeave() @@ -2989,7 +3289,7 @@ void tst_QWebEngineView::mouseLeave() QVBoxLayout *layout = new QVBoxLayout; layout->setAlignment(Qt::AlignTop); layout->setSpacing(0); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(label); layout->addWidget(view); containerWidget->setLayout(layout); @@ -3031,51 +3331,59 @@ void tst_QWebEngineView::webUIURLs_data() QTest::addColumn<bool>("supported"); QTest::newRow("about") << QUrl("chrome://about") << false; QTest::newRow("accessibility") << QUrl("chrome://accessibility") << true; - QTest::newRow("appcache-internals") << QUrl("chrome://appcache-internals") << true; + QTest::newRow("app-service-internals") << QUrl("chrome://app-service-internals") << false; + QTest::newRow("app-settings") << QUrl("chrome://app-settings") << false; QTest::newRow("apps") << QUrl("chrome://apps") << false; + QTest::newRow("attribution-internals") << QUrl("chrome://attribution-internals") << true; + QTest::newRow("autofill-internals") << QUrl("chrome://autofill-internals") << 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("connectors-internals") << QUrl("chrome://connectors-internals") << 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("device-log") << QUrl("chrome://device-log") << true; QTest::newRow("dino") << QUrl("chrome://dino") << false; // It works but this is an error page - QTest::newRow("dns") << QUrl("chrome://dns") << false; + QTest::newRow("discards") << QUrl("chrome://discards") << false; + QTest::newRow("download-internals") << QUrl("chrome://download-internals") << false; QTest::newRow("downloads") << QUrl("chrome://downloads") << false; QTest::newRow("extensions") << QUrl("chrome://extensions") << false; + QTest::newRow("extensions-internals") << QUrl("chrome://extensions-internals") << 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("history") << QUrl("chrome://history") << false; + QTest::newRow("history-clusters-internals") << QUrl("chrome://history-clusters-internals") << false; QTest::newRow("indexeddb-internals") << QUrl("chrome://indexeddb-internals") << true; QTest::newRow("inspect") << QUrl("chrome://inspect") << false; + QTest::newRow("interstitials") << QUrl("chrome://interstitials") << 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("management") << QUrl("chrome://management") << false; + QTest::newRow("media-engagement") << QUrl("chrome://media-engagement") << false; QTest::newRow("media-internals") << QUrl("chrome://media-internals") << true; + QTest::newRow("nacl") << QUrl("chrome://nacl") << false; QTest::newRow("net-export") << QUrl("chrome://net-export") << false; - QTest::newRow("net-internals") << QUrl("chrome://net-internals") << false; + QTest::newRow("net-internals") << QUrl("chrome://net-internals") << true; 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("optimization-guide-internals") << QUrl("chrome://optimization-guide-internals") << 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("prefs-internals") << QUrl("chrome://prefs-internals") << false; QTest::newRow("print") << QUrl("chrome://print") << false; QTest::newRow("process-internals") << QUrl("chrome://process-internals") << true; - QTest::newRow("profiler") << QUrl("chrome://profiler") << false; QTest::newRow("quota-internals") << QUrl("chrome://quota-internals") << true; QTest::newRow("safe-browsing") << QUrl("chrome://safe-browsing") << false; -#ifdef Q_OS_LINUX +#if defined(Q_OS_LINUX) || defined(Q_OS_WIN) QTest::newRow("sandbox") << QUrl("chrome://sandbox") << true; #else QTest::newRow("sandbox") << QUrl("chrome://sandbox") << false; @@ -3084,19 +3392,23 @@ void tst_QWebEngineView::webUIURLs_data() 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("terms") << QUrl("chrome://terms") << false; - QTest::newRow("thumbnails") << QUrl("chrome://thumbnails") << false; - QTest::newRow("tracing") << QUrl("chrome://tracing") << false; + QTest::newRow("tracing") << QUrl("chrome://tracing") << true; QTest::newRow("translate-internals") << QUrl("chrome://translate-internals") << false; + QTest::newRow("ukm") << QUrl("chrome://ukm") << true; QTest::newRow("usb-internals") << QUrl("chrome://usb-internals") << false; - QTest::newRow("user-actions") << QUrl("chrome://user-actions") << false; + QTest::newRow("user-actions") << QUrl("chrome://user-actions") << true; QTest::newRow("version") << QUrl("chrome://version") << false; + QTest::newRow("web-app-internals") << QUrl("chrome://web-app-internals") << false; +#if QT_CONFIG(webengine_webrtc) QTest::newRow("webrtc-internals") << QUrl("chrome://webrtc-internals") << true; - QTest::newRow("webrtc-logs") << QUrl("chrome://webrtc-logs") << false; +#if QT_CONFIG(webengine_extensions) + QTest::newRow("webrtc-logs") << QUrl("chrome://webrtc-logs") << true; +#endif // QT_CONFIG(webengine_extensions) +#endif // QT_CONFIG(webengine_webrtc) + QTest::newRow("whats-new") << QUrl("chrome://whats-new") << false; } void tst_QWebEngineView::webUIURLs() @@ -3108,7 +3420,7 @@ void tst_QWebEngineView::webUIURLs() view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); view.load(url); - QVERIFY(loadFinishedSpy.wait()); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 90000); QCOMPARE(loadFinishedSpy.takeFirst().at(0).toBool(), supported); } @@ -3117,7 +3429,7 @@ void tst_QWebEngineView::visibilityState() QWebEngineView view; QSignalSpy spy(&view, &QWebEngineView::loadFinished); view.load(QStringLiteral("about:blank")); - QVERIFY(spy.count() || spy.wait()); + QVERIFY(spy.size() || spy.wait()); QVERIFY(spy.takeFirst().takeFirst().toBool()); QCOMPARE(evaluateJavaScriptSync(view.page(), "document.visibilityState").toString(), QStringLiteral("hidden")); view.show(); @@ -3125,6 +3437,60 @@ void tst_QWebEngineView::visibilityState() QCOMPARE(evaluateJavaScriptSync(view.page(), "document.visibilityState").toString(), QStringLiteral("visible")); } +void tst_QWebEngineView::visibilityState2() +{ + QWebEngineView view; + QSignalSpy spy(&view, &QWebEngineView::loadFinished); + view.show(); + view.load(QStringLiteral("about:blank")); + view.hide(); + QVERIFY(spy.size() || spy.wait()); + QVERIFY(spy.takeFirst().takeFirst().toBool()); + QCOMPARE(evaluateJavaScriptSync(view.page(), "document.visibilityState").toString(), QStringLiteral("hidden")); +} + +void tst_QWebEngineView::visibilityState3() +{ + QWebEnginePage page1; + QWebEnginePage page2; + QSignalSpy spy1(&page1, &QWebEnginePage::loadFinished); + QSignalSpy spy2(&page2, &QWebEnginePage::loadFinished); + page1.load(QStringLiteral("about:blank")); + page2.load(QStringLiteral("about:blank")); + QVERIFY(spy1.size() || spy1.wait()); + QVERIFY(spy2.size() || spy2.wait()); + QWebEngineView view; + view.setPage(&page1); + view.show(); + QCOMPARE(evaluateJavaScriptSync(&page1, "document.visibilityState").toString(), QStringLiteral("visible")); + QCOMPARE(evaluateJavaScriptSync(&page2, "document.visibilityState").toString(), QStringLiteral("hidden")); + view.setPage(&page2); + QCOMPARE(evaluateJavaScriptSync(&page1, "document.visibilityState").toString(), QStringLiteral("hidden")); + QCOMPARE(evaluateJavaScriptSync(&page2, "document.visibilityState").toString(), QStringLiteral("visible")); +} + +void tst_QWebEngineView::jsKeyboardEvent_data() +{ + QTest::addColumn<char>("key"); + QTest::addColumn<Qt::KeyboardModifiers>("modifiers"); + QTest::addColumn<QString>("expected"); + +#if defined(Q_OS_MACOS) + // See Qt::AA_MacDontSwapCtrlAndMeta + Qt::KeyboardModifiers controlModifier = Qt::MetaModifier; +#else + Qt::KeyboardModifiers controlModifier = Qt::ControlModifier; +#endif + + QTest::newRow("Ctrl+Shift+A") << 'A' << (controlModifier | Qt::ShiftModifier) << QStringLiteral( + "16,ShiftLeft,Shift,false,true,false;" + "17,ControlLeft,Control,true,true,false;" + "65,KeyA,A,true,true,false;"); + QTest::newRow("Ctrl+z") << 'z' << controlModifier << QStringLiteral( + "17,ControlLeft,Control,true,false,false;" + "90,KeyZ,z,true,false,false;"); +} + void tst_QWebEngineView::jsKeyboardEvent() { QWebEngineView view; @@ -3134,18 +3500,13 @@ void tst_QWebEngineView::jsKeyboardEvent() "addEventListener('keydown', (ev) => {" " log += [ev.keyCode, ev.code, ev.key, ev.ctrlKey, ev.shiftKey, ev.altKey].join(',') + ';';" "});"); + + QFETCH(char, key); + QFETCH(Qt::KeyboardModifiers, modifiers); + QFETCH(QString, expected); + // Note that this only tests the fallback code path where native scan codes are not used. -#if defined(Q_OS_MACOS) - // See Qt::AA_MacDontSwapCtrlAndMeta - QTest::keyClick(view.focusProxy(), 'A', Qt::MetaModifier | Qt::ShiftModifier); -#else - QTest::keyClick(view.focusProxy(), 'A', Qt::ControlModifier | Qt::ShiftModifier); -#endif - QString expected = QStringLiteral( - "16,ShiftLeft,Shift,false,true,false;" - "17,ControlLeft,Control,true,true,false;" - "65,KeyA,A,true,true,false;" - ); + QTest::keyClick(view.focusProxy(), key, modifiers); QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "log") != QVariant(QString())); QCOMPARE(evaluateJavaScriptSync(view.page(), "log"), expected); } @@ -3161,7 +3522,27 @@ void tst_QWebEngineView::deletePage() QVERIFY(view.page()); QSignalSpy spy(view.page(), &QWebEnginePage::loadFinished); view.page()->load(QStringLiteral("about:blank")); - QTRY_VERIFY(spy.count()); + QTRY_VERIFY(spy.size()); +} + +void tst_QWebEngineView::autoDeleteOnExternalPageDelete() +{ + QPointer<QWebEngineView> view = new QWebEngineView; + QPointer<QWebEnginePage> page = new QWebEnginePage; + auto sg = qScopeGuard([&] () { delete view; delete page; }); + + QSignalSpy spy(page, &QWebEnginePage::loadFinished); + view->setPage(page); + view->show(); + view->resize(320, 240); + page->load(QUrl("about:blank")); + QTRY_VERIFY(spy.size()); + QVERIFY(page->parent() != view); + + auto sc = QObject::connect(page, &QWebEnginePage::destroyed, view, &QWebEngineView::deleteLater); + QTimer::singleShot(0, page, &QObject::deleteLater); + QTRY_VERIFY(!page); + QTRY_VERIFY(!view); } class TestView : public QWebEngineView { @@ -3188,7 +3569,7 @@ void tst_QWebEngineView::closeOpenerTab() testView->settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true); QSignalSpy loadFinishedSpy(testView, SIGNAL(loadFinished(bool))); testView->setUrl(QStringLiteral("about:blank")); - QTRY_VERIFY(loadFinishedSpy.count()); + QTRY_VERIFY(loadFinishedSpy.size()); testView->page()->runJavaScript(QStringLiteral("window.open('about:blank','_blank')")); QTRY_COMPARE(testView->createdWindows.size(), 1); auto *newView = testView->createdWindows.at(0); @@ -3207,9 +3588,13 @@ void tst_QWebEngineView::switchPage() QWebEnginePage page2(&profile); QSignalSpy loadFinishedSpy1(&page1, SIGNAL(loadFinished(bool))); QSignalSpy loadFinishedSpy2(&page2, SIGNAL(loadFinished(bool))); + // TODO fixme: page without the view has no real widget behind, so + // reading graphical content will fail, add view for now. + QWebEngineView webView1(&page1, nullptr); + QWebEngineView webView2(&page2, nullptr); page1.setHtml("<html><body bgcolor=\"#000000\"></body></html>"); page2.setHtml("<html><body bgcolor=\"#ffffff\"></body></html>"); - QTRY_VERIFY(loadFinishedSpy1.count() && loadFinishedSpy2.count()); + QTRY_VERIFY(loadFinishedSpy1.size() && loadFinishedSpy2.size()); QWebEngineView webView; webView.resize(300,300); webView.show(); @@ -3250,7 +3635,7 @@ void tst_QWebEngineView::setViewDeletesImplicitPage() QWebEngineView view; QPointer<QWebEnginePage> implicitPage = view.page(); QWebEnginePage explicitPage; - explicitPage.setView(&view); + view.setPage(&explicitPage); QCOMPARE(view.page(), &explicitPage); QVERIFY(!implicitPage); // should be deleted } @@ -3271,8 +3656,8 @@ void tst_QWebEngineView::setViewPreservesExplicitPage() QWebEngineView view; QPointer<QWebEnginePage> explicitPage1 = new QWebEnginePage(&view); QPointer<QWebEnginePage> explicitPage2 = new QWebEnginePage(&view); - explicitPage1->setView(&view); - explicitPage2->setView(&view); + view.setPage(explicitPage1.data()); + view.setPage(explicitPage2.data()); QCOMPARE(view.page(), explicitPage2.data()); QVERIFY(explicitPage1); // should not be deleted } @@ -3280,17 +3665,342 @@ void tst_QWebEngineView::setViewPreservesExplicitPage() void tst_QWebEngineView::closeDiscardsPage() { QWebEngineProfile profile; - QWebEnginePage page(&profile); - QWebEngineView view; - view.setPage(&page); + QWebEngineView view(&profile, nullptr); view.resize(300, 300); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); - QCOMPARE(page.isVisible(), true); - QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QCOMPARE(view.page()->isVisible(), true); + QCOMPARE(view.page()->lifecycleState(), QWebEnginePage::LifecycleState::Active); view.close(); - QCOMPARE(page.isVisible(), false); - QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Discarded); + QCOMPARE(view.page()->isVisible(), false); + QCOMPARE(view.page()->lifecycleState(), QWebEnginePage::LifecycleState::Discarded); +} + + +void tst_QWebEngineView::loadAfterRendererCrashed() +{ + QWebEngineView view; + view.resize(640, 480); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + bool terminated = false; + connect(view.page(), &QWebEnginePage::renderProcessTerminated, [&] () { terminated = true; }); + view.load(QUrl("chrome://crash")); + QTRY_VERIFY_WITH_TIMEOUT(terminated, 30000); + + QSignalSpy loadSpy(&view, &QWebEngineView::loadFinished); + view.load(QUrl("qrc:///resources/dummy.html")); + QTRY_COMPARE(loadSpy.size(), 1); + QVERIFY(loadSpy.first().first().toBool()); +} + +void tst_QWebEngineView::inspectElement() +{ + QWebEngineView view; + view.resize(640, 480); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + auto page = view.page(); + // shouldn't do anything until page is set + page->triggerAction(QWebEnginePage::InspectElement); + QTest::qWait(100); + + QSignalSpy spy(&view, &QWebEngineView::loadFinished); + view.load(QUrl("data:text/plain,foobarbaz")); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 12000); + + // shouldn't do anything since inspector is not attached + page->triggerAction(QWebEnginePage::InspectElement); + QTest::qWait(100); + + QWebEngineView inspectorView; + inspectorView.resize(640, 480); + inspectorView.show(); + QVERIFY(QTest::qWaitForWindowExposed(&inspectorView)); + inspectorView.page()->setInspectedPage(page); + + page->triggerAction(QWebEnginePage::InspectElement); + // TODO verify somehow + QTest::qWait(100); +} + +void tst_QWebEngineView::navigateOnDrop_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<bool>("navigateOnDrop"); + QTest::newRow("file") << QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).absoluteFilePath("resources/dummy.html")) << true; + QTest::newRow("qrc") << QUrl("qrc:///resources/dummy.html") << true; + QTest::newRow("file_no_navigate") << QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).absoluteFilePath("resources/dummy.html")) << false; + QTest::newRow("qrc_no_navigate") << QUrl("qrc:///resources/dummy.html") << false; +} + +void tst_QWebEngineView::navigateOnDrop() +{ + QFETCH(QUrl, url); + QFETCH(bool, navigateOnDrop); + struct WebEngineView : QWebEngineView { + QWebEngineView* createWindow(QWebEnginePage::WebWindowType /* type */) override { return this; } + } view; + view.page()->settings()->setAttribute(QWebEngineSettings::NavigateOnDropEnabled, navigateOnDrop); + view.resize(640, 480); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + QSignalSpy loadSpy(&view, &QWebEngineView::loadFinished); + QMimeData mimeData; + mimeData.setUrls({ url }); + + auto sendEvents = [&] () { + QDragEnterEvent dee(view.rect().center(), Qt::CopyAction, &mimeData, Qt::LeftButton, Qt::NoModifier); + QApplication::sendEvent(&view, &dee); + QDropEvent de(view.rect().center(), Qt::CopyAction, &mimeData, Qt::LeftButton, Qt::NoModifier); + QApplication::sendEvent(&view, &de); + }; + + sendEvents(); + if (navigateOnDrop) { + QTRY_COMPARE(loadSpy.size(), 1); + QVERIFY(loadSpy.last().first().toBool()); + QCOMPARE(view.url(), url); + } else { + QTest::qWait(500); + QCOMPARE(loadSpy.size(), 0); + QVERIFY(view.url() != url); + } + + // Check dynamically changing the setting + loadSpy.clear(); + view.page()->settings()->setAttribute(QWebEngineSettings::NavigateOnDropEnabled, !navigateOnDrop); + view.setUrl(QUrl("about:blank")); + QTRY_COMPARE(loadSpy.size(), 1); + + sendEvents(); + if (!navigateOnDrop) { + QTRY_COMPARE(loadSpy.size(), 2); + QVERIFY(loadSpy.last().first().toBool()); + QCOMPARE(view.url(), url); + } else { + QTest::qWait(500); + QCOMPARE(loadSpy.size(), 1); + QVERIFY(view.url() != url); + } +} + +void tst_QWebEngineView::emptyUriListOnDrop() +{ + QWebEngineView view; + view.resize(640, 480); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + QMimeData mimeData; + mimeData.setUrls({}); // creates an empty uri-list MIME type entry + QVERIFY(mimeData.hasUrls()); + + QDragEnterEvent dee(view.rect().center(), Qt::CopyAction, &mimeData, Qt::LeftButton, + Qt::NoModifier); + QApplication::sendEvent(&view, &dee); + QDropEvent de(view.rect().center(), Qt::CopyAction, &mimeData, Qt::LeftButton, Qt::NoModifier); + QApplication::sendEvent(&view, &de); + + QSignalSpy loadSpy(&view, &QWebEngineView::loadFinished); + view.setUrl(QUrl("about:blank")); + QTRY_COMPARE(loadSpy.size(), 1); +} + +void tst_QWebEngineView::datalist() +{ + QString html("<html><body>" + "<input id='browserInput' list='browserDatalist'>" + "<datalist id='browserDatalist'>" + " <option value='Internet Explorer'>" + " <option value='Firefox'>" + " <option value='Chrome'>" + " <option value='Opera'>" + " <option value='Safari'>" + "</datalist>" + "</body></html>"); + + QWebEngineView view; + view.resize(200, 400); + view.show(); + + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + QSignalSpy loadSpy(&view, &QWebEngineView::loadFinished); + view.setHtml(html); + QTRY_COMPARE(loadSpy.size(), 1); + + QString listValuesJS("(function() {" + " var browserDatalist = document.getElementById('browserDatalist');" + " var options = browserDatalist.options;" + " var result = [];" + " for (let i = 0; i < options.length; ++i) {" + " result.push(options[i].value);" + " }" + " return result;" + "})();"); + QStringList values = evaluateJavaScriptSync(view.page(), listValuesJS).toStringList(); + QCOMPARE(values, QStringList({ "Internet Explorer", "Firefox", "Chrome", "Opera", "Safari" })); + QCOMPARE(evaluateJavaScriptSync(view.page(), "document.getElementById('browserInput').value;") + .toString(), + QStringLiteral("")); + + auto listView = [&view]() -> QListView * { + if (QApplication::topLevelWidgets().size() == 1) { + // No popup case. + return nullptr; + } + + QWidget *autofillPopupWidget = nullptr; + for (QWidget *w : QApplication::topLevelWidgets()) { + if (w != &view) { + autofillPopupWidget = w; + break; + } + } + + if (!autofillPopupWidget) + return nullptr; + + for (QObject *o : autofillPopupWidget->children()) { + if (QListView *listView = qobject_cast<QListView *>(o)) + return listView; + } + + return nullptr; + }; + + // Make sure there is no open popup yet. + QVERIFY(!listView()); + // Click in the input field. + QPoint browserInputCenter = elementCenter(view.page(), "browserInput"); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, browserInputCenter); + // Wait for the popup. + QTRY_VERIFY(listView()); + + // No suggestion is selected. + QCOMPARE(listView()->currentIndex(), QModelIndex()); + QCOMPARE(listView()->model()->rowCount(), 5); + + // Accepting suggestion does nothing. + QTest::keyClick(view.windowHandle(), Qt::Key_Enter); + QVERIFY(listView()); + QCOMPARE(listView()->currentIndex(), QModelIndex()); + + // Escape should close popup. + QTest::keyClick(view.windowHandle(), Qt::Key_Escape); + QTRY_VERIFY(!listView()); + + // Key Down should open the popup and select the first suggestion. + QTest::keyClick(view.windowHandle(), Qt::Key_Down); + QTRY_VERIFY(listView()); + QCOMPARE(listView()->currentIndex().row(), 0); + + // Test keyboard navigation in list. + QTest::keyClick(view.windowHandle(), Qt::Key_Up); + QCOMPARE(listView()->currentIndex().row(), 4); + QTest::keyClick(view.windowHandle(), Qt::Key_Up); + QCOMPARE(listView()->currentIndex().row(), 3); + QTest::keyClick(view.windowHandle(), Qt::Key_PageDown); + QCOMPARE(listView()->currentIndex().row(), 4); + QTest::keyClick(view.windowHandle(), Qt::Key_PageUp); + QCOMPARE(listView()->currentIndex().row(), 0); + QTest::keyClick(view.windowHandle(), Qt::Key_Down); + QCOMPARE(listView()->currentIndex().row(), 1); + QTest::keyClick(view.windowHandle(), Qt::Key_Down); + QCOMPARE(listView()->currentIndex().row(), 2); + + // Test accepting suggestion. + QCOMPARE(static_cast<QStringListModel *>(listView()->model()) + ->data(listView()->currentIndex()) + .toString(), + QStringLiteral("Chrome")); + QTest::keyClick(view.windowHandle(), Qt::Key_Enter); + QTRY_COMPARE( + evaluateJavaScriptSync(view.page(), "document.getElementById('browserInput').value") + .toString(), + QStringLiteral("Chrome")); + // Accept closes popup. + QTRY_VERIFY(!listView()); + + // Clear input field, should not trigger popup. + evaluateJavaScriptSync(view.page(), "document.getElementById('browserInput').value = ''"); + QVERIFY(!listView()); + + // Filter suggestions. + QTest::keyClick(view.windowHandle(), Qt::Key_F); + QTRY_VERIFY(listView()); + QCOMPARE(listView()->model()->rowCount(), 2); + QCOMPARE(listView()->currentIndex(), QModelIndex()); + QCOMPARE(static_cast<QStringListModel *>(listView()->model()) + ->data(listView()->model()->index(0, 0)) + .toString(), + QStringLiteral("Firefox")); + QCOMPARE(static_cast<QStringListModel *>(listView()->model()) + ->data(listView()->model()->index(1, 0)) + .toString(), + QStringLiteral("Safari")); + QTest::keyClick(view.windowHandle(), Qt::Key_I); + QTRY_COMPARE(listView()->model()->rowCount(), 1); + QCOMPARE(listView()->currentIndex(), QModelIndex()); + QCOMPARE(static_cast<QStringListModel *>(listView()->model()) + ->data(listView()->model()->index(0, 0)) + .toString(), + QStringLiteral("Firefox")); + QTest::keyClick(view.windowHandle(), Qt::Key_L); + // Mismatch should close popup. + QTRY_VERIFY(!listView()); + QTRY_COMPARE( + evaluateJavaScriptSync(view.page(), "document.getElementById('browserInput').value") + .toString(), + QStringLiteral("fil")); +} + +class ConsolePage : public QWebEnginePage +{ + Q_OBJECT +public: + ConsolePage(QObject *parent = nullptr) : QWebEnginePage(parent) { } + void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString &message, + int lineNumber, const QString &sourceID) override + { + Q_UNUSED(level) + Q_UNUSED(lineNumber) + Q_UNUSED(sourceID) + if (message.contains("TEST_KEY:Shift")) + emit done(); + } +signals: + void done(); +}; + +//qtbug_113704 +void tst_QWebEngineView::longKeyEventText() +{ + const QString html(QStringLiteral("<html><body><p>TEST</p>" + "<script>" + "document.addEventListener('keydown', (event)=> {" + "console.log('TEST_KEY:' + event.key);" + "});" + "</script>" + "</body></html>")); + + QWebEngineView view; + ConsolePage page; + view.setPage(&page); + QSignalSpy loadFinishedSpy(view.page(), &QWebEnginePage::loadFinished); + view.resize(200, 400); + view.show(); + view.setHtml(html); + QTRY_VERIFY(loadFinishedSpy.size()); + QSignalSpy consoleMessageSpy(&page, &ConsolePage::done); + Qt::Key key(Qt::Key_Shift); + QKeyEvent event(QKeyEvent::KeyPress, key, Qt::NoModifier, QKeySequence(key).toString()); + QApplication::sendEvent(view.focusProxy(), &event); + QTRY_VERIFY(consoleMessageSpy.size()); } QTEST_MAIN(tst_QWebEngineView) diff --git a/tests/auto/widgets/qwebengineview/tst_qwebengineview.qrc b/tests/auto/widgets/qwebengineview/tst_qwebengineview.qrc deleted file mode 100644 index a09be0399..000000000 --- a/tests/auto/widgets/qwebengineview/tst_qwebengineview.qrc +++ /dev/null @@ -1,10 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>resources/index.html</file> - <file>resources/frame_a.html</file> - <file>resources/input_types.html</file> - <file>resources/scrolltest_page.html</file> - <file>resources/keyboardEvents.html</file> - <file>resources/image2.png</file> - </qresource> -</RCC> diff --git a/tests/auto/widgets/schemes/CMakeLists.txt b/tests/auto/widgets/schemes/CMakeLists.txt new file mode 100644 index 000000000..5299b3148 --- /dev/null +++ b/tests/auto/widgets/schemes/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../util/util.cmake) + +qt_internal_add_test(tst_schemes + SOURCES + tst_schemes.cpp + LIBRARIES + Qt::WebEngineWidgets + Test::Util +) + diff --git a/tests/auto/widgets/schemes/schemes.pro b/tests/auto/widgets/schemes/schemes.pro deleted file mode 100644 index e56bbe8f7..000000000 --- a/tests/auto/widgets/schemes/schemes.pro +++ /dev/null @@ -1,3 +0,0 @@ -include(../tests.pri) -exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc -QT *= core-private gui-private diff --git a/tests/auto/widgets/schemes/tst_schemes.cpp b/tests/auto/widgets/schemes/tst_schemes.cpp index 6da34efd8..188c112e4 100644 --- a/tests/auto/widgets/schemes/tst_schemes.cpp +++ b/tests/auto/widgets/schemes/tst_schemes.cpp @@ -1,46 +1,50 @@ -/**************************************************************************** -** -** 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:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <QtTest/QtTest> -#include <qwebengineview.h> #include <qwebenginepage.h> #include <qwebengineprofile.h> #include <qwebenginesettings.h> +#include <qwebengineurlrequestjob.h> +#include <qwebengineurlscheme.h> +#include <qwebengineurlschemehandler.h> +#include <qwebengineview.h> +#include <widgetutil.h> class tst_Schemes : public QObject { Q_OBJECT private Q_SLOTS: + void initTestCase(); + void unknownUrlSchemePolicy_data(); void unknownUrlSchemePolicy(); + void customSchemeFragmentNavigation_data(); + void customSchemeFragmentNavigation(); }; +void tst_Schemes::initTestCase() +{ + QWebEngineUrlScheme pathScheme("path"); + pathScheme.setSyntax(QWebEngineUrlScheme::Syntax::Path); + QWebEngineUrlScheme::registerScheme(pathScheme); + + QWebEngineUrlScheme hostScheme("host"); + hostScheme.setSyntax(QWebEngineUrlScheme::Syntax::Host); + QWebEngineUrlScheme::registerScheme(hostScheme); + + QWebEngineUrlScheme hostAndPortScheme("hostandport"); + hostAndPortScheme.setSyntax(QWebEngineUrlScheme::Syntax::HostAndPort); + hostAndPortScheme.setDefaultPort(3000); + QWebEngineUrlScheme::registerScheme(hostAndPortScheme); + + QWebEngineUrlScheme hostPortUserInfoScheme("hostportuserinfo"); + hostPortUserInfoScheme.setSyntax(QWebEngineUrlScheme::Syntax::HostPortAndUserInformation); + hostPortUserInfoScheme.setDefaultPort(3000); + QWebEngineUrlScheme::registerScheme(hostPortUserInfoScheme); +} + class AcceptNavigationRequestHandler : public QWebEnginePage { public: @@ -58,8 +62,27 @@ public: } }; +Q_DECLARE_METATYPE(QWebEngineSettings::UnknownUrlSchemePolicy) + +void tst_Schemes::unknownUrlSchemePolicy_data() +{ + QTest::addColumn<QWebEngineSettings::UnknownUrlSchemePolicy>("policy"); + QTest::addColumn<bool>("userAction"); + QTest::newRow("DisallowUnknownUrlSchemes, script") << QWebEngineSettings::DisallowUnknownUrlSchemes << false; + QTest::newRow("DisallowUnknownUrlSchemes, user") << QWebEngineSettings::DisallowUnknownUrlSchemes << true; + QTest::newRow("AllowUnknownUrlSchemesFromUserInteraction, script") << QWebEngineSettings::AllowUnknownUrlSchemesFromUserInteraction << false; + QTest::newRow("AllowUnknownUrlSchemesFromUserInteraction, user") << QWebEngineSettings::AllowUnknownUrlSchemesFromUserInteraction << true; + QTest::newRow("AllowAllUnknownUrlSchemes, script") << QWebEngineSettings::AllowAllUnknownUrlSchemes << false; + QTest::newRow("AllowAllUnknownUrlSchemes, user") << QWebEngineSettings::AllowAllUnknownUrlSchemes << true; + QTest::newRow("default UnknownUrlSchemePolicy, script") << QWebEngineSettings::UnknownUrlSchemePolicy(0) << false; + QTest::newRow("default UnknownUrlSchemePolicy, user") << QWebEngineSettings::UnknownUrlSchemePolicy(0) << true; +} + void tst_Schemes::unknownUrlSchemePolicy() { + QFETCH(QWebEngineSettings::UnknownUrlSchemePolicy, policy); + QFETCH(bool, userAction); + QWebEngineView view; AcceptNavigationRequestHandler page; QSignalSpy loadFinishedSpy(&page, &QWebEnginePage::loadFinished); @@ -71,41 +94,187 @@ void tst_Schemes::unknownUrlSchemePolicy() settings->setAttribute(QWebEngineSettings::ErrorPageEnabled, true); settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); - QWebEngineSettings::UnknownUrlSchemePolicy policies[6] = {QWebEngineSettings::DisallowUnknownUrlSchemes, - QWebEngineSettings::DisallowUnknownUrlSchemes, - QWebEngineSettings::AllowUnknownUrlSchemesFromUserInteraction, - QWebEngineSettings::AllowUnknownUrlSchemesFromUserInteraction, - QWebEngineSettings::AllowAllUnknownUrlSchemes, - QWebEngineSettings::AllowAllUnknownUrlSchemes}; - // even iterations are for navigation-requests from javascript, - // odd iterations are for navigations-requests from user-interaction - for (int i = 0; i < 8; i++) { - if (i <= 5) - settings->setUnknownUrlSchemePolicy(policies[i]); - else - settings->resetUnknownUrlSchemePolicy(); - loadFinishedSpy.clear(); - page.acceptNavigationRequestCalls = 0; - bool shouldAccept; - - if (i % 2 == 0) { // navigation request coming from javascript - shouldAccept = (4 <= i && i <= 5); // only case AllowAllUnknownUrlSchemes - view.setHtml("<html><script>setTimeout(function(){ window.location.href='nonexistentscheme://somewhere'; }, 10);</script><body>testing...</body></html>"); - } else { // navigation request coming from user interaction - shouldAccept = (2 <= i); // all cases except DisallowUnknownUrlSchemes - view.setHtml("<html><body><a id='nonexlink' href='nonexistentscheme://somewhere'>nonexistentscheme://somewhere</a></body></html>"); - QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 15000); - // focus and trigger the link - view.page()->runJavaScript("document.getElementById('nonexlink').focus();", [&view](const QVariant &result) { - Q_UNUSED(result); - QTest::sendKeyEvent(QTest::Press, view.focusProxy(), Qt::Key_Return, QString("\r"), Qt::NoModifier); - QTest::sendKeyEvent(QTest::Release, view.focusProxy(), Qt::Key_Return, QString("\r"), Qt::NoModifier); - }); - } - - QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 2, 15000); - QCOMPARE(page.acceptNavigationRequestCalls, shouldAccept ? 1 : 0); + if (policy > 0) + settings->setUnknownUrlSchemePolicy(policy); + else + settings->resetUnknownUrlSchemePolicy(); + loadFinishedSpy.clear(); + page.acceptNavigationRequestCalls = 0; + bool shouldAccept; + + if (!userAction) { // navigation request coming from javascript + shouldAccept = (policy == QWebEngineSettings::AllowAllUnknownUrlSchemes); + view.setHtml("<html><script>setTimeout(function(){ window.location.href='nonexistentscheme://somewhere'; }, 10);</script><body>testing...</body></html>"); + } else { // navigation request coming from user interaction + shouldAccept = (policy != QWebEngineSettings::DisallowUnknownUrlSchemes); + view.setHtml("<html><body><a id='nonexlink' href='nonexistentscheme://somewhere'>nonexistentscheme://somewhere</a></body></html>"); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 15000); + // focus and trigger the link + view.page()->runJavaScript("document.getElementById('nonexlink').focus();", [&view](const QVariant &result) { + Q_UNUSED(result); + QTest::sendKeyEvent(QTest::Press, view.focusProxy(), Qt::Key_Return, QString("\r"), Qt::NoModifier); + QTest::sendKeyEvent(QTest::Release, view.focusProxy(), Qt::Key_Return, QString("\r"), Qt::NoModifier); + }); } + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 2, 60000); + QCOMPARE(page.acceptNavigationRequestCalls, shouldAccept ? 1 : 0); +} + +class CustomScheme : public QWebEngineUrlSchemeHandler +{ +public: + CustomScheme(const QString &linkUrl) : m_linkUrl(linkUrl) { } + + void requestStarted(QWebEngineUrlRequestJob *requestJob) override + { + QString html = QString("<html><body>" + "<p style='height: 2000px;'>" + "<a href='%1' id='link'>Click link</a>" + "</p><p id='anchor'>Anchor</p>" + "</body></html>") + .arg(m_linkUrl); + QBuffer *buffer = new QBuffer(requestJob); + buffer->setData(html.toUtf8()); + requestJob->reply("text/html", buffer); + } + + QString m_linkUrl; +}; + +void tst_Schemes::customSchemeFragmentNavigation_data() +{ + QTest::addColumn<QUrl>("baseUrl"); + QTest::addColumn<QString>("linkUrl"); + QTest::addColumn<QUrl>("expectedUrl"); + + // Path syntax + // - Preserves each part of the URL after navigation + QTest::newRow("Path syntax, path only, relative url") + << QUrl("path://path") << "#anchor" << QUrl("path://path#anchor"); + QTest::newRow("Path syntax, path only, absolute url") + << QUrl("path://path") << "path://path#anchor" << QUrl("path://path#anchor"); + QTest::newRow("Path syntax, host/path, relative url") + << QUrl("path://host/path") << "#anchor" << QUrl("path://host/path#anchor"); + QTest::newRow("Path syntax, host/path, absolute url") + << QUrl("path://host/path") << "path://host/path#anchor" + << QUrl("path://host/path#anchor"); + QTest::newRow("Path syntax, host:port, relative url") + << QUrl("path://host:3000") << "#anchor" << QUrl("path://host:3000#anchor"); + QTest::newRow("Path syntax, host:port, absolute url") + << QUrl("path://host:3000") << "path://host:3000#anchor" + << QUrl("path://host:3000#anchor"); + QTest::newRow("Path syntax, userinfo@host:port, relative url") + << QUrl("path://user:password@host:3000") << "#anchor" + << QUrl("path://user:password@host:3000#anchor"); + QTest::newRow("Path syntax, userinfo@host:port, absolute url") + << QUrl("path://user:password@host:3000") << "path://user:password@host:3000#anchor" + << QUrl("path://user:password@host:3000#anchor"); + + // Host syntax + // - We lose the port and the user info from the authority after navigation + QTest::newRow("Host syntax, host only, relative url") + << QUrl("host://host") << "#anchor" << QUrl("host://host/#anchor"); + QTest::newRow("Host syntax, host only, absolute url") + << QUrl("host://host") << "host://host#anchor" << QUrl("host://host/#anchor"); + QTest::newRow("Host syntax, host/path, relative url") + << QUrl("host://host/path") << "#anchor" << QUrl("host://host/path#anchor"); + QTest::newRow("Host syntax, host/path, absolute url") + << QUrl("host://host/path") << "host://host/path#anchor" + << QUrl("host://host/path#anchor"); + QTest::newRow("Host syntax, host:port, relative url") + << QUrl("host://host:3000") << "#anchor" << QUrl("host://host/#anchor"); + QTest::newRow("Host syntax, host:port, absolute url") + << QUrl("host://host:3000") << "host://host:3000#anchor" << QUrl("host://host/#anchor"); + QTest::newRow("Host syntax, userinfo@host:port, relative url") + << QUrl("host://user:password@host:3000") << "#anchor" << QUrl("host://host/#anchor"); + QTest::newRow("Host syntax, userinfo@host:port, absolute url") + << QUrl("host://user:password@host:3000") << "host://user:password@host:3000#anchor" + << QUrl("host://host/#anchor"); + + // HostAndPort syntax + // - We lose the port and the user info from the authority after navigation + QTest::newRow("HostAndPort syntax, host only, relative url") + << QUrl("hostandport://host") << "#anchor" << QUrl("hostandport://host/#anchor"); + QTest::newRow("HostAndPort syntax, host only, absolute url") + << QUrl("hostandport://host") << "hostandport://host#anchor" + << QUrl("hostandport://host/#anchor"); + QTest::newRow("HostAndPort syntax, host/path, relative url") + << QUrl("hostandport://host/path") << "#anchor" + << QUrl("hostandport://host/path#anchor"); + QTest::newRow("HostAndPort syntax, host/path, absolute url") + << QUrl("hostandport://host/path") << "hostandport://host/path#anchor" + << QUrl("hostandport://host/path#anchor"); + QTest::newRow("HostAndPort syntax, host:port, relative url") + << QUrl("hostandport://host:3000") << "#anchor" << QUrl("hostandport://host/#anchor"); + QTest::newRow("HostAndPort syntax, host:port, absolute url") + << QUrl("hostandport://host:3000") << "hostandport://host:3000#anchor" + << QUrl("hostandport://host/#anchor"); + QTest::newRow("HostAndPort syntax, userinfo@host:port, relative url") + << QUrl("hostandport://user:password@host:3000") << "#anchor" + << QUrl("hostandport://host/#anchor"); + QTest::newRow("HostAndPort syntax, userinfo@host:port, absolute url") + << QUrl("hostandport://user:password@host:3000") + << "hostandport://user:password@host:3000#anchor" << QUrl("hostandport://host/#anchor"); + + // HostPortAndUserInformation syntax + // - We lose the port and it preserves the user info in the authority after navigation + QTest::newRow("HostPortAndUserInformation syntax, host only, relative url") + << QUrl("hostportuserinfo://host") << "#anchor" + << QUrl("hostportuserinfo://host/#anchor"); + QTest::newRow("HostPortAndUserInformation syntax, host only, absolute url") + << QUrl("hostportuserinfo://host") << "hostportuserinfo://host#anchor" + << QUrl("hostportuserinfo://host/#anchor"); + QTest::newRow("HostPortAndUserInformation syntax, host/path, relative url") + << QUrl("hostportuserinfo://host/path") << "#anchor" + << QUrl("hostportuserinfo://host/path#anchor"); + QTest::newRow("HostPortAndUserInformation syntax, host/path, absolute url") + << QUrl("hostportuserinfo://host/path") << "hostportuserinfo://host/path#anchor" + << QUrl("hostportuserinfo://host/path#anchor"); + QTest::newRow("HostPortAndUserInformation syntax, host:port, relative url") + << QUrl("hostportuserinfo://host:3000") << "#anchor" + << QUrl("hostportuserinfo://host/#anchor"); + QTest::newRow("HostPortAndUserInformation syntax, host:port, absolute url") + << QUrl("hostportuserinfo://host:3000") << "hostportuserinfo://host:3000#anchor" + << QUrl("hostportuserinfo://host/#anchor"); + QTest::newRow("HostPortAndUserInformation syntax, userinfo@host:port, relative url") + << QUrl("hostportuserinfo://user:password@host:3000") << "#anchor" + << QUrl("hostportuserinfo://user:password@host/#anchor"); + QTest::newRow("HostPortAndUserInformation syntax, userinfo@host:port, absolute url") + << QUrl("hostportuserinfo://user:password@host:3000") + << "hostportuserinfo://user:password@host:3000#anchor" + << QUrl("hostportuserinfo://user:password@host/#anchor"); +} + +void tst_Schemes::customSchemeFragmentNavigation() +{ + QFETCH(QUrl, baseUrl); + QFETCH(QUrl, expectedUrl); + QFETCH(QString, linkUrl); + + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QWebEngineView view; + view.setPage(&page); + view.resize(800, 600); + view.show(); + QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + QSignalSpy urlChangedSpy(&page, SIGNAL(urlChanged(QUrl))); + + CustomScheme *schemeHandler = new CustomScheme(linkUrl); + page.profile()->installUrlSchemeHandler(baseUrl.scheme().toUtf8(), schemeHandler); + + view.load(baseUrl); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "window.scrollY").toInt() == 0); + + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, elementCenter(&page, "link")); + QVERIFY(urlChangedSpy.wait()); + QCOMPARE(page.url(), expectedUrl); + QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "window.scrollY").toInt() > 0); + + // Same document navigation doesn't emit loadFinished + QTRY_COMPARE(loadFinishedSpy.size(), 1); } QTEST_MAIN(tst_Schemes) diff --git a/tests/auto/widgets/shutdown/CMakeLists.txt b/tests/auto/widgets/shutdown/CMakeLists.txt new file mode 100644 index 000000000..e2ce9eeb9 --- /dev/null +++ b/tests/auto/widgets/shutdown/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_test(tst_shutdown + SOURCES + tst_shutdown.cpp + LIBRARIES + Qt::WebEngineWidgets +) diff --git a/tests/auto/widgets/shutdown/shutdown.pro b/tests/auto/widgets/shutdown/shutdown.pro deleted file mode 100644 index e99c7f493..000000000 --- a/tests/auto/widgets/shutdown/shutdown.pro +++ /dev/null @@ -1 +0,0 @@ -include(../tests.pri) diff --git a/tests/auto/widgets/shutdown/tst_shutdown.cpp b/tests/auto/widgets/shutdown/tst_shutdown.cpp index 5c1e426d2..c2b31bb80 100644 --- a/tests/auto/widgets/shutdown/tst_shutdown.cpp +++ b/tests/auto/widgets/shutdown/tst_shutdown.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <QtTest/QtTest> diff --git a/tests/auto/widgets/spellchecking/CMakeLists.txt b/tests/auto/widgets/spellchecking/CMakeLists.txt new file mode 100644 index 000000000..d0c7656c1 --- /dev/null +++ b/tests/auto/widgets/spellchecking/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../util/util.cmake) +include(../../../../src/core/api/Qt6WebEngineCoreMacros.cmake) + +qt_internal_add_test(tst_spellchecking + SOURCES + tst_spellchecking.cpp + LIBRARIES + Qt::WebEngineWidgets + Test::Util +) + +qt_internal_add_resource(tst_spellchecking "tst_spellchecking" + PREFIX + "/" + FILES + "resources/index.html" +) + +file(GLOB_RECURSE dicts + ABSOLUTE ${CMAKE_CURRENT_LIST_DIR}/dict + *.dic +) + +foreach(dictFile ${dicts}) + qt_add_webengine_dictionary( + TARGET tst_spellchecking + SOURCE "${dictFile}" + OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) +endforeach() diff --git a/tests/auto/widgets/spellchecking/spellchecking.pro b/tests/auto/widgets/spellchecking/spellchecking.pro deleted file mode 100644 index a36c82e20..000000000 --- a/tests/auto/widgets/spellchecking/spellchecking.pro +++ /dev/null @@ -1,24 +0,0 @@ -include(../tests.pri) - -DISTFILES += \ - dict/en-US.dic \ - dict/en-US.aff \ - dict/de-DE.dic \ - dict/de-DE.aff \ - -qtPrepareTool(CONVERT_TOOL, qwebengine_convert_dict) - -debug_and_release { - CONFIG(debug, debug|release): DICTIONARIES_DIR = debug/qtwebengine_dictionaries - else: DICTIONARIES_DIR = release/qtwebengine_dictionaries -} else { - DICTIONARIES_DIR = qtwebengine_dictionaries -} - -dict.files = $$PWD/dict/en-US.dic $$PWD/dict/de-DE.dic -dictoolbuild.input = dict.files -dictoolbuild.output = $${DICTIONARIES_DIR}/${QMAKE_FILE_BASE}.bdic -dictoolbuild.commands = $${CONVERT_TOOL} ${QMAKE_FILE_IN} ${QMAKE_FILE_OUT} -dictoolbuild.name = Build ${QMAKE_FILE_IN_BASE} -dictoolbuild.CONFIG = no_link target_predeps -QMAKE_EXTRA_COMPILERS += dictoolbuild diff --git a/tests/auto/widgets/spellchecking/tst_spellchecking.cpp b/tests/auto/widgets/spellchecking/tst_spellchecking.cpp index 64df05d89..c643a56ba 100644 --- a/tests/auto/widgets/spellchecking/tst_spellchecking.cpp +++ b/tests/auto/widgets/spellchecking/tst_spellchecking.cpp @@ -1,38 +1,13 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "util.h" +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <util.h> #include <QtTest/QtTest> -#include <QtWebEngineWidgets/qwebenginecontextmenudata.h> -#include <QtWebEngineWidgets/qwebengineprofile.h> -#include <QtWebEngineWidgets/qwebenginepage.h> +#include <QtWebEngineCore/qwebenginecontextmenurequest.h> +#include <QtWebEngineCore/qwebenginepage.h> +#include <QtWebEngineCore/qwebengineprofile.h> +#include <QtWebEngineCore/qwebenginesettings.h> #include <QtWebEngineWidgets/qwebengineview.h> -#include <qwebenginesettings.h> class WebView : public QWebEngineView { @@ -41,28 +16,25 @@ public: void activateMenu(QWidget *widget, const QPoint &position) { QTest::mouseMove(widget, position); - QTest::mousePress(widget, Qt::RightButton, 0, position); + QTest::mousePress(widget, Qt::RightButton, {}, position); QContextMenuEvent evcont(QContextMenuEvent::Mouse, position, mapToGlobal(position)); event(&evcont); - QTest::mouseRelease(widget, Qt::RightButton, 0, position); + QTest::mouseRelease(widget, Qt::RightButton, {}, position); } - const QWebEngineContextMenuData& data() - { - return m_data; - } + QWebEngineContextMenuRequest *data() { return m_data; } signals: void menuReady(); protected: - void contextMenuEvent(QContextMenuEvent *) + void contextMenuEvent(QContextMenuEvent *) override { - m_data = page()->contextMenuData(); + m_data = lastContextMenuRequest(); emit menuReady(); } private: - QWebEngineContextMenuData m_data; + QWebEngineContextMenuRequest *m_data; }; class tst_Spellchecking : public QObject @@ -173,19 +145,20 @@ void tst_Spellchecking::spellcheck() 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>()); + QTRY_VERIFY(m_view->focusWidget()); //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)); + QTest::mousePress(m_view->focusWidget(), Qt::LeftButton, {}, QPoint(20,20)); + QTest::mouseRelease(m_view->focusWidget(), Qt::LeftButton, {}, QPoint(20,20)); QString text("I lowe Qt ...."); - for (int i = 0; i < text.length(); i++) { + for (int i = 0; i < text.size(); i++) { QTest::keyClicks(m_view->focusWidget(), text.at(i)); QTest::qWait(60); } // make sure text is there QString result = evaluateJavaScriptSync(m_view->page(), "text();").toString(); - QVERIFY(result == text); + QCOMPARE(result, text); bool gotMisspelledWord = false; // clumsy QTRY_VERIFY still execs expr after first success QString detail; @@ -204,17 +177,17 @@ void tst_Spellchecking::spellcheck() return false; } - if (!m_view->data().isValid()) { + if (!m_view->data()) { detail = "invalid data"; return false; } - if (!m_view->data().isContentEditable()) { + if (!m_view->data()->isContentEditable()) { detail = "content is not editable"; return false; } - if (m_view->data().misspelledWord().isEmpty()) { + if (m_view->data()->misspelledWord().isEmpty()) { detail = "no misspelled word"; return false; }; @@ -224,10 +197,10 @@ void tst_Spellchecking::spellcheck() } (), qPrintable(QString("Context menu: %1").arg(detail))); // check misspelled word - QCOMPARE(m_view->data().misspelledWord(), QStringLiteral("lowe")); + QCOMPARE(m_view->data()->misspelledWord(), QStringLiteral("lowe")); // check suggestions - QCOMPARE(m_view->data().spellCheckerSuggestions(), suggestions); + QCOMPARE(m_view->data()->spellCheckerSuggestions(), suggestions); // check replace word m_view->page()->replaceMisspelledWord("love"); diff --git a/tests/auto/widgets/spellchecking/tst_spellchecking.qrc b/tests/auto/widgets/spellchecking/tst_spellchecking.qrc deleted file mode 100644 index 505b932c7..000000000 --- a/tests/auto/widgets/spellchecking/tst_spellchecking.qrc +++ /dev/null @@ -1,5 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>resources/index.html</file> - </qresource> -</RCC> diff --git a/tests/auto/widgets/tests.pri b/tests/auto/widgets/tests.pri deleted file mode 100644 index 97954aedc..000000000 --- a/tests/auto/widgets/tests.pri +++ /dev/null @@ -1,22 +0,0 @@ -include($$QTWEBENGINE_OUT_ROOT/src/core/qtwebenginecore-config.pri) # workaround for QTBUG-68093 -QT_FOR_CONFIG += webenginecore-private - -TEMPLATE = app - -CONFIG += testcase -CONFIG += c++14 - -VPATH += $$_PRO_FILE_PWD_ -TARGET = tst_$$TARGET - -SOURCES += $${TARGET}.cpp -INCLUDEPATH += $$PWD - -exists($$_PRO_FILE_PWD_/$${TARGET}.qrc): RESOURCES += $${TARGET}.qrc - -QT += testlib network webenginewidgets widgets quick quickwidgets - -# This define is used by some tests to look up resources in the source tree -DEFINES += TESTS_SOURCE_DIR=\\\"$$PWD/\\\" - -include(../embed_info_plist.pri) diff --git a/tests/auto/widgets/touchinput/CMakeLists.txt b/tests/auto/widgets/touchinput/CMakeLists.txt new file mode 100644 index 000000000..bd76666d6 --- /dev/null +++ b/tests/auto/widgets/touchinput/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include(../../util/util.cmake) + +qt_internal_add_test(tst_touchinput + SOURCES + tst_touchinput.cpp + LIBRARIES + Qt::WebEngineWidgets + Qt::GuiPrivate + Test::Util +) diff --git a/tests/auto/widgets/touchinput/tst_touchinput.cpp b/tests/auto/widgets/touchinput/tst_touchinput.cpp new file mode 100644 index 000000000..42178558c --- /dev/null +++ b/tests/auto/widgets/touchinput/tst_touchinput.cpp @@ -0,0 +1,372 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <util.h> + +#include <QtGui/qpa/qwindowsysteminterface.h> +#include <QSignalSpy> +#include <QTest> +#include <QPointingDevice> +#include <QWebEngineSettings> +#include <QWebEngineView> + +static QPointingDevice* s_touchDevice = nullptr; + +struct Page : QWebEnginePage +{ + QStringList alerts; + void javaScriptAlert(const QUrl &/*origin*/, const QString &msg) override { alerts.append(msg); } +}; + +class TouchInputTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + +private Q_SLOTS: + void touchTap(); + void touchTapAndHold(); + void touchTapAndHoldCancelled(); + void scrolling(); + void pinchZoom_data(); + void pinchZoom(); + void complexSequence(); + void buttonClickHandler(); + void htmlSelectPopup(); + +private: + Page page; + QWebEngineView view; + QSignalSpy loadSpy { &view, &QWebEngineView::loadFinished }; + QPoint notextCenter, textCenter, inputCenter; + + QString activeElement() { return evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(); } + + void makeTouch(QWindow *w, const QPoint &p) { + QTest::touchEvent(w, s_touchDevice).press(1, p); + QTest::touchEvent(w, s_touchDevice).release(1, p); + } + void makeTouch(const QPoint &p) { makeTouch(view.windowHandle(), p); } + + void gestureScroll(bool down) { + auto target = view.focusProxy(); + QPoint p(target->width() / 2, target->height() / 4 * (down ? 3 : 1)); + + QTest::touchEvent(target, s_touchDevice).press(1, p, target); + + QSignalSpy spy(view.page(), &QWebEnginePage::scrollPositionChanged); + for (int i = 0; i < 3; ++i) { + down ? p -= QPoint(5, 15) : p += QPoint(5, 15); + QTest::qWait(100); // too fast and events are recognized as fling gesture + QTest::touchEvent(target, s_touchDevice).move(1, p, target); + spy.wait(); + } + + QTest::touchEvent(target, s_touchDevice).release(1, p, target); + } + + void gesturePinch(bool zoomIn, bool tapOneByOne = false) { + auto target = view.focusProxy(); + QPoint p(target->width() / 2, target->height() / 2); + auto t1 = p - QPoint(zoomIn ? 50 : 150, 10), t2 = p + QPoint(zoomIn ? 50 : 150, 10); + + if (tapOneByOne) { + QTest::touchEvent(target, s_touchDevice).press(0, t1, target); + QTest::touchEvent(target, s_touchDevice).stationary(0).press(1, t2, target); + } else { + QTest::touchEvent(target, s_touchDevice).press(0, t1, target).press(1, t2, target); + } + + for (int i = 0; i < 3; ++i) { + if (zoomIn) { + t1 -= QPoint(25, 5); + t2 += QPoint(25, 5); + } else { + t1 += QPoint(35, 5); + t2 -= QPoint(35, 5); + } + QTest::qWait(100); // too fast and events are recognized as fling gesture + QTest::touchEvent(target, s_touchDevice).move(1, t1, target).move(0, t2, target); + } + + if (tapOneByOne) { + QTest::touchEvent(target, s_touchDevice).stationary(0).release(1, t2, target); + QTest::touchEvent(target, s_touchDevice).release(0, t1, target); + } else { + QTest::touchEvent(target, s_touchDevice).release(0, t1, target).release(1, t2, target); + } + } + + int getScrollPosition(int *position = nullptr) { + int p = evaluateJavaScriptSync(view.page(), "window.scrollY").toInt(); + return position ? (*position = p) : p; + } + + int pageScrollPosition() { + // this one is updated later in page in asynchronous way + return qRound(view.page()->scrollPosition().y()); + } + + double getScaleFactor(double *scale = nullptr) { + double s = evaluateJavaScriptSync(view.page(), "window.visualViewport.scale").toDouble(); + return scale ? (*scale = s) : s; + } +}; + +void TouchInputTest::initTestCase() +{ + s_touchDevice = QTest::createTouchDevice(); + + view.setPage(&page); + view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false); + + view.show(); view.resize(480, 320); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + view.setHtml("<html><head><style>.rect { min-width: 240px; min-height: 120px; }</style></head><body>" + "<p id='text' style='width: 150px;'>The Qt Company</p>" + "<div id='notext' style='width: 150px; height: 100px; background-color: #f00;'></div>" + "<form><input id='input' style='width: 150px;' type='text' value='The Qt Company2' /></form>" + "<button id='btn' type='button' onclick='alert(\"button clicked!\")'>Click Me!</button>" + "<select id='select' onchange='alert(\"option changed to: \" + this.value)'>" + "<option value='O1'>O1</option><option value='O2'>O2</option><option value='O3'>O3</option></select>" + "<table style='width: 100%; padding: 15px; text-align: center;'>" + "<tr><td>BEFORE</td><td><div class='rect' style='background-color: #00f;'></div></td><td>AFTER</td></tr>" + "<tr><td>BEFORE</td><td><div class='rect' style='background-color: #0f0;'></div></td><td>AFTER</td></tr>" + "<tr><td>BEFORE</td><td><div class='rect' style='background-color: #f00;'></div></td><td>AFTER</td></tr></table>" + "</body></html>"); + QVERIFY(loadSpy.wait() && loadSpy.first().first().toBool()); + + notextCenter = elementCenter(view.page(), "notext"); + textCenter = elementCenter(view.page(), "text"); + inputCenter = elementCenter(view.page(), "input"); +} + +void TouchInputTest::init() +{ + QCOMPARE(activeElement(), QString()); +} + +void TouchInputTest::cleanup() +{ + evaluateJavaScriptSync(view.page(), "if (document.activeElement) document.activeElement.blur()"); + evaluateJavaScriptSync(view.page(), "window.scrollTo(0, 0)"); + QTRY_COMPARE(getScrollPosition(), 0); + QTRY_COMPARE(pageScrollPosition(), 0); + page.alerts.clear(); +} + +void TouchInputTest::touchTap() +{ + auto singleTap = [target = view.focusProxy()] (const QPoint& tapCoords) -> void { + QTest::touchEvent(target, s_touchDevice).press(1, tapCoords, target); + QTest::touchEvent(target, s_touchDevice).stationary(1); + QTest::touchEvent(target, s_touchDevice).release(1, tapCoords, target); + }; + + // Single tap on text doesn't trigger a selection + singleTap(textCenter); + QTRY_COMPARE(activeElement(), QString()); + QTRY_VERIFY(!view.hasSelection()); + + // Single tap inside the input field focuses it without selecting the text + singleTap(inputCenter); + QTRY_COMPARE(activeElement(), QStringLiteral("input")); + QTRY_VERIFY(!view.hasSelection()); + + // Single tap on the div clears the input field focus + singleTap(notextCenter); + QTRY_COMPARE(activeElement(), QString()); + + // Double tap on text still doesn't trigger a selection + singleTap(textCenter); + singleTap(textCenter); + QTRY_COMPARE(activeElement(), QString()); + QTRY_VERIFY(!view.hasSelection()); + + // Double tap inside the input field focuses it and selects the word under it + singleTap(inputCenter); + singleTap(inputCenter); + QTRY_COMPARE(activeElement(), QStringLiteral("input")); + QTRY_COMPARE(view.selectedText(), QStringLiteral("Company2")); + + // Double tap outside the input field behaves like a single tap: clears its focus and selection + singleTap(notextCenter); + singleTap(notextCenter); + QTRY_COMPARE(activeElement(), QString()); + QTRY_VERIFY(!view.hasSelection()); +} + +void TouchInputTest::touchTapAndHold() +{ + auto tapAndHold = [target = view.focusProxy()] (const QPoint& tapCoords) -> void { + QTest::touchEvent(target, s_touchDevice).press(1, tapCoords, target); + QTest::touchEvent(target, s_touchDevice).stationary(1); + QTest::qWait(1000); + QTest::touchEvent(target, s_touchDevice).release(1, tapCoords, target); + }; + + // Tap-and-hold on text selects the word under it + tapAndHold(textCenter); + QTRY_COMPARE(activeElement(), QString()); + QTRY_COMPARE(view.selectedText(), QStringLiteral("Company")); + + // Tap-and-hold inside the input field focuses it and selects the word under it + tapAndHold(inputCenter); + QTRY_COMPARE(activeElement(), QStringLiteral("input")); + QTRY_COMPARE(view.selectedText(), QStringLiteral("Company2")); + + // Only test the page context menu on Windows, as Linux doesn't handle context menus consistently + // and other non-desktop platforms like Android may not even support context menus at all +#if defined(Q_OS_WIN) + // Tap-and-hold clears the text selection and shows the page's context menu + QVERIFY(QApplication::activePopupWidget() == nullptr); + tapAndHold(notextCenter); + QTRY_COMPARE(activeElement(), QString()); + QTRY_VERIFY(!view.hasSelection()); + QTRY_VERIFY(QApplication::activePopupWidget() != nullptr); + + QApplication::activePopupWidget()->close(); + QVERIFY(QApplication::activePopupWidget() == nullptr); +#endif +} + +void TouchInputTest::touchTapAndHoldCancelled() +{ + auto cancelledTapAndHold = [target = view.focusProxy()] (const QPoint& tapCoords) -> void { + QTest::touchEvent(target, s_touchDevice).press(1, tapCoords, target); + QTest::touchEvent(target, s_touchDevice).stationary(1); + QTest::qWait(1000); + QWindowSystemInterface::handleTouchCancelEvent(target->windowHandle(), s_touchDevice); + }; + + // A cancelled tap-and-hold should cancel text selection, but currently doesn't + cancelledTapAndHold(textCenter); + QEXPECT_FAIL("", "Incorrect Chromium selection behavior when cancelling tap-and-hold on text", Continue); + QTRY_VERIFY_WITH_TIMEOUT(!view.hasSelection(), 100); + + // A cancelled tap-and-hold should cancel input field focusing and selection, but currently doesn't + cancelledTapAndHold(inputCenter); + QEXPECT_FAIL("", "Incorrect Chromium selection behavior when cancelling tap-and-hold on input field", Continue); + QTRY_VERIFY_WITH_TIMEOUT(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty(), 100); + QEXPECT_FAIL("", "Incorrect Chromium focus behavior when cancelling tap-and-hold on input field", Continue); + QTRY_VERIFY_WITH_TIMEOUT(!view.hasSelection(), 100); + + // Only test the page context menu on Windows, as Linux doesn't handle context menus consistently + // and other non-desktop platforms like Android may not even support context menus at all +#if defined(Q_OS_WIN) + // A cancelled tap-and-hold cancels the context menu + QVERIFY(QApplication::activePopupWidget() == nullptr); + cancelledTapAndHold(notextCenter); + QVERIFY(QApplication::activePopupWidget() == nullptr); +#endif +} + +void TouchInputTest::scrolling() +{ + int p = getScrollPosition(); + QCOMPARE(p, 0); + + // scroll a bit down... + for (int i = 0; i < 3; ++i) { + gestureScroll(/* down = */true); + int positionBefore = p; + QTRY_VERIFY2(getScrollPosition(&p) > positionBefore, qPrintable(QString("i: %1, position: %2 -> %3").arg(i).arg(positionBefore).arg(p))); + } + + // ... and then scroll page again but in opposite direction + for (int i = 0; i < 3; ++i) { + gestureScroll(/* down = */false); + int positionBefore = p; + QTRY_VERIFY2(getScrollPosition(&p) < positionBefore, qPrintable(QString("i: %1, position: %2 -> %3").arg(i).arg(positionBefore).arg(p))); + } + + QTRY_COMPARE(getScrollPosition(), 0); +} + +void TouchInputTest::pinchZoom_data() +{ + QTest::addColumn<bool>("tapOneByOne"); + QTest::addRow("sequential") << true; + QTest::addRow("simultaneous") << false; +} + +void TouchInputTest::pinchZoom() +{ + QFETCH(bool, tapOneByOne); + double scale = getScaleFactor(); + QCOMPARE(scale, 1.0); + + for (int i = 0; i < 3; ++i) { + gesturePinch(/* zoomIn = */true, tapOneByOne); + QTRY_VERIFY2(getScaleFactor(&scale) > 1.0, qPrintable(QString("i: %1, scale: %2").arg(i).arg(scale))); + gesturePinch(/* zoomIn = */false, tapOneByOne); + QTRY_COMPARE(getScaleFactor(&scale), 1.0); + } +} + +void TouchInputTest::complexSequence() +{ + auto t = view.focusProxy(); + QPoint pc(view.width() / 2, view.height() / 2), p1 = pc - QPoint(50, 25), p2 = pc + QPoint(50, 25); + + for (int i = 0; i < 4; ++i) { + QTest::touchEvent(t, s_touchDevice).press(42, p1, t); QTest::qWait(50); + QTest::touchEvent(t, s_touchDevice).stationary(42).press(24, p2, t); QTest::qWait(50); + QTest::touchEvent(t, s_touchDevice).release(42, p1, t).release(24, p2, t); + + // for additional variablity add zooming in on even steps and zooming out on odd steps + // MEMO scroll position will always be 0 while viewport scale factor > 1.0, so do zoom in after scroll + bool zoomIn = i % 2 == 0; + + if (!zoomIn) { + gesturePinch(false); + QTRY_COMPARE(getScaleFactor(), 1.0); + } + + int p = getScrollPosition(), positionBefore = p; + gestureScroll(true); + QTRY_VERIFY2_WITH_TIMEOUT(getScrollPosition(&p) > positionBefore, qPrintable(QString("i: %1, position: %2 -> %3").arg(i).arg(positionBefore).arg(p)), 1000); + + if (zoomIn) { + double s = getScaleFactor(), scaleBefore = s; + gesturePinch(true); + QTRY_VERIFY2(getScaleFactor(&s) > scaleBefore, qPrintable(QString("i: %1, scale: %2").arg(i).arg(s))); + } + } +} + +void TouchInputTest::buttonClickHandler() +{ + auto buttonCenter = elementGeometry(view.page(), "btn").center(); + makeTouch(buttonCenter); + QTRY_VERIFY(!page.alerts.isEmpty()); + QCOMPARE(page.alerts.first(), "button clicked!"); + QCOMPARE(page.alerts.size(), 1); + QEXPECT_FAIL("", "Shouldn't trigger twice due to synthesized mouse events for touch", Continue); + QTRY_VERIFY_WITH_TIMEOUT(page.alerts.size() == 2, 500); +} + +void TouchInputTest::htmlSelectPopup() +{ + auto selectRect = elementGeometry(view.page(), "select"); + makeTouch(selectRect.center()); + QTRY_VERIFY(QApplication::activePopupWidget()); + QCOMPARE(activeElement(), QStringLiteral("select")); + + auto popup = QApplication::activePopupWidget(); + makeTouch(popup->windowHandle(), QPoint(popup->width() / 2, popup->height() / 2)); + QTRY_VERIFY(!QApplication::activePopupWidget()); + + QTRY_VERIFY(!page.alerts.isEmpty()); + QCOMPARE(page.alerts.first(), "option changed to: O2"); + QEXPECT_FAIL("", "Shouldn't trigger twice due to synthesized mouse events for touch", Continue); + QTRY_VERIFY_WITH_TIMEOUT(page.alerts.size() == 2, 500); +} + +QTEST_MAIN(TouchInputTest) +#include "tst_touchinput.moc" diff --git a/tests/auto/widgets/util.h b/tests/auto/widgets/util.h deleted file mode 100644 index f27466225..000000000 --- a/tests/auto/widgets/util.h +++ /dev/null @@ -1,178 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebEngine module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -// Functions and macros that really need to be in QTestLib - -#if 0 -#pragma qt_no_master_include -#endif - -#include <QEventLoop> -#include <QSignalSpy> -#include <QTimer> -#include <qwebenginepage.h> - -#if !defined(TESTS_SOURCE_DIR) -#define TESTS_SOURCE_DIR "" -#endif - -/** - * Just like QSignalSpy but facilitates sync and async - * signal emission. For example if you want to verify that - * page->foo() emitted a signal, it could be that the - * implementation decides to emit the signal asynchronously - * - in which case we want to spin a local event loop until - * emission - or that the call to foo() emits it right away. - */ -class SignalBarrier : private QSignalSpy -{ -public: - SignalBarrier(const QObject* obj, const char* aSignal) - : QSignalSpy(obj, aSignal) - { } - - bool ensureSignalEmitted() - { - bool result = count() > 0; - if (!result) - result = wait(); - clear(); - return result; - } -}; - -template<typename T, typename R> -struct CallbackWrapper { - QPointer<R> p; - void operator()(const T& result) { - if (p) - (*p)(result); - } -}; - -template<typename T> -class CallbackSpy: public QObject { -public: - CallbackSpy() : called(false) { - timeoutTimer.setSingleShot(true); - QObject::connect(&timeoutTimer, SIGNAL(timeout()), &eventLoop, SLOT(quit())); - } - - T waitForResult() { - if (!called) { - timeoutTimer.start(10000); - eventLoop.exec(); - } - return result; - } - - bool wasCalled() const { - return called; - } - - void operator()(const T &result) { - this->result = result; - called = true; - eventLoop.quit(); - } - - CallbackWrapper<T, CallbackSpy<T> > ref() - { - CallbackWrapper<T, CallbackSpy<T> > wrapper = {this}; - return wrapper; - } - -private: - Q_DISABLE_COPY(CallbackSpy) - bool called; - QTimer timeoutTimer; - QEventLoop eventLoop; - T result; -}; - -static inline QString toPlainTextSync(QWebEnginePage *page) -{ - CallbackSpy<QString> spy; - page->toPlainText(spy.ref()); - return spy.waitForResult(); -} - -static inline QString toHtmlSync(QWebEnginePage *page) -{ - CallbackSpy<QString> spy; - page->toHtml(spy.ref()); - return spy.waitForResult(); -} - -static inline bool findTextSync(QWebEnginePage *page, const QString &subString) -{ - CallbackSpy<bool> spy; - page->findText(subString, 0, spy.ref()); - return spy.waitForResult(); -} - -static inline QVariant evaluateJavaScriptSync(QWebEnginePage *page, const QString &script) -{ - CallbackSpy<QVariant> spy; - page->runJavaScript(script, spy.ref()); - return spy.waitForResult(); -} - -static inline QVariant evaluateJavaScriptSyncInWorld(QWebEnginePage *page, const QString &script, int worldId) -{ - CallbackSpy<QVariant> spy; - page->runJavaScript(script, worldId, spy.ref()); - return spy.waitForResult(); -} - -static inline QUrl baseUrlSync(QWebEnginePage *page) -{ - CallbackSpy<QVariant> spy; - page->runJavaScript("document.baseURI", spy.ref()); - return spy.waitForResult().toUrl(); -} - -#define W_QSKIP(a, b) QSKIP(a) - -#define W_QTEST_MAIN(TestObject, params) \ -int main(int argc, char *argv[]) \ -{ \ - QVector<const char *> w_argv(argc); \ - for (int i = 0; i < argc; ++i) \ - w_argv[i] = argv[i]; \ - for (int i = 0; i < params.size(); ++i) \ - w_argv.append(params[i].data()); \ - int w_argc = w_argv.size(); \ - \ - QApplication app(w_argc, const_cast<char **>(w_argv.data())); \ - app.setAttribute(Qt::AA_Use96Dpi, true); \ - QTEST_DISABLE_KEYPAD_NAVIGATION \ - TestObject tc; \ - QTEST_SET_MAIN_SOURCE_PATH \ - return QTest::qExec(&tc, argc, argv); \ -} diff --git a/tests/auto/widgets/widgets.pro b/tests/auto/widgets/widgets.pro deleted file mode 100644 index df553df55..000000000 --- a/tests/auto/widgets/widgets.pro +++ /dev/null @@ -1,48 +0,0 @@ -include($$QTWEBENGINE_OUT_ROOT/src/core/qtwebenginecore-config.pri) # workaround for QTBUG-68093 -QT_FOR_CONFIG += webenginecore webenginecore-private - -TEMPLATE = subdirs - -SUBDIRS += \ - certificateerror \ - defaultsurfaceformat \ - devtools \ - faviconmanager \ - loadsignals \ - offscreen \ - origins \ - proxy \ - proxypac \ - schemes \ - shutdown \ - qwebenginedownloaditem \ - qwebenginepage \ - qwebenginehistory \ - qwebengineprofile \ - qwebenginescript \ - qwebenginesettings \ - qwebengineview - -qtConfig(accessibility) { - SUBDIRS += accessibility -} - -qtConfig(webengine-printing-and-pdf) { - SUBDIRS += printing -} - -qtConfig(webengine-spellchecker):!cross_compile { - !qtConfig(webengine-native-spellchecker) { - SUBDIRS += spellchecking - } else { - message("Spellcheck test will not be built because it depends on usage of Hunspell dictionaries.") - } -} - -# QTBUG-60268 -boot2qt: SUBDIRS -= accessibility defaultsurfaceformat devtools \ - qwebenginepage \ - qwebengineprofile \ - qwebengineview - -win32: SUBDIRS -= offscreen |