diff options
Diffstat (limited to 'tests/auto/widgets')
133 files changed, 5841 insertions, 4937 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 748837c7f..1579b61e2 100644 --- a/tests/auto/widgets/accessibility/tst_accessibility.cpp +++ b/tests/auto/widgets/accessibility/tst_accessibility.cpp @@ -17,8 +17,9 @@ 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> @@ -48,6 +49,9 @@ private Q_SLOTS: void value(); void roles_data(); void roles(); + void objectName(); + void crossTreeParent(); + void tableCellInterface(); }; // This will be called before the first test function is executed. @@ -77,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); @@ -104,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); @@ -151,17 +154,17 @@ void tst_Accessibility::hierarchy() void tst_Accessibility::focusChild_data() { QTest::addColumn<QString>("interfaceName"); - QTest::addColumn<QVector<QAccessible::Role>>("ancestorRoles"); + QTest::addColumn<QList<QAccessible::Role>>("ancestorRoles"); - QTest::newRow("QWebEngineView") << QString("QWebEngineView") << QVector<QAccessible::Role>({QAccessible::Client}); - QTest::newRow("RenderWidgetHostViewQtDelegate") << QString("RenderWidgetHostViewQtDelegate") << QVector<QAccessible::Role>({QAccessible::Client}); - QTest::newRow("QMainWindow") << QString("QMainWindow") << QVector<QAccessible::Role>({QAccessible::Window, QAccessible::Client /* central widget */, QAccessible::Client /* view */}); + 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(QVector<QAccessible::Role>, ancestorRoles); + QFETCH(QList<QAccessible::Role>, ancestorRoles); for (int i = 0; i < ancestorRoles.size(); ++i) { if (iface->childCount() == 0 || iface->role() != ancestorRoles[i]) return nullptr; @@ -239,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); @@ -339,7 +342,7 @@ void tst_Accessibility::roles_data() 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::Link; + 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; @@ -361,7 +364,6 @@ void tst_Accessibility::roles_data() 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::kCommenSection") << QString("<div role='commentsection'></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; @@ -402,8 +404,10 @@ void tst_Accessibility::roles_data() 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; @@ -422,9 +426,9 @@ void tst_Accessibility::roles_data() 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::Footer; + 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></form>") << 0 << QAccessible::Form; + 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; @@ -462,10 +466,10 @@ void tst_Accessibility::roles_data() 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='menu'><input type='button' /></menu>") << 1 << QAccessible::MenuItem; + 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='group'><div role='menuitem'>a</div></menu>") << 1 << QAccessible::MenuItem; + 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; @@ -473,19 +477,18 @@ void tst_Accessibility::roles_data() 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::ComboBox; + 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::kRevision") << QString("<div role='revision'></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::StaticText; - QTest::newRow("ax::mojom::Role::kRubyAnnotation") << QString("<ruby><rt>a</rt></ruby>") << 2 << QAccessible::StaticText; + 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; @@ -499,7 +502,7 @@ void tst_Accessibility::roles_data() 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'></svg>") << 1 << QAccessible::Graphic; + 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 @@ -528,10 +531,10 @@ void tst_Accessibility::roles() 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); @@ -541,7 +544,7 @@ 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 *element = document->child(0); @@ -553,9 +556,116 @@ void tst_Accessibility::roles() 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" - << "--enable-features=AccessibilityExposeARIAAnnotations"; + << "--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 f11d9236c..000000000 --- a/tests/auto/widgets/certificateerror/tst_certificateerror.cpp +++ /dev/null @@ -1,124 +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) - , loadSpy(this, &QWebEnginePage::loadFinished) { - } - - bool deferError, acceptCertificate; - - QSignalSpy loadSpy; - 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->certificateChain(); - 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()); - QCOMPARE(page.loadSpy.count(), 0); - QCOMPARE(toPlainTextSync(&page), QString()); - - if (acceptCertificate) - page.error->ignoreCertificateError(); - else - page.error->rejectCertificate(); - - QVERIFY(page.error->answered()); - page.error.reset(); - } - QTRY_COMPARE_WITH_TIMEOUT(page.loadSpy.count(), 1, 30000); - QCOMPARE(page.loadSpy.takeFirst().value(0).toBool(), 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 3026b3931..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_WITH_TIMEOUT(spy.count(), 1, 12000); - - 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_WITH_TIMEOUT(spy.count(), 1, 12000); - - 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 46038cdc6..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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 30000); - 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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 30000); - 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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 30000); - 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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 30000); - 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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 30000); - 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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 30000); - 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, 30000); - 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, 30000); - 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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 30000); - 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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 30000); - 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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 30000); - 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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 30000); - 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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 30000); - 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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 30000); - 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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 30000); - 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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 30000); - 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_WITH_TIMEOUT(loadFinishedSpy.count(), 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.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 20e5fbf0d..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, - [&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 e3927f763..000000000 --- a/tests/auto/widgets/origins/tst_origins.cpp +++ /dev/null @@ -1,874 +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); - } - - { - QWebEngineUrlScheme scheme(QBAL("cors")); - 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); - profile->installUrlSchemeHandler(QBAL("cors"), 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 cleanupTestCase(); - void init(); - void cleanup(); - - 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 verifyLoad(const QUrl &url) - { - QSignalSpy spy(m_page, &QWebEnginePage::loadFinished); - m_page->load(url); - [&spy]() { QTRY_VERIFY_WITH_TIMEOUT(!spy.isEmpty(), 90000); }(); - return !spy.isEmpty() && 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_handler = new TstUrlSchemeHandler(&m_profile); -} - -void tst_Origins::cleanupTestCase() -{ - QVERIFY(!m_page); - delete m_handler; -} - -void tst_Origins::init() -{ - m_page = new QWebEnginePage(&m_profile, nullptr); -} - -void tst_Origins::cleanup() -{ - delete m_page; - m_page = nullptr; - m_handler->requests().clear(); -} - -// 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(verifyLoad(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(verifyLoad(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(verifyLoad(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(verifyLoad(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(verifyLoad(QSL("qrc:/resources/subdir/index.html"))); - QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); - QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); - - QVERIFY(verifyLoad(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); - - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - QVERIFY(verifyLoad(QSL("file:" THIS_DIR "resources/subdir/index.html"))); - QCOMPARE(eval(QSL("msg[0]")), QVariant()); - QCOMPARE(eval(QSL("msg[1]")), QVariant()); - - QVERIFY(verifyLoad(QSL("qrc:/resources/subdir/index.html"))); - QCOMPARE(eval(QSL("msg[0]")), QVariant(QSL("hello"))); - QCOMPARE(eval(QSL("msg[1]")), QVariant(QSL("world"))); - - QVERIFY(verifyLoad(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(verifyLoad(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"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - - QVERIFY(verifyLoad(QSL("qrc:/resources/mixedSchemes.html"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - 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"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - - QVERIFY(verifyLoad(QSL("tst:/resources/mixedSchemes.html"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Not allowed to load local resource"))); - eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - 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(verifyLoad(QSL("PathSyntax:/resources/mixedSchemes.html"))); - eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Not allowed to load local resource"))); - 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')")); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - - QVERIFY(verifyLoad(QSL("PathSyntax-LocalAccessAllowed:/resources/mixedSchemes.html"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - 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"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - - QVERIFY(verifyLoad(QSL("PathSyntax-NoAccessAllowed:/resources/mixedSchemes.html"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Not allowed to load local resource"))); - eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - - QVERIFY(verifyLoad(QSL("HostSyntax://a/resources/mixedSchemes.html"))); - eval(QSL("setIFrameUrl('HostSyntax://a/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - 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(verifyLoad(QSL("HostSyntax://a/resources/mixedSchemesWithCsp.html"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("violates the following Content Security Policy"))); - eval(QSL("setIFrameUrl('HostSyntax://a/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("violates the following Content Security Policy"))); - eval(QSL("setIFrameUrl('HostSyntax://b/resources/mixedSchemes_frame.html')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); - - QVERIFY(verifyLoad(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"))); - QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); - 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. -// -// Cross-origin XMLHttpRequests can only be made to CORS-enabled schemes. These -// include the builtin schemes http, https, data, and chrome, as well as custom -// schemes with the CorsEnabled flag. -void tst_Origins::mixedXHR() -{ - QVERIFY(verifyLoad(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"))); - eval(QSL("sendXHR('cors:/resources/mixedXHR.txt')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); - - QVERIFY(verifyLoad(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"))); - eval(QSL("sendXHR('cors:/resources/mixedXHR.txt')")); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); - - QVERIFY(verifyLoad(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"))); - eval(QSL("sendXHR('cors:/resources/mixedXHR.txt')")); - 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(verifyLoad(QSL("file:" THIS_DIR "resources/websocket.html"))); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); - - QVERIFY(verifyLoad(QSL("qrc:/resources/websocket.html"))); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); - - // Unregistered schemes can also open WebSockets (since Chromium 71) - QVERIFY(verifyLoad(QSL("tst:/resources/websocket.html"))); - QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("ok"))); - - // Even an insecure registered scheme can open WebSockets. - QVERIFY(verifyLoad(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(verifyLoad(QSL("file:" THIS_DIR "resources/dedicatedWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QCOMPARE(eval(QSL("result")), QVariant(42)); - - QVERIFY(verifyLoad(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(verifyLoad(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(verifyLoad(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(verifyLoad(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(verifyLoad(QSL("file:" THIS_DIR "resources/sharedWorker.html"))); - QTRY_VERIFY_WITH_TIMEOUT(eval(QSL("done")).toBool(), 10000); - QVERIFY(eval(QSL("error")).toString() - .contains(QSL("cannot be accessed from origin 'null'"))); - } - - { - ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, true); - QVERIFY(verifyLoad(QSL("file:" THIS_DIR "resources/sharedWorker.html"))); - QTRY_VERIFY_WITH_TIMEOUT(eval(QSL("done")).toBool(), 10000); - QCOMPARE(eval(QSL("result")), QVariant(42)); - } - - QVERIFY(verifyLoad(QSL("qrc:/resources/sharedWorker.html"))); - QTRY_VERIFY_WITH_TIMEOUT(eval(QSL("done")).toBool(), 10000); - QCOMPARE(eval(QSL("result")), QVariant(42)); - - // Unregistered schemes should not create SharedWorkers. - - QVERIFY(verifyLoad(QSL("PathSyntax:/resources/sharedWorker.html"))); - QTRY_VERIFY_WITH_TIMEOUT(eval(QSL("done")).toBool(), 10000); - QCOMPARE(eval(QSL("result")), QVariant(42)); - - QVERIFY(verifyLoad(QSL("PathSyntax-NoAccessAllowed:/resources/sharedWorker.html"))); - QTRY_VERIFY_WITH_TIMEOUT(eval(QSL("done")).toBool(), 10000); - 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(verifyLoad(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(verifyLoad(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(verifyLoad(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(verifyLoad(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(verifyLoad(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(verifyLoad(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(verifyLoad(QSL("PathSyntax-Secure-ServiceWorkersAllowed:/resources/serviceWorker.html"))); - QTRY_VERIFY(eval(QSL("done")).toBool()); - QCOMPARE(eval(QSL("error")), QVariant()); - - QVERIFY(verifyLoad(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(verifyLoad(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(verifyLoad(QSL("view-source:qrc:/resources/viewSource.html"))); - QCOMPARE(m_page->requestedUrl().toString(), QSL("qrc:/resources/viewSource.html")); - - QVERIFY(verifyLoad(QSL("view-source:tst:/resources/viewSource.html"))); - QCOMPARE(m_page->requestedUrl().toString(), QSL("about:blank")); - - QVERIFY(verifyLoad(QSL("view-source:PathSyntax:/resources/viewSource.html"))); - QCOMPARE(m_page->requestedUrl().toString(), QSL("about:blank")); - - QVERIFY(verifyLoad(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(verifyLoad(QSL("qrc:/resources/createObjectURL.html"))); - QVERIFY(eval(QSL("result")).toString().startsWith(QSL("blob:qrc:"))); - - // Also legal for unregistered schemes (since Chromium 71) - QVERIFY(verifyLoad(QSL("tst:/resources/createObjectURL.html"))); - QVERIFY(eval(QSL("result")).toString().startsWith(QSL("blob:tst:"))); -} - -void tst_Origins::redirect() -{ - QVERIFY(verifyLoad(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 3bf915609..000000000 --- a/tests/auto/widgets/proxy/proxy_server.cpp +++ /dev/null @@ -1,102 +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(); - m_authenticate = true; -} - -void ProxyServer::setCookie(const QByteArray &cookie) -{ - m_cookie.append(QByteArrayLiteral("Cookie: ")); - m_cookie.append(cookie); -} - - -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_authenticate && !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_authenticate && m_data.contains(m_auth)) { - emit authenticationSuccess(); - } - - if (m_data.contains(m_cookie)) { - emit cookieMatch(); - } - 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 7bc7b100b..000000000 --- a/tests/auto/widgets/proxy/proxy_server.h +++ /dev/null @@ -1,64 +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); - void setCookie(const QByteArray &cookie); - bool isListening(); - -public slots: - void run(); - -private slots: - void handleNewConnection(); - void handleReadReady(); - -signals: - void authenticationSuccess(); - void cookieMatch(); - -private: - QByteArray m_data; - QTcpServer m_server; - QByteArray m_auth; - QByteArray m_cookie; - bool m_authenticate = false; -}; - -#endif // PROXY_SERVER_H diff --git a/tests/auto/widgets/proxy/tst_proxy.cpp b/tests/auto/widgets/proxy/tst_proxy.cpp index c3e3c88a4..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> @@ -33,7 +8,7 @@ #include <QWebEnginePage> #include <QWebEngineView> #include <QWebEngineUrlRequestInterceptor> - +#include <QWebEngineLoadingInfo> struct Interceptor : public QWebEngineUrlRequestInterceptor { @@ -53,6 +28,7 @@ public: private slots: void proxyAuthentication(); void forwardCookie(); + void invalidHostName(); }; @@ -74,7 +50,7 @@ void tst_Proxy::proxyAuthentication() QWebEnginePage page; QSignalSpy successSpy(&server, &ProxyServer::authenticationSuccess); page.load(QUrl("http://www.qt.io")); - QTRY_VERIFY2(successSpy.count() > 0, "Could not get authentication token"); + QTRY_VERIFY2(successSpy.size() > 0, "Could not get authentication token"); } void tst_Proxy::forwardCookie() @@ -94,7 +70,20 @@ void tst_Proxy::forwardCookie() page.setUrlRequestInterceptor(&interceptor); QSignalSpy cookieSpy(&server, &ProxyServer::cookieMatch); page.load(QUrl("http://www.qt.io")); - QTRY_VERIFY2(cookieSpy.count() > 0, "Could not get cookie"); + 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_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.pri b/tests/auto/widgets/proxypac/proxypac.pri deleted file mode 100644 index b3b2856c8..000000000 --- a/tests/auto/widgets/proxypac/proxypac.pri +++ /dev/null @@ -1,5 +0,0 @@ -TEMPLATE = app -CONFIG += testcase -QT += testlib network webenginewidgets webengine -HEADERS += $$PWD/proxyserver.h -SOURCES += $$PWD/proxyserver.cpp $$PWD/tst_proxypac.cpp diff --git a/tests/auto/widgets/proxypac/proxypac.pro b/tests/auto/widgets/proxypac/proxypac.pro deleted file mode 100644 index f2a43d41f..000000000 --- a/tests/auto/widgets/proxypac/proxypac.pro +++ /dev/null @@ -1,4 +0,0 @@ -TEMPLATE = subdirs -SUBDIRS = proxypac_file proxypac_qrc -CONFIG += ordered - diff --git a/tests/auto/widgets/proxypac/proxypac.qrc b/tests/auto/widgets/proxypac/proxypac.qrc deleted file mode 100644 index 9047585a0..000000000 --- a/tests/auto/widgets/proxypac/proxypac.qrc +++ /dev/null @@ -1,7 +0,0 @@ -<!DOCTYPE RCC> -<RCC version="1.0"> -<qresource profix="/"> - <file>proxy.pac</file> -</qresource> -</RCC> - diff --git a/tests/auto/widgets/proxypac/proxypac_file/proxypac_file.pro b/tests/auto/widgets/proxypac/proxypac_file/proxypac_file.pro deleted file mode 100644 index 037123054..000000000 --- a/tests/auto/widgets/proxypac/proxypac_file/proxypac_file.pro +++ /dev/null @@ -1,9 +0,0 @@ -include(../proxypac.pri) - -proxy_pac.name = QTWEBENGINE_CHROMIUM_FLAGS -win32:proxy_pac.value = --proxy-pac-url="file:///$$PWD/../proxy.pac" -else:proxy_pac.value = --proxy-pac-url="file://$$PWD/../proxy.pac" -boot2qt:proxy_pac.value = "--single-process --no-sandbox --proxy-pac-url=file://$$PWD/../proxy.pac" - -QT_TOOL_ENV += proxy_pac - diff --git a/tests/auto/widgets/proxypac/proxypac_qrc/proxypac_qrc.pro b/tests/auto/widgets/proxypac/proxypac_qrc/proxypac_qrc.pro deleted file mode 100644 index a5ab64605..000000000 --- a/tests/auto/widgets/proxypac/proxypac_qrc/proxypac_qrc.pro +++ /dev/null @@ -1,7 +0,0 @@ -include(../proxypac.pri) - -proxy_pac.name = QTWEBENGINE_CHROMIUM_FLAGS -proxy_pac.value = --proxy-pac-url="qrc:///proxy.pac" -boot2qt:proxy_pac.value = "--single-process --no-sandbox --proxy-pac-url=qrc:///proxy.pac" -QT_TOOL_ENV += proxy_pac -RESOURCES+= $$PWD/../proxypac.qrc 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 934e23fde..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,30 +19,39 @@ private slots: void tst_ProxyPac::proxypac() { - const QString fromEnv = QString::fromLocal8Bit(qgetenv("QTWEBENGINE_CHROMIUM_FLAGS")); + const QString fromEnv = qEnvironmentVariable("QTWEBENGINE_CHROMIUM_FLAGS"); if (!fromEnv.contains("--proxy-pac-url")) - qFatal("--proxy-pac-url argument is not passed."); + 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 39948c211..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,53 +54,40 @@ private Q_SLOTS: void downloadToDefaultLocation(); void downloadToNonExistentDir(); void downloadToReadOnlyDir(); -#if QT_DEPRECATED_SINCE(5, 14) - void downloadPathValidation(); -#endif 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); }); }); @@ -134,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; @@ -159,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()); @@ -184,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); @@ -192,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"); @@ -213,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") @@ -221,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; @@ -308,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. @@ -332,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. @@ -399,7 +355,7 @@ void tst_QWebEngineDownloadItem::downloadLink_data() /* fileAction */ << FileIsDownloaded; } -void tst_QWebEngineDownloadItem::downloadLink() +void tst_QWebEngineDownloadRequest::downloadLink() { QFETCH(UserAction, userAction); QFETCH(bool, anchorHasDownloadAttribute); @@ -440,8 +396,7 @@ void tst_QWebEngineDownloadItem::downloadLink() rr->setResponseBody(fileContents); rr->sendResponse(); } else { - rr->setResponseStatus(404); - rr->sendResponse(); + rr->sendResponse(404); } }); @@ -457,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); @@ -500,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); @@ -508,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; @@ -527,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"); @@ -537,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); @@ -561,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(); } }); @@ -573,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()); @@ -607,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 @@ -621,20 +565,20 @@ void tst_QWebEngineDownloadItem::downloadTwoLinks() QTRY_COMPARE(finishedCount, 2); } -void tst_QWebEngineDownloadItem::downloadPage_data() +void tst_QWebEngineDownloadRequest::downloadPage_data() { QTest::addColumn<bool>("saveWithPageAction"); - QTest::addColumn<QWebEngineDownloadItem::SavePageFormat>("savePageFormat"); - QTest::newRow("SingleHtmlSaveFormat") << false << QWebEngineDownloadItem::SingleHtmlSaveFormat; - QTest::newRow("CompleteHtmlSaveFormat") << false << QWebEngineDownloadItem::CompleteHtmlSaveFormat; - QTest::newRow("MimeHtmlSaveFormat") << false << QWebEngineDownloadItem::MimeHtmlSaveFormat; - QTest::newRow("SavePageAction") << true << QWebEngineDownloadItem::MimeHtmlSaveFormat; + 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(bool, saveWithPageAction); - QFETCH(QWebEngineDownloadItem::SavePageFormat, savePageFormat); + QFETCH(QWebEngineDownloadRequest::SavePageFormat, savePageFormat); // Set up HTTP server int indexRequestCount = 0; @@ -644,9 +588,6 @@ 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(); } }); @@ -657,13 +598,12 @@ void tst_QWebEngineDownloadItem::downloadPage() QUrl downloadUrl = m_server->url("/"); int acceptedCount = 0; int finishedCount = 0; - ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { - QCOMPARE(item->state(), saveWithPageAction ? QWebEngineDownloadItem::DownloadRequested : 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")); @@ -681,13 +621,12 @@ void tst_QWebEngineDownloadItem::downloadPage() QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), downloadPath); - 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::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); @@ -704,7 +643,7 @@ 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); @@ -720,7 +659,7 @@ void tst_QWebEngineDownloadItem::downloadPage() 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 @@ -735,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()); }); @@ -752,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); @@ -763,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); @@ -774,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++; }); @@ -797,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++; @@ -819,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; @@ -878,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"); @@ -887,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); @@ -902,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; @@ -945,7 +876,7 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilename() } } -void tst_QWebEngineDownloadItem::downloadUniqueFilenameWithTimestamp() +void tst_QWebEngineDownloadRequest::downloadUniqueFilenameWithTimestamp() { // Set up HTTP server QString baseName("test(1.test)"); @@ -960,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()); @@ -1024,7 +955,7 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilenameWithTimestamp() } } -void tst_QWebEngineDownloadItem::downloadToDefaultLocation() +void tst_QWebEngineDownloadRequest::downloadToDefaultLocation() { QTemporaryDir tmpDir; QVERIFY(tmpDir.isValid()); @@ -1041,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"); @@ -1056,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()); @@ -1094,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."); @@ -1113,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, [&]() { + connect(item, &QWebEngineDownloadRequest::isFinishedChanged, [&]() { downloadFinished = true; }); downloadAccepted = true; @@ -1143,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); @@ -1155,126 +1086,7 @@ void tst_QWebEngineDownloadItem::downloadToReadOnlyDir() QFile(m_profile->downloadPath()).setPermissions(QFileDevice::WriteOwner); } -#if QT_DEPRECATED_SINCE(5, 14) -void tst_QWebEngineDownloadItem::downloadPathValidation() -{ - const QString fileName = "test.txt"; - QString downloadPath; - QString originalDownloadPath; - - 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); -} -#endif - -void tst_QWebEngineDownloadItem::downloadToDirectoryWithFileName_data() +void tst_QWebEngineDownloadRequest::downloadToDirectoryWithFileName_data() { QTest::addColumn<bool>("setDirectoryFirst"); @@ -1282,7 +1094,7 @@ void tst_QWebEngineDownloadItem::downloadToDirectoryWithFileName_data() QTest::newRow("setFileNameFirst") << false; } -void tst_QWebEngineDownloadItem::downloadToDirectoryWithFileName() +void tst_QWebEngineDownloadRequest::downloadToDirectoryWithFileName() { QFETCH(bool, setDirectoryFirst); QString downloadDirectory; @@ -1300,44 +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) { - + 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()); @@ -1375,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"; @@ -1396,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"; @@ -1418,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/tst_qwebenginehistory.cpp b/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp index bdb486793..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(); } @@ -150,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(); @@ -168,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(); @@ -205,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()); } @@ -225,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++) { @@ -236,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); @@ -297,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 @@ -310,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); } @@ -428,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() @@ -467,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); } @@ -486,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 02b297d5a..52def48d1 100644 --- a/tests/auto/widgets/qwebenginepage/BLACKLIST +++ b/tests/auto/widgets/qwebenginepage/BLACKLIST @@ -5,8 +5,11 @@ osx windows macos # Can't move cursor (QTBUG-76312) -[devTools] -msvc-2019 +[comboBoxPopupPositionAfterMove] +macos -[setLifecycleStateWithDevTools] -msvc-2019 +[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 621c2dbd7..f1d64776b 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 @@ -89,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(); @@ -129,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 @@ -141,7 +172,8 @@ private Q_SLOTS: void runJavaScriptDisabled(); void runJavaScriptFromSlot(); void fullScreenRequested(); - void quotaRequested(); + void requestQuota_data(); + void requestQuota(); // Tests from tst_QWebEngineFrame @@ -173,7 +205,6 @@ private Q_SLOTS: void setUrlUsingStateObject(); void setUrlThenLoads_data(); void setUrlThenLoads(); - void loadFinishedAfterNotFoundError(); void loadInSignalHandlers_data(); void loadInSignalHandlers(); void loadFromQrc(); @@ -204,7 +235,13 @@ private Q_SLOTS: 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(); @@ -225,6 +262,8 @@ private Q_SLOTS: void editActionsWithoutSelection(); void customUserAgentInNewTab(); + void openNewTabInDifferentProfile_data(); + void openNewTabInDifferentProfile(); void renderProcessCrashed(); void renderProcessPid(); void backgroundColor(); @@ -233,20 +272,54 @@ private Q_SLOTS: 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; + 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() @@ -288,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() @@ -295,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: @@ -302,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); @@ -315,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_WITH_TIMEOUT(loadSpy.count(), 1, 20000); + 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 @@ -365,6 +469,7 @@ private: bool m_allowGeolocation; }; +#ifndef Q_OS_MACOS void tst_QWebEnginePage::geolocationRequestJS_data() { QTest::addColumn<bool>("allowed"); @@ -379,7 +484,7 @@ void tst_QWebEnginePage::geolocationRequestJS() QFETCH(int, errorCode); QWebEngineView view; JSTestPage *newPage = new JSTestPage(&view); - newPage->setView(&view); + view.setPage(newPage); newPage->setGeolocationPermission(allowed); connect(newPage, SIGNAL(featurePermissionRequested(const QUrl&, QWebEnginePage::Feature)), @@ -387,7 +492,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_WITH_TIMEOUT(spyLoadFinished.count(), 1, 20000); + QTRY_COMPARE_WITH_TIMEOUT(spyLoadFinished.size(), 1, 20000); // Geolocation is only enabled for visible WebContents. view.show(); @@ -404,6 +509,7 @@ void tst_QWebEnginePage::geolocationRequestJS() QEXPECT_FAIL("", "No location service available.", Continue); QCOMPARE(result, errorCode); } +#endif void tst_QWebEnginePage::loadFinished() { @@ -414,19 +520,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_WITH_TIMEOUT(spyLoadFinished.count(), 1, 20000); + 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() @@ -483,6 +589,7 @@ void tst_QWebEnginePage::pasteImage() 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); @@ -493,7 +600,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); @@ -512,42 +619,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; @@ -555,77 +649,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_WITH_TIMEOUT(loadSpy.count(), 1, 20000); - 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_WITH_TIMEOUT(loadSpy.count(), 2, 20000); - 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_WITH_TIMEOUT(loadSpy.count(), 6, 20000); - 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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 20000); + "</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 @@ -637,7 +836,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); @@ -665,16 +865,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_WITH_TIMEOUT(loadSpy1.count(), 1, 20000); - QTRY_COMPARE_WITH_TIMEOUT(loadSpy2.count(), 1, 20000); + 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")); @@ -723,7 +923,7 @@ void tst_QWebEnginePage::textSelection() QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); page.setHtml(content); - QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 20000); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); // these actions must exist QVERIFY(page.action(QWebEnginePage::SelectAll) != 0); @@ -750,7 +950,7 @@ void tst_QWebEnginePage::textSelection() // navigate away and check that selection is cleared page.load(QUrl("about:blank")); - QTRY_COMPARE(loadSpy.count(), 2); + QTRY_COMPARE(loadSpy.size(), 2); QVERIFY(!page.hasSelection()); QVERIFY(page.selectedText().isEmpty()); @@ -771,7 +971,7 @@ void tst_QWebEnginePage::backActionUpdate() QVERIFY(!action->isEnabled()); page->load(QUrl("qrc:///resources/framedindex.html")); - QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 20000); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); QVERIFY(!action->isEnabled()); auto firstAnchorCenterInFrame = [](QWebEnginePage *page, const QString &frameName) { @@ -783,7 +983,7 @@ void tst_QWebEnginePage::backActionUpdate() "return [(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2];" "})()").toList(); - if (rectList.count() != 2) { + if (rectList.size() != 2) { qWarning("firstAnchorCenterInFrame failed."); return QPoint(); } @@ -810,8 +1010,8 @@ 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_WITH_TIMEOUT(loadSpy1.count(), 1, 20000); - QTRY_COMPARE_WITH_TIMEOUT(loadSpy2.count(), 1, 20000); + 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()); @@ -824,13 +1024,14 @@ void tst_QWebEnginePage::localStorageVisibility() // ...first check second page (for storage to appear) as applying settings is batched and done asynchronously QTRY_VERIFY(evaluateJavaScriptSync(&webPage2, QString("(window.localStorage != undefined)")).toBool()); // Switching the feature off does not actively remove the object from webPage1. - QVERIFY(evaluateJavaScriptSync(&webPage1, QString("(window.localStorage != undefined)")).toBool()); +// 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()); } @@ -904,7 +1105,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(); @@ -931,7 +1132,7 @@ void tst_QWebEnginePage::testJSPrompt() bool res; QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); page.setHtml(QStringLiteral("<html><body></body></html>")); - QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 20000); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); // OK + QString() res = evaluateJavaScriptSync(&page, @@ -965,7 +1166,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()); @@ -975,22 +1176,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("", {}, 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", {}, callbackSpy.ref()); - QCOMPARE(callbackSpy.waitForResult(), false); - QTRY_COMPARE(signalSpy.count(), 1); + 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()); @@ -1003,22 +1204,22 @@ 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", {}, callbackSpy.ref()); - QVERIFY(callbackSpy.waitForResult()); - QTRY_COMPARE(signalSpy.count(), 1); + 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("", {}, callbackSpy.ref()); QTRY_VERIFY(callbackSpy.wasCalled()); - QTRY_COMPARE(signalSpy.count(), 1); + QTRY_COMPARE(signalSpy.size(), 1); QTRY_COMPARE(m_view->selectedText(), QString("foo")); } @@ -1028,7 +1229,7 @@ void tst_QWebEnginePage::findText() QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); m_view->findText("foo", {}); m_view->findText("foo", {}); - QTRY_COMPARE(signalSpy.count(), 2); + QTRY_COMPARE(signalSpy.size(), 2); QTRY_VERIFY(m_view->selectedText().isEmpty()); QCOMPARE(signalSpy.at(0).value(0).value<QWebEngineFindTextResult>().numberOfMatches(), 0); @@ -1039,11 +1240,11 @@ void tst_QWebEnginePage::findText() 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.activeMatch() }); + return QList<int>({ r.numberOfMatches(), r.activeMatch() }); }; // findText will abort in blink if the view has an empty size. @@ -1052,36 +1253,36 @@ 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_WITH_TIMEOUT(loadSpy.count(), 1, 20000); + 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()); @@ -1103,15 +1304,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", {}, [this, &callbackCalled](bool found) { - QVERIFY(found); + m_view->page()->findText("foo", {}, [this, &callbackCalled](QWebEngineFindTextResult result) { + QVERIFY(result.numberOfMatches()); - m_view->page()->findText("bar", {}, [&callbackCalled](bool found) { - QVERIFY(found); + m_view->page()->findText("bar", {}, [&callbackCalled](QWebEngineFindTextResult result) { + QVERIFY(result.numberOfMatches()); callbackCalled = true; }); }); @@ -1140,12 +1341,12 @@ 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", {}); - QTRY_COMPARE(findTextSpy.count(), 1); + QTRY_COMPARE(findTextSpy.size(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 3); QCOMPARE(result.activeMatch(), i); @@ -1153,28 +1354,28 @@ void tst_QWebEnginePage::findTextActiveMatchOrdinal() // The last match is followed by the fist one. m_view->page()->findText("foo", {}); - QTRY_COMPARE(findTextSpy.count(), 1); + QTRY_COMPARE(findTextSpy.size(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 3); 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.activeMatch(), 3); // Finding another word resets the activeMatch. m_view->page()->findText("bar", {}); - QTRY_COMPARE(findTextSpy.count(), 1); + QTRY_COMPARE(findTextSpy.size(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 2); QCOMPARE(result.activeMatch(), 1); // If no match activeMatch is 0. m_view->page()->findText("bla", {}); - QTRY_COMPARE(findTextSpy.count(), 1); + QTRY_COMPARE(findTextSpy.size(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 0); QCOMPARE(result.activeMatch(), 0); @@ -1184,45 +1385,60 @@ 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.count() != 2) { + if (posList.size() != 2) { qWarning("jsViewPosition failed."); return QPoint(); } @@ -1234,18 +1450,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()); - QTest::mouseClick(window, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(1, 1)); + 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); @@ -1256,29 +1482,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(); @@ -1288,19 +1518,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("αβγδεζηθικλμπ"); @@ -1328,6 +1564,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: @@ -1385,6 +1623,7 @@ void tst_QWebEnginePage::loadSignalsOrder() page.load(url); QTRY_VERIFY_WITH_TIMEOUT(loadSpy.isFinished(), 20000); } +#endif // defined(QT_STATEMACHINE_LIB) void tst_QWebEnginePage::renderWidgetHostViewNotShowTopLevel() { @@ -1651,9 +1890,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); }); @@ -1667,7 +1906,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. @@ -1684,18 +1923,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); @@ -1766,7 +2008,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_WITH_TIMEOUT(spy.count(), 1, 20000); + 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), @@ -1783,7 +2025,7 @@ void tst_QWebEnginePage::runJavaScriptFromSlot() page.setHtml("<html><body>" " <input type='text' id='input1' value='QtWebEngine' size='50' />" "</body></html>"); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); bool done = false; connect(&page, &QWebEnginePage::selectionChanged, [&]() { @@ -1800,13 +2042,14 @@ void tst_QWebEnginePage::fullScreenRequested() { 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); QTRY_VERIFY(isTrueJavaScriptResult(page, "document.webkitFullscreenEnabled")); QVERIFY(isFalseJavaScriptResult(page, "document.webkitIsFullScreen")); @@ -1820,6 +2063,13 @@ void tst_QWebEnginePage::fullScreenRequested() QTest::keyPress(view.focusProxy(), Qt::Key_Space); 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")); @@ -1830,8 +2080,18 @@ void tst_QWebEnginePage::fullScreenRequested() 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); @@ -1839,35 +2099,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() @@ -1882,13 +2130,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_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 20000); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 20000); QCOMPARE(view.history()->count(), 1); QCOMPARE(toPlainTextSync(view.page()), QString("Test")); @@ -1902,8 +2151,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. @@ -1981,7 +2230,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())); @@ -2002,11 +2251,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; } @@ -2020,7 +2269,7 @@ private Q_SLOTS: void continueError() { - emit error(this->error()); + emit errorOccurred(this->error()); emit finished(); } }; @@ -2034,11 +2283,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; @@ -2062,7 +2311,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()); @@ -2071,7 +2320,7 @@ void tst_QWebEnginePage::requestedUrlAfterSetAndLoadFailures() QVERIFY(first != second); page.load(second); - QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 2, 20000); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 2, 20000); QCOMPARE(page.url(), first); QCOMPARE(page.requestedUrl(), second); QVERIFY(!spy.at(1).first().toBool()); @@ -2117,7 +2366,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); @@ -2126,7 +2375,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); @@ -2167,10 +2416,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>"); @@ -2179,10 +2433,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); @@ -2199,7 +2455,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/"))); @@ -2228,9 +2484,6 @@ void tst_QWebEnginePage::setHtmlWithModuleImport() "}\n"); rr->setResponseHeader("Content-Type", "text/javascript"); rr->sendResponse(); - } else { - rr->setResponseStatus(404); - rr->sendResponse(); } }); QVERIFY(server.start()); @@ -2248,7 +2501,7 @@ void tst_QWebEnginePage::setHtmlWithModuleImport() QWebEnginePage page; QSignalSpy spy(&page, &QWebEnginePage::loadFinished); page.setHtml(html, server.url()); - QVERIFY(spy.count() || spy.wait()); + QVERIFY(spy.size() || spy.wait()); QCOMPARE(evaluateJavaScriptSync(&page, "fib7"), QVariant(13)); } @@ -2284,7 +2537,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); @@ -2293,7 +2546,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); @@ -2303,12 +2557,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(); @@ -2329,7 +2583,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()); } @@ -2340,7 +2594,8 @@ signals: void repaintRequested(); protected: - bool event(QEvent *event) { + bool event(QEvent *event) override + { if (event->type() == QEvent::UpdateRequest) emit repaintRequested(); @@ -2363,7 +2618,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); @@ -2379,9 +2634,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; @@ -2408,7 +2667,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()) @@ -2453,7 +2712,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); @@ -2462,7 +2721,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); @@ -2471,7 +2730,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); @@ -2480,7 +2739,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); @@ -2535,9 +2794,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()); @@ -2548,9 +2807,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()); @@ -2574,9 +2833,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()); @@ -2588,9 +2847,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()); @@ -2621,7 +2880,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. @@ -2630,7 +2889,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); @@ -2641,14 +2900,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. @@ -2660,7 +2919,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. @@ -2673,7 +2932,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() @@ -2688,6 +2947,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); @@ -2695,20 +2955,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(m_page->history()->count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QCOMPARE(m_page->url(), url); + QCOMPARE(m_page->history()->count(), 1); evaluateJavaScriptSync(m_page, "window.history.pushState(null, 'push', 'navigate/to/here')"); expectedUrlChangeCount++; - QTRY_COMPARE(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++; - QTRY_COMPARE(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()); @@ -2716,7 +2978,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()); @@ -2745,9 +3007,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); @@ -2761,11 +3023,11 @@ void tst_QWebEnginePage::setUrlThenLoads() 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); @@ -2775,31 +3037,17 @@ void tst_QWebEnginePage::setUrlThenLoads() 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 @@ -2881,11 +3129,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); } @@ -2896,31 +3140,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); } @@ -2937,7 +3181,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)); @@ -2947,7 +3191,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)); @@ -2970,42 +3214,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() @@ -3025,17 +3286,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); @@ -3064,34 +3328,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; @@ -3100,12 +3347,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())); @@ -3126,7 +3373,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"); @@ -3142,8 +3390,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); @@ -3155,7 +3407,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); @@ -3190,7 +3442,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))); @@ -3212,7 +3464,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() @@ -3235,19 +3487,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\")"); @@ -3257,7 +3507,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); @@ -3268,7 +3518,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()); @@ -3279,22 +3529,12 @@ 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, {}, 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, {}, elementCenter(m_page, "link")); QVERIFY(urlChangedSpy.wait()); @@ -3318,7 +3558,7 @@ void tst_QWebEnginePage::devTools() QCOMPARE(devToolsPage.devToolsPage(), nullptr); QCOMPARE(devToolsPage.inspectedPage(), &inspectedPage1); - QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 30000); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 90000); QVERIFY(spy.takeFirst().value(0).toBool()); devToolsPage.setInspectedPage(&inspectedPage2); @@ -3330,7 +3570,7 @@ void tst_QWebEnginePage::devTools() QCOMPARE(devToolsPage.devToolsPage(), nullptr); QCOMPARE(devToolsPage.inspectedPage(), &inspectedPage2); - QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 30000); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 90000); QVERIFY(spy.takeFirst().value(0).toBool()); devToolsPage.setInspectedPage(nullptr); @@ -3341,19 +3581,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); @@ -3361,13 +3602,13 @@ 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; + targetPage = &page2; QTest::mouseClick(view.focusProxy(), Qt::MiddleButton, {}, elementCenter(&page1, "link")); - QTRY_COMPARE(spy2.count(), 1); + QTRY_COMPARE(spy2.size(), 1); QVERIFY(spy2.takeFirst().value(0).toBool()); } @@ -3427,7 +3668,7 @@ void tst_QWebEnginePage::openLinkInNewPage_data() // 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::LoadInSelf; + 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; @@ -3465,6 +3706,7 @@ void tst_QWebEnginePage::openLinkInNewPage() QFETCH(Effect, effect); QWebEngineProfile profile; + profile.installUrlSchemeHandler("echo", new EchoingUrlSchemeHandler(&profile)); Page page1(&profile); Page page2(&profile); View view1(&page1); @@ -3474,9 +3716,9 @@ void tst_QWebEnginePage::openLinkInNewPage() QVERIFY(QTest::qWaitForWindowExposed(&view1)); page1.setHtml("<html><body>" - "<a id='link' href='data:,hello' target='_blank'>link</a>" - "</body></html>"); - QTRY_COMPARE(page1.spy.count(), 1); + "<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) { @@ -3491,7 +3733,7 @@ void tst_QWebEnginePage::openLinkInNewPage() break; } - Qt::MouseButton button; + Qt::MouseButton button = Qt::NoButton; switch (cause) { case Cause::TargetBlank: button = Qt::LeftButton; @@ -3504,12 +3746,15 @@ void tst_QWebEnginePage::openLinkInNewPage() switch (effect) { case Effect::Blocked: - // Nothing to test + // 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.count(), 1); + QTRY_COMPARE(page1.spy.size(), 1); QVERIFY(page1.spy.takeFirst().value(0).toBool()); - QCOMPARE(page2.spy.count(), 0); + QCOMPARE(page2.spy.size(), 0); if (decision == Decision::ReturnSelf && cause == Cause::TargetBlank) // History was discarded due to AddNewContents QCOMPARE(page1.history()->count(), 1); @@ -3518,9 +3763,9 @@ void tst_QWebEnginePage::openLinkInNewPage() QCOMPARE(page2.history()->count(), 0); break; case Effect::LoadInOther: - QTRY_COMPARE(page2.spy.count(), 1); + QTRY_COMPARE(page2.spy.size(), 1); QVERIFY(page2.spy.takeFirst().value(0).toBool()); - QCOMPARE(page1.spy.count(), 0); + QCOMPARE(page1.spy.size(), 0); QCOMPARE(page1.history()->count(), 1); QCOMPARE(page2.history()->count(), 1); break; @@ -3542,7 +3787,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")); } @@ -3618,7 +3863,7 @@ void tst_QWebEnginePage::notificationPermission() QSignalSpy spy(&page, &QWebEnginePage::loadFinished); page.setHtml(QString("<html><body>Test</body></html>"), baseUrl); - QTRY_COMPARE(spy.count(), 1); + QTRY_COMPARE(spy.size(), 1); QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("Notification.permission")), setOnInit ? permission : QLatin1String("default")); @@ -3676,6 +3921,149 @@ 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; + 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; + 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); @@ -3686,8 +4074,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); @@ -3704,6 +4092,60 @@ 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); + 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); + } + + // Reset permission, since otherwise it will be stored between runs + page.setFeaturePermission(QUrl("qrc:///resources/fontaccess.html"), QWebEnginePage::LocalFontsAccess, QWebEnginePage::PermissionUnknown); +} + void tst_QWebEnginePage::setLifecycleState() { qRegisterMetaType<QWebEnginePage::LifecycleState>("LifecycleState"); @@ -3715,64 +4157,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)); @@ -3781,21 +4223,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)); } @@ -3811,18 +4253,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); @@ -3831,28 +4273,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); @@ -3861,31 +4303,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)); } @@ -3896,7 +4338,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 @@ -3933,7 +4375,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 @@ -3972,7 +4414,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 @@ -3997,16 +4439,16 @@ void tst_QWebEnginePage::setLifecycleStateWithDevTools() // Ensure pages are initialized inspectedPage.load(QStringLiteral("about:blank")); devToolsPage.load(QStringLiteral("about:blank")); - QTRY_COMPARE_WITH_TIMEOUT(inspectedSpy.count(), 1, 30000); + QTRY_COMPARE_WITH_TIMEOUT(inspectedSpy.size(), 1, 90000); QCOMPARE(inspectedSpy.takeFirst().value(0), QVariant(true)); - QTRY_COMPARE_WITH_TIMEOUT(devToolsSpy.count(), 1, 30000); + 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); @@ -4014,9 +4456,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); @@ -4024,7 +4466,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); @@ -4032,8 +4474,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 @@ -4071,35 +4512,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); } @@ -4116,21 +4557,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()); } @@ -4146,14 +4587,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)); @@ -4163,21 +4604,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); } @@ -4273,32 +4714,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)); } @@ -4313,33 +4754,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)); } @@ -4358,20 +4799,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")); } @@ -4391,13 +4832,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")); } @@ -4417,15 +4858,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")); } @@ -4441,8 +4882,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()); @@ -4454,7 +4895,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")); @@ -4468,6 +4909,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; @@ -4480,55 +4943,84 @@ 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()); + 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()); + 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() @@ -4569,6 +5061,141 @@ void tst_QWebEnginePage::renderProcessPid() 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; @@ -4622,24 +5249,27 @@ void tst_QWebEnginePage::audioMuted() page.setAudioMuted(true); loadSync(&page, QUrl("about:blank")); QCOMPARE(page.isAudioMuted(), true); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(spy[0][0], QVariant(true)); page.setAudioMuted(false); QCOMPARE(page.isAudioMuted(), false); - QCOMPARE(spy.count(), 2); + 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.count(), 1); + QTRY_COMPARE(windowCreatedSpy.size(), 1); QWebEngineView *dialogView = new QWebEngineView; QWebEnginePage *dialogPage = page.createdWindows[0]; - dialogPage->setView(dialogView); + dialogView->setPage(dialogPage); QCOMPARE(dialogPage->lifecycleState(), QWebEnginePage::LifecycleState::Active); // This should not crash. @@ -4662,8 +5292,8 @@ void tst_QWebEnginePage::isSafeRedirect_data() fileScheme += "/"; #endif - QString tempDir(fileScheme + QDir::tempPath()); - QTest::newRow(qPrintable(tempDir)) << QUrl(tempDir) << QUrl(tempDir + "/"); + 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/"); } @@ -4676,11 +5306,452 @@ void tst_QWebEnginePage::isSafeRedirect() TestPage page; QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); page.setUrl(requestedUrl); - QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 20000); + 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); + + 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")}; W_QTEST_MAIN(tst_QWebEnginePage, params) 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/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 ca16cee39..000000000 --- a/tests/auto/widgets/qwebengineprofile/qwebengineprofile.pro +++ /dev/null @@ -1,4 +0,0 @@ -include(../tests.pri) -include(../../shared/http.pri) -exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc -QT *= core-private gui-private diff --git a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp index 00d4bae5a..cebdaaa47 100644 --- a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp +++ b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp @@ -1,32 +1,7 @@ -/**************************************************************************** -** -** 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> @@ -35,11 +10,11 @@ #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> @@ -57,16 +32,16 @@ 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(); @@ -78,7 +53,6 @@ private Q_SLOTS: void changePersistentPath(); void changeHttpUserAgent(); void changeHttpAcceptLanguage(); - void changeUseForGlobalCertificateVerification(); void changePersistentCookiesPolicy(); void initiator(); void badDeleteOrder(); @@ -105,61 +79,48 @@ void tst_QWebEngineProfile::initTestCase() 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")); } class AutoDir : public QDir @@ -197,14 +158,12 @@ public: private: void onNewRequest(HttpReqRep *rr) { - const QDir resourceDir(TESTS_SOURCE_DIR "qwebengineprofile/resources"); + const QDir resourceDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + "/resources"); QString path = rr->requestPath(); path.remove(0, 1); - if (rr->requestMethod() != "GET" || !resourceDir.exists(path)) - { - rr->setResponseStatus(404); - rr->sendResponse(); + if (rr->requestMethod() != "GET" || !resourceDir.exists(path)) { + rr->sendResponse(404); return; } @@ -229,8 +188,10 @@ void tst_QWebEngineProfile::clearDataFromCache() QVERIFY(server.start()); AutoDir cacheDir("./tst_QWebEngineProfile_clearDataFromCache"); + QVERIFY(!cacheDir.exists("Cache")); - QWebEngineProfile profile(QStringLiteral("Test")); + QWebEngineProfile profile(QStringLiteral("clearDataFromCache")); + QSignalSpy cacheSpy(&profile, &QWebEngineProfile::clearHttpCacheCompleted); profile.setCachePath(cacheDir.path()); profile.setHttpCacheType(QWebEngineProfile::DiskHttpCache); @@ -241,10 +202,14 @@ void tst_QWebEngineProfile::clearDataFromCache() QVERIFY(cacheDir.exists("Cache")); qint64 sizeBeforeClear = totalSize(cacheDir); + QCOMPARE_GT(sizeBeforeClear, 0); profile.clearHttpCache(); - // Wait for cache to be cleared. - QTest::qWait(1000); - QVERIFY(sizeBeforeClear > totalSize(cacheDir)); + 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(); } @@ -256,18 +221,18 @@ void tst_QWebEngineProfile::disableCache() AutoDir cacheDir("./tst_QWebEngineProfile_disableCache"); - QWebEnginePage page; - QWebEngineProfile *profile = page.profile(); - profile->setCachePath(cacheDir.path()); + QWebEngineProfile profile("disableCache"); + QWebEnginePage page(&profile); + profile.setCachePath(cacheDir.path()); QVERIFY(!cacheDir.exists("Cache")); - profile->setHttpCacheType(QWebEngineProfile::NoCache); + profile.setHttpCacheType(QWebEngineProfile::NoCache); // Wait for cache to be cleared. QTest::qWait(1000); QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); QVERIFY(!cacheDir.exists("Cache")); - profile->setHttpCacheType(QWebEngineProfile::DiskHttpCache); + profile.setHttpCacheType(QWebEngineProfile::DiskHttpCache); QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); QVERIFY(cacheDir.exists("Cache")); @@ -277,7 +242,7 @@ void tst_QWebEngineProfile::disableCache() 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())); @@ -295,7 +260,7 @@ public: { } - void requestStarted(QWebEngineUrlRequestJob *job) + void requestStarted(QWebEngineUrlRequestJob *job) override { QBuffer *buffer = new QBuffer(job); buffer->setData(job->requestUrl().toString().toUtf8()); @@ -306,10 +271,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); @@ -320,12 +287,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); @@ -346,7 +312,7 @@ protected: memcpy(data, m_data.constData() + m_bytesRead, len); m_bytesAvailable -= len; m_bytesRead += len; - } else if (atEnd()) + } else if (internalAtEnd()) return -1; return len; @@ -356,19 +322,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: @@ -377,12 +350,26 @@ public: { } - void requestStarted(QWebEngineUrlRequestJob *job) + void requestStarted(QWebEngineUrlRequestJob *job) override { job->reply("text/plain;charset=utf-8", new StreamingIODevice(job)); } }; +class StreamingUrlSchemeHandler2 : public QWebEngineUrlSchemeHandler +{ +public: + StreamingUrlSchemeHandler2(QObject *parent = nullptr) + : QWebEngineUrlSchemeHandler(parent) + { + } + + void requestStarted(QWebEngineUrlRequestJob *job) override + { + job->reply("text/plain;charset=utf-8", new StreamingIODeviceBasic(job)); + } +}; + void tst_QWebEngineProfile::urlSchemeHandlers() { RedirectingUrlSchemeHandler lettertoHandler; @@ -392,42 +379,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), false)); + 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, false)); + 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()); } @@ -487,7 +474,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()); } @@ -501,7 +489,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()); } @@ -515,7 +504,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)); @@ -576,7 +583,7 @@ void tst_QWebEngineProfile::urlSchemeHandlerRequestHeaders() 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() @@ -648,7 +655,7 @@ private: class XhrStatusUrlSchemeHandler : public QWebEngineUrlSchemeHandler { public: - void requestStarted(QWebEngineUrlRequestJob *job) + void requestStarted(QWebEngineUrlRequestJob *job) override { QString path = job->requestUrl().path(); if (path == "/") { @@ -708,7 +715,7 @@ void tst_QWebEngineProfile::urlSchemeHandlerXhrStatus() profile.installUrlSchemeHandler("aviancarrier", &handler); page.setWebChannel(&channel); page.load(QUrl("aviancarrier:/")); - QTRY_VERIFY(host.isReady()); + QTRY_VERIFY_WITH_TIMEOUT(host.isReady(), 30000); host.load(QUrl("aviancarrier:/ok")); host.load(QUrl("aviancarrier:/redirect")); host.load(QUrl("aviancarrier:/fail")); @@ -726,7 +733,7 @@ void tst_QWebEngineProfile::urlSchemeHandlerXhrStatus() class ScriptsUrlSchemeHandler : public QWebEngineUrlSchemeHandler { public: - void requestStarted(QWebEngineUrlRequestJob *job) + void requestStarted(QWebEngineUrlRequestJob *job) override { auto *script = new QBuffer(job); script->setData(QByteArrayLiteral("window.test = 'SUCCESS';")); @@ -742,12 +749,12 @@ void tst_QWebEngineProfile::urlSchemeHandlerScriptModule() 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.count(), 1); + 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.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("test")).toString(), QStringLiteral("SUCCESS")); } @@ -757,7 +764,7 @@ public: LongReplyUrlSchemeHandler(QObject *parent = nullptr) : QWebEngineUrlSchemeHandler(parent) {} ~LongReplyUrlSchemeHandler() {} - void requestStarted(QWebEngineUrlRequestJob *job) + void requestStarted(QWebEngineUrlRequestJob *job) override { QBuffer *buffer = new QBuffer(job); buffer->setData(QByteArray(128 * 1024, ' ') + @@ -782,7 +789,7 @@ void tst_QWebEngineProfile::customUserAgent() 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); @@ -795,7 +802,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); @@ -809,7 +816,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(); @@ -821,7 +828,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); @@ -833,12 +840,12 @@ void tst_QWebEngineProfile::httpAcceptLanguage() void tst_QWebEngineProfile::downloadItem() { - qRegisterMetaType<QWebEngineDownloadItem *>(); + qRegisterMetaType<QWebEngineDownloadRequest *>(); QWebEngineProfile testProfile; QWebEnginePage page(&testProfile); - QSignalSpy downloadSpy(&testProfile, SIGNAL(downloadRequested(QWebEngineDownloadItem *))); + 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() @@ -846,12 +853,12 @@ void tst_QWebEngineProfile::changePersistentPath() TestServer server; QVERIFY(server.start()); - AutoDir dataDir1(QStandardPaths::writableLocation(QStandardPaths::DataLocation) - + QStringLiteral("/QtWebEngine/Test")); - AutoDir dataDir2(QStandardPaths::writableLocation(QStandardPaths::DataLocation) - + QStringLiteral("/QtWebEngine/Test2")); + AutoDir dataDir1(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + + QStringLiteral("/QtWebEngine/changePersistentPath1")); + AutoDir dataDir2(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + + QStringLiteral("/QtWebEngine/changePersistentPath2")); - QWebEngineProfile testProfile(QStringLiteral("Test")); + QWebEngineProfile testProfile(QStringLiteral("changePersistentPath1")); QCOMPARE(testProfile.persistentStoragePath(), dataDir1.path()); // Make sure the profile has been used: @@ -872,13 +879,13 @@ void tst_QWebEngineProfile::changeHttpUserAgent() TestServer server; QVERIFY(server.start()); - QVector<QByteArray> userAgents; + 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("Test")); + QWebEngineProfile profile(QStringLiteral("changeHttpUserAgent")); std::unique_ptr<QWebEnginePage> page; page.reset(new QWebEnginePage(&profile)); QVERIFY(loadSync(page.get(), server.url("/hedgehog.html"))); @@ -899,13 +906,13 @@ void tst_QWebEngineProfile::changeHttpAcceptLanguage() TestServer server; QVERIFY(server.start()); - QVector<QByteArray> languages; + 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("Test")); + QWebEngineProfile profile(QStringLiteral("changeHttpAcceptLanguage")); std::unique_ptr<QWebEnginePage> page; page.reset(new QWebEnginePage(&profile)); QVERIFY(loadSync(page.get(), server.url("/hedgehog.html"))); @@ -921,25 +928,6 @@ void tst_QWebEngineProfile::changeHttpAcceptLanguage() QVERIFY(server.stop()); } -void tst_QWebEngineProfile::changeUseForGlobalCertificateVerification() -{ - TestServer server; - QVERIFY(server.start()); - - // Check that we don't crash - - QWebEngineProfile profile(QStringLiteral("Test")); - std::unique_ptr<QWebEnginePage> page; - page.reset(new QWebEnginePage(&profile)); - QVERIFY(loadSync(page.get(), server.url("/hedgehog.html"))); - page.reset(); - profile.setUseForGlobalCertificateVerification(true); - page.reset(new QWebEnginePage(&profile)); - QVERIFY(loadSync(page.get(), server.url("/hedgehog.html"))); - // Don't check for error: there can be disconnects during GET hedgehog.png. - (void)server.stop(); -} - void tst_QWebEngineProfile::changePersistentCookiesPolicy() { TestServer server; @@ -947,7 +935,7 @@ void tst_QWebEngineProfile::changePersistentCookiesPolicy() AutoDir dataDir("./tst_QWebEngineProfile_dataDir"); - QWebEngineProfile profile(QStringLiteral("Test")); + QWebEngineProfile profile(QStringLiteral("changePersistentCookiesPolicy")); QWebEnginePage page(&profile); profile.setPersistentStoragePath(dataDir.path()); @@ -983,29 +971,29 @@ void tst_QWebEngineProfile::initiator() QWebEnginePage page(&profile, nullptr); QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); page.load(QUrl("about:blank")); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + 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'"); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); loadFinishedSpy.clear(); QCOMPARE(handler.initiator, QUrl("null")); page.setHtml("", QUrl("http://test:123/foo%20bar")); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(loadFinishedSpy.size(), 1); loadFinishedSpy.clear(); // baseUrl determines the origin, so QUrl("http://test:123") evaluateJavaScriptSync(&page, "window.location = 'foo:bar'"); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + 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")); - QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 10000); QCOMPARE(handler.initiator, QUrl()); } @@ -1021,7 +1009,7 @@ 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; @@ -1037,7 +1025,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/tst_qwebenginescript.cpp b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp index 32384aec2..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,10 +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() @@ -180,7 +185,7 @@ void tst_QWebEngineScript::loadEvents() // Single frame / setHtml page.setHtml(QStringLiteral("<!DOCTYPE html><html><head><title>mr</title></head><body></body></html>")); - QTRY_COMPARE_WITH_TIMEOUT(page.spy.count(), 1, 20000); + 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())); @@ -188,14 +193,14 @@ void tst_QWebEngineScript::loadEvents() // After discard page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); page.setLifecycleState(QWebEnginePage::LifecycleState::Active); - QTRY_COMPARE_WITH_TIMEOUT(page.spy.count(), 1, 20000); + 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_WITH_TIMEOUT(page.spy.count(), 1, 20000); + 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())); @@ -206,7 +211,7 @@ void tst_QWebEngineScript::loadEvents() // Cross-process navigation page.load(QUrl("chrome://gpu")); - QTRY_COMPARE_WITH_TIMEOUT(page.spy.count(), 1, 20000); + 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())); @@ -214,9 +219,9 @@ void tst_QWebEngineScript::loadEvents() // Using window.open from JS QVERIFY(profile.pages.size() == 1); page.load(QUrl("qrc:/resources/test_window_open.html")); - QTRY_COMPARE(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())); @@ -267,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()); @@ -276,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()); @@ -294,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)); } @@ -320,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); } @@ -413,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() @@ -421,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() @@ -453,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( @@ -486,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); @@ -497,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())); @@ -511,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() @@ -521,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); @@ -553,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); } @@ -589,6 +594,36 @@ 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() @@ -612,6 +647,86 @@ document.title = 'New title'; 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 3290cb588..000000000 --- a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.qrc +++ /dev/null @@ -1,11 +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/title_a.html</file> - <file>resources/title_b.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 a09901e69..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); - // TODO: note this setting is flaky, consider settings().commit() - return true; - } -}; - -void tst_QWebEngineSettings::setInAcceptNavigationRequest() -{ - NavigationRequestOverride page; - QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); - QWebEngineSettings::defaultSettings()->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 266f08886..26f2da4bb 100644 --- a/tests/auto/widgets/qwebengineview/BLACKLIST +++ b/tests/auto/widgets/qwebengineview/BLACKLIST @@ -1,8 +1,12 @@ -[microFocusCoordinates] -osx - -[textSelectionOutOfInputField] +[mixLangLocale:eu_ES] * -[visibilityState3] +[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 5e16361c5..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,44 +67,6 @@ do { \ QCOMPARE((__expr), __expected); \ } while (0) -static QTouchDevice* s_touchDevice = nullptr; - -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(); @@ -106,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); @@ -142,6 +112,7 @@ private Q_SLOTS: void changePage(); void reusePage_data(); void reusePage(); + void setLoadedPage(); void microFocusCoordinates(); void focusInputTypes(); void unhandledKeyEventPropagation(); @@ -162,14 +133,13 @@ private Q_SLOTS: void doNotBreakLayout(); void changeLocale(); + void mixLangLocale_data(); + void mixLangLocale(); void inputMethodsTextFormat_data(); void inputMethodsTextFormat(); void keyboardEvents(); void keyboardFocusAfterPopup(); void mouseClick(); - void touchTap(); - void touchTapAndHold(); - void touchTapAndHoldCancelled(); void postData(); void inputFieldOverridesShortcuts(); @@ -188,7 +158,7 @@ private Q_SLOTS: void mouseLeave(); -#ifndef QT_NO_CLIPBOARD +#if QT_CONFIG(clipboard) void globalMouseSelection(); #endif void noContextMenu(); @@ -202,6 +172,7 @@ private Q_SLOTS: void jsKeyboardEvent_data(); void jsKeyboardEvent(); void deletePage(); + void autoDeleteOnExternalPageDelete(); void closeOpenerTab(); void switchPage(); void setPageDeletesImplicitPage(); @@ -210,13 +181,20 @@ 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. // It is only called once. void tst_QWebEngineView::initTestCase() { - s_touchDevice = QTest::createTouchDevice(); } // This will be called after the last test function is executed. @@ -233,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() @@ -331,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() @@ -401,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()); @@ -434,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 @@ -508,7 +604,7 @@ void tst_QWebEngineView::microFocusCoordinates() 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::ImCursorRectangle).isValid()); QVariant currentMicroFocus = webView.focusProxy()->inputMethodQuery(Qt::ImCursorRectangle); @@ -518,8 +614,8 @@ void tst_QWebEngineView::microFocusCoordinates() 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); @@ -550,7 +646,8 @@ void tst_QWebEngineView::focusInputTypes() 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"); @@ -589,7 +686,8 @@ void tst_QWebEngineView::focusInputTypes() 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, {}, textInputCenter); @@ -603,7 +701,8 @@ void tst_QWebEngineView::focusInputTypes() 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"); @@ -616,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() @@ -632,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")); @@ -665,32 +765,42 @@ 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))); @@ -927,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; @@ -961,7 +1071,7 @@ public: private: int m_eventCounter; - QVector<QString> m_eventHistory; + QList<QString> m_eventHistory; }; void tst_QWebEngineView::doNotSendMouseKeyboardEventsWhenDisabled() @@ -982,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. @@ -1029,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()"); @@ -1142,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. @@ -1165,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() @@ -1203,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/"); @@ -1210,7 +1353,7 @@ 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]"), Qt::SkipEmptyParts); @@ -1220,7 +1363,7 @@ void tst_QWebEngineView::changeLocale() 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]"), Qt::SkipEmptyParts); @@ -1233,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]"), 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"); @@ -1272,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); @@ -1306,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() @@ -1316,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"; @@ -1435,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")); } @@ -1474,7 +1666,7 @@ void tst_QWebEngineView::mouseClick() textInputCenter = elementCenter(view.page(), "input"); 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 @@ -1489,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, {}, textInputCenter); QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 2); + QCOMPARE(selectionChangedSpy.size(), 2); QVERIFY(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString().isEmpty()); // Triple click @@ -1510,182 +1702,16 @@ 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, {}, textInputCenter); QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 3); + QCOMPARE(selectionChangedSpy.size(), 3); QVERIFY(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString().isEmpty()); } -void tst_QWebEngineView::touchTap() -{ -#if defined(Q_OS_MACOS) - QSKIP("Synthetic touch events are not supported on macOS"); -#endif - - QWebEngineView view; - view.show(); - view.resize(200, 200); - QVERIFY(QTest::qWaitForWindowExposed(&view)); - - QSignalSpy loadFinishedSpy(&view, &QWebEngineView::loadFinished); - - view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false); - view.setHtml("<html><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' width='150px' type='text' value='The Qt Company2' /></form>" - "</body></html>"); - QVERIFY(loadFinishedSpy.wait()); - QVERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty()); - - auto singleTap = [](QWidget* target, 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(view.focusProxy(), elementCenter(view.page(), "text")); - QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty()); - QTRY_VERIFY(!view.hasSelection()); - - // Single tap inside the input field focuses it without selecting the text - singleTap(view.focusProxy(), elementCenter(view.page(), "input")); - QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input")); - QTRY_VERIFY(!view.hasSelection()); - - // Single tap on the div clears the input field focus - singleTap(view.focusProxy(), elementCenter(view.page(), "notext")); - QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty()); - - // Double tap on text still doesn't trigger a selection - singleTap(view.focusProxy(), elementCenter(view.page(), "text")); - singleTap(view.focusProxy(), elementCenter(view.page(), "text")); - QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty()); - QTRY_VERIFY(!view.hasSelection()); - - // Double tap inside the input field focuses it and selects the word under it - singleTap(view.focusProxy(), elementCenter(view.page(), "input")); - singleTap(view.focusProxy(), elementCenter(view.page(), "input")); - QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), 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(view.focusProxy(), elementCenter(view.page(), "notext")); - singleTap(view.focusProxy(), elementCenter(view.page(), "notext")); - QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty()); - QTRY_VERIFY(!view.hasSelection()); -} - -void tst_QWebEngineView::touchTapAndHold() -{ -#if defined(Q_OS_MACOS) - QSKIP("Synthetic touch events are not supported on macOS"); -#endif - - QWebEngineView view; - view.show(); - view.resize(200, 200); - QVERIFY(QTest::qWaitForWindowExposed(&view)); - - QSignalSpy loadFinishedSpy(&view, &QWebEngineView::loadFinished); - - view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false); - view.setHtml("<html><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' width='150px' type='text' value='The Qt Company2' /></form>" - "</body></html>"); - QVERIFY(loadFinishedSpy.wait()); - QVERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty()); - - auto tapAndHold = [](QWidget* target, 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(view.focusProxy(), elementCenter(view.page(), "text")); - QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty()); - QTRY_COMPARE(view.selectedText(), QStringLiteral("Company")); - - // Tap-and-hold inside the input field focuses it and selects the word under it - tapAndHold(view.focusProxy(), elementCenter(view.page(), "input")); - QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), 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(view.focusProxy(), elementCenter(view.page(), "notext")); - QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty()); - QTRY_VERIFY(!view.hasSelection()); - QTRY_VERIFY(QApplication::activePopupWidget() != nullptr); - - QApplication::activePopupWidget()->close(); - QVERIFY(QApplication::activePopupWidget() == nullptr); -#endif -} - -void tst_QWebEngineView::touchTapAndHoldCancelled() -{ -#if defined(Q_OS_MACOS) - QSKIP("Synthetic touch events are not supported on macOS"); -#endif - - QWebEngineView view; - view.show(); - view.resize(200, 200); - QVERIFY(QTest::qWaitForWindowExposed(&view)); - - QSignalSpy loadFinishedSpy(&view, &QWebEngineView::loadFinished); - - view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false); - view.setHtml("<html><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' width='150px' type='text' value='The Qt Company2' /></form>" - "</body></html>"); - QVERIFY(loadFinishedSpy.wait()); - QVERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty()); - - auto cancelledTapAndHold = [](QWidget* target, 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(view.focusProxy(), elementCenter(view.page(), "text")); - 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(view.focusProxy(), elementCenter(view.page(), "input")); - 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(view.focusProxy(), elementCenter(view.page(), "notext")); - QVERIFY(QApplication::activePopupWidget() == nullptr); -#endif -} - void tst_QWebEngineView::postData() { QMap<QString, QString> postData; @@ -1710,12 +1736,12 @@ void tst_QWebEngineView::postData() // examine request QStringList request = lines[0].split(" ", Qt::SkipEmptyParts); - bool requestOk = request.length() > 2 + 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] == "/"; @@ -1723,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") @@ -1741,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 @@ -1819,11 +1845,10 @@ 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))); @@ -1848,7 +1873,7 @@ void tst_QWebEngineView::inputFieldOverridesShortcuts() }; // 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")); @@ -1884,7 +1909,7 @@ void tst_QWebEngineView::inputFieldOverridesShortcuts() // A Ctrl-1 action is no default Qt key binding and should be triggerable. 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); + action->setShortcut(Qt::CTRL | Qt::Key_1); QTest::keyClick(view.windowHandle(), Qt::Key_1, Qt::ControlModifier); QTRY_VERIFY(actionTriggered); QCOMPARE(inputFieldValue(), QString("yxx")); @@ -1941,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; @@ -2050,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, {}, textInputCenter); - QTRY_COMPARE(testContext.infos.count(), 2); + 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); @@ -2068,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")); @@ -2077,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!")); @@ -2086,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!")); @@ -2101,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. @@ -2127,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!")); @@ -2142,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!")); @@ -2156,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); @@ -2173,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!")); @@ -2183,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(); } @@ -2202,6 +2219,7 @@ 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, {}, textInputCenter); @@ -2226,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); } { @@ -2235,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 @@ -2299,6 +2317,7 @@ 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 @@ -2310,24 +2329,24 @@ void tst_QWebEngineView::textSelectionInInputField() 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); @@ -2339,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); @@ -2349,7 +2368,7 @@ void tst_QWebEngineView::textSelectionInInputField() // Deselect the selection (this moves the current cursor to the end of the text) 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); @@ -2362,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); @@ -2372,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(); @@ -2381,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, {}, view.geometry().center()); - QCOMPARE(selectionChangedSpy.count(), 0); + 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, {}, view.geometry().center()); - QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 2); + 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")); @@ -2417,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()); @@ -2429,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()); @@ -2440,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, {}, textInputCenter); - QVERIFY(selectionChangedSpy.wait()); + 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, {}, view.geometry().center()); - QVERIFY(selectionChangedSpy.wait()); - QCOMPARE(selectionChangedSpy.count(), 4); + QTRY_COMPARE(selectionChangedSpy.size(), 4); QVERIFY(!view.hasSelection()); QVERIFY(view.page()->selectedText().isEmpty()); } @@ -2508,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; @@ -2556,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. { @@ -2568,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); @@ -2589,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'. { @@ -2601,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. { @@ -2614,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. @@ -2628,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. { @@ -2641,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. @@ -2659,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. { @@ -2672,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. @@ -2682,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); @@ -2696,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 @@ -2716,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(); @@ -2741,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")); @@ -2758,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() @@ -2773,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()); @@ -2897,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")); @@ -2911,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")); @@ -2927,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")); @@ -2941,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")); @@ -2959,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")); @@ -2972,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 ")); @@ -3019,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")); @@ -3038,6 +3057,7 @@ void tst_QWebEngineView::imeCompositionQueryEvent() } QInputMethodQueryEvent srrndTextQuery(Qt::ImSurroundingText); + QInputMethodQueryEvent absolutePosQuery(Qt::ImAbsolutePosition); QInputMethodQueryEvent cursorPosQuery(Qt::ImCursorPosition); QInputMethodQueryEvent anchorPosQuery(Qt::ImAnchorPosition); @@ -3049,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 { @@ -3072,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()) { @@ -3103,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, {}, 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 @@ -3142,7 +3215,7 @@ 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()); } @@ -3182,7 +3255,7 @@ void tst_QWebEngineView::contextMenu() view.load(QUrl("about:blank")); view.resize(640, 480); view.show(); - QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(loadSpy.size(), 1); QVERIFY(view.findChildren<QMenu *>().isEmpty()); QTest::mouseMove(view.windowHandle(), QPoint(10,10)); @@ -3190,9 +3263,9 @@ void tst_QWebEngineView::contextMenu() // 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 *>().count() > 0; }, 500)); + QVERIFY(!QTest::qWaitFor([&view] () { return view.findChildren<QMenu *>().size() > 0; }, 500)); } else { - QTRY_COMPARE(view.findChildren<QMenu *>().count(), childrenCount); + QTRY_COMPARE(view.findChildren<QMenu *>().size(), childrenCount); if (isCustomMenu) { QCOMPARE(view.findChildren<QMenu *>().first(), customMenu); } @@ -3258,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; @@ -3311,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() @@ -3335,7 +3420,7 @@ void tst_QWebEngineView::webUIURLs() view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); view.load(url); - QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.count(), 1, 30000); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 90000); QCOMPARE(loadFinishedSpy.takeFirst().at(0).toBool(), supported); } @@ -3344,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(); @@ -3359,7 +3444,7 @@ void tst_QWebEngineView::visibilityState2() view.show(); view.load(QStringLiteral("about:blank")); view.hide(); - QVERIFY(spy.count() || spy.wait()); + QVERIFY(spy.size() || spy.wait()); QVERIFY(spy.takeFirst().takeFirst().toBool()); QCOMPARE(evaluateJavaScriptSync(view.page(), "document.visibilityState").toString(), QStringLiteral("hidden")); } @@ -3372,8 +3457,8 @@ void tst_QWebEngineView::visibilityState3() QSignalSpy spy2(&page2, &QWebEnginePage::loadFinished); page1.load(QStringLiteral("about:blank")); page2.load(QStringLiteral("about:blank")); - QVERIFY(spy1.count() || spy1.wait()); - QVERIFY(spy2.count() || spy2.wait()); + QVERIFY(spy1.size() || spy1.wait()); + QVERIFY(spy2.size() || spy2.wait()); QWebEngineView view; view.setPage(&page1); view.show(); @@ -3437,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 { @@ -3464,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); @@ -3483,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(); @@ -3526,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 } @@ -3547,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 } @@ -3556,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 a4a0e34ff..188c112e4 100644 --- a/tests/auto/widgets/schemes/tst_schemes.cpp +++ b/tests/auto/widgets/schemes/tst_schemes.cpp @@ -1,47 +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: @@ -118,5 +121,161 @@ void tst_Schemes::unknownUrlSchemePolicy() 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) #include "tst_schemes.moc" 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 801e2a76c..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 { @@ -47,22 +22,19 @@ public: 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, {}, 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 cb58f4243..000000000 --- a/tests/auto/widgets/util.h +++ /dev/null @@ -1,194 +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> -#include <qwebengineview.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(int timeout = 20000) { - const int step = 1000; - int elapsed = 0; - while (elapsed < timeout && !called) { - timeoutTimer.start(step); - eventLoop.exec(); - elapsed += step; - } - 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, {}, 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(); -} - -static inline bool loadSync(QWebEnginePage *page, const QUrl &url, bool ok = true) -{ - QSignalSpy spy(page, &QWebEnginePage::loadFinished); - page->load(url); - return (!spy.empty() || spy.wait(20000)) && (spy.front().value(0).toBool() == ok); -} - -static inline bool loadSync(QWebEngineView *view, const QUrl &url, bool ok = true) -{ - return loadSync(view->page(), url, ok); -} - -#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 6d65eecb5..000000000 --- a/tests/auto/widgets/widgets.pro +++ /dev/null @@ -1,51 +0,0 @@ -include($$QTWEBENGINE_OUT_ROOT/src/core/qtwebenginecore-config.pri) # workaround for QTBUG-68093 -QT_FOR_CONFIG += webenginecore webenginecore-private - -TEMPLATE = subdirs - -SUBDIRS += \ - 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(ssl) { - SUBDIRS += certificateerror -} - -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 |