diff options
Diffstat (limited to 'tests/auto/widgets/qwebenginepage')
5 files changed, 833 insertions, 125 deletions
diff --git a/tests/auto/widgets/qwebenginepage/BLACKLIST b/tests/auto/widgets/qwebenginepage/BLACKLIST index 7eb97b3bb..01d1ffe3d 100644 --- a/tests/auto/widgets/qwebenginepage/BLACKLIST +++ b/tests/auto/widgets/qwebenginepage/BLACKLIST @@ -5,11 +5,15 @@ osx windows macos # Can't move cursor (QTBUG-76312) -[acceptNavigationRequestNavigationType] -b2qt arm - [comboBoxPopupPositionAfterMove] macos [comboBoxPopupPositionAfterChildMove] macos + +[backgroundColor] +macos + +[dynamicFrame] +ubuntu-22.04 + diff --git a/tests/auto/widgets/qwebenginepage/CMakeLists.txt b/tests/auto/widgets/qwebenginepage/CMakeLists.txt index a15bb6e06..f63d6211c 100644 --- a/tests/auto/widgets/qwebenginepage/CMakeLists.txt +++ b/tests/auto/widgets/qwebenginepage/CMakeLists.txt @@ -24,6 +24,7 @@ set(tst_qwebenginepage_resource_files "resources/content.html" "resources/dynamicFrame.html" "resources/foo.txt" + "resources/fontaccess.html" "resources/frame_a.html" "resources/frame_c.html" "resources/framedindex.html" 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/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 64acdb3d5..976a650ec 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 @@ -36,6 +36,7 @@ #include <QPaintEngine> #include <QPushButton> #include <QScreen> +#include <QWheelEvent> #if defined(QT_STATEMACHINE_LIB) # include <QStateMachine> #endif @@ -49,7 +50,9 @@ #include <qnetworkcookiejar.h> #include <qnetworkreply.h> #include <qnetworkrequest.h> +#include <qwebengineclienthints.h> #include <qwebenginedownloadrequest.h> +#include <qwebenginedesktopmediarequest.h> #include <qwebenginefilesystemaccessrequest.h> #include <qwebenginefindtextresult.h> #include <qwebenginefullscreenrequest.h> @@ -64,10 +67,12 @@ #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 <qwebenginepermission.h> #include <qimagewriter.h> #include <QColorSpace> #include <QQuickRenderControl> @@ -113,10 +118,13 @@ private Q_SLOTS: 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(); @@ -165,10 +173,10 @@ private Q_SLOTS: void runJavaScriptDisabled(); void runJavaScriptFromSlot(); void fullScreenRequested(); - void quotaRequested(); - + void requestQuota_data(); + void requestQuota(); - // Tests from tst_QWebEngineFrame + // Tests from pre-6.8 tst_QWebEngineFrame void symmetricUrl(); void progressSignal(); void urlChange(); @@ -227,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(); @@ -263,15 +277,24 @@ private Q_SLOTS: 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-" @@ -279,17 +302,24 @@ private: return tmpd; } - QScopedPointer<QPointingDevice> s_touchDevice; - void makeClick(QWindow *window, bool withTouch = false, const QPoint &p = QPoint()) { + 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 { - if (!s_touchDevice) - s_touchDevice.reset(QTest::createTouchDevice()); 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() @@ -421,12 +451,12 @@ public: return true; } public Q_SLOTS: - void requestPermission(const QUrl &origin, QWebEnginePage::Feature feature) + void requestPermission(QWebEnginePermission permission) { if (m_allowGeolocation) - setFeaturePermission(origin, feature, PermissionGrantedByUser); + permission.grant(); else - setFeaturePermission(origin, feature, PermissionDeniedByUser); + permission.deny(); } public: @@ -439,6 +469,7 @@ private: bool m_allowGeolocation; }; +#ifndef Q_OS_MACOS void tst_QWebEnginePage::geolocationRequestJS_data() { QTest::addColumn<bool>("allowed"); @@ -454,10 +485,11 @@ void tst_QWebEnginePage::geolocationRequestJS() QWebEngineView view; JSTestPage *newPage = new JSTestPage(&view); view.setPage(newPage); + newPage->profile()->setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions); newPage->setGeolocationPermission(allowed); - connect(newPage, SIGNAL(featurePermissionRequested(const QUrl&, QWebEnginePage::Feature)), - newPage, SLOT(requestPermission(const QUrl&, QWebEnginePage::Feature))); + connect(newPage, SIGNAL(permissionRequested(QWebEnginePermission)), + newPage, SLOT(requestPermission(QWebEnginePermission))); QSignalSpy spyLoadFinished(newPage, SIGNAL(loadFinished(bool))); newPage->setHtml(QString("<html><body>test</body></html>"), QUrl("qrc://secure/origin")); @@ -478,6 +510,7 @@ void tst_QWebEnginePage::geolocationRequestJS() QEXPECT_FAIL("", "No location service available.", Continue); QCOMPARE(result, errorCode); } +#endif void tst_QWebEnginePage::loadFinished() { @@ -605,6 +638,7 @@ public: QWebEngineNavigationRequest::NavigationType type; QUrl url; bool isMainFrame; + bool hasFormData; }; QList<Navigation> navigations; @@ -622,6 +656,7 @@ private Q_SLOTS: n.url = request.url(); n.type = request.navigationType(); n.isMainFrame = request.isMainFrame(); + n.hasFormData = request.hasFormData(); navigations.append(n); request.accept(); } @@ -639,9 +674,33 @@ private Q_SLOTS: } }; -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))); @@ -664,19 +723,19 @@ void tst_QWebEnginePage::acceptNavigationRequestNavigationType() QTRY_COMPARE(loadSpy.size(), 4); QTRY_COMPARE(page.navigations.size(), 4); - page.load(QUrl("qrc:///resources/reload.html")); - QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 6, 20000); - QTRY_COMPARE(page.navigations.size(), 6); - QList<QWebEngineNavigationRequest::NavigationType> expectedList; expectedList << QWebEngineNavigationRequest::TypedNavigation << QWebEngineNavigationRequest::TypedNavigation << QWebEngineNavigationRequest::BackForwardNavigation - << QWebEngineNavigationRequest::ReloadNavigation - << QWebEngineNavigationRequest::TypedNavigation - << QWebEngineNavigationRequest::RedirectNavigation; + << QWebEngineNavigationRequest::ReloadNavigation; // client side redirect + page.load(QUrl("qrc:///resources/reload.html")); + 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); @@ -1345,7 +1404,11 @@ void tst_QWebEnginePage::comboBoxPopupPositionAfterMove_data() 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(); @@ -1357,7 +1420,7 @@ void tst_QWebEnginePage::comboBoxPopupPositionAfterMove() QTRY_COMPARE(spyLoadFinished.size(), 1); const auto oldTlws = QGuiApplication::topLevelWindows(); QFETCH(bool, withTouch); - QWindow *window = view.windowHandle(); + QPointer<QWindow> window = view.windowHandle(); auto pos = elementCenter(view.page(), "foo"); makeClick(window, withTouch, pos); QWindow *popup = nullptr; @@ -1407,6 +1470,9 @@ void tst_QWebEnginePage::comboBoxPopupPositionAfterChildMove_data() 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); @@ -1417,6 +1483,7 @@ 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(); @@ -1430,7 +1497,7 @@ void tst_QWebEnginePage::comboBoxPopupPositionAfterChildMove() const auto oldTlws = QGuiApplication::topLevelWindows(); QFETCH(bool, withTouch); - QWindow *window = view.window()->windowHandle(); + QPointer<QWindow> window = view.window()->windowHandle(); makeClick(window, withTouch, view.mapTo(view.window(), elementCenter(view.page(), "foo"))); QWindow *popup = nullptr; @@ -1589,11 +1656,13 @@ public: GetUserMediaTestPage() : m_gotRequest(false) , m_loadSucceeded(false) + , m_permission(nullptr) { - connect(this, &QWebEnginePage::featurePermissionRequested, this, &GetUserMediaTestPage::onFeaturePermissionRequested); + connect(this, &QWebEnginePage::permissionRequested, this, &GetUserMediaTestPage::onPermissionRequested); connect(this, &QWebEnginePage::loadFinished, [this](bool success){ m_loadSucceeded = success; }); + profile()->setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions); // We need to load content from a resource in order for the securityOrigin to be valid. load(QUrl("qrc:///resources/content.html")); } @@ -1627,18 +1696,20 @@ public: void rejectPendingRequest() { - setFeaturePermission(m_requestSecurityOrigin, m_requestedFeature, QWebEnginePage::PermissionDeniedByUser); + QVERIFY(m_permission); + m_permission->deny(); m_gotRequest = false; } void acceptPendingRequest() { - setFeaturePermission(m_requestSecurityOrigin, m_requestedFeature, QWebEnginePage::PermissionGrantedByUser); + QVERIFY(m_permission); + m_permission->grant(); m_gotRequest = false; } - bool gotFeatureRequest(QWebEnginePage::Feature feature) + bool gotFeatureRequest(QWebEnginePermission::Feature feature) { - return m_gotRequest && m_requestedFeature == feature; + return m_gotRequest && m_permission && m_permission->feature() == feature; } bool gotFeatureRequest() const @@ -1652,50 +1723,47 @@ public: } private Q_SLOTS: - void onFeaturePermissionRequested(const QUrl &securityOrigin, QWebEnginePage::Feature feature) + void onPermissionRequested(QWebEnginePermission permission) { - m_requestedFeature = feature; - m_requestSecurityOrigin = securityOrigin; + m_permission.reset(new QWebEnginePermission(permission)); m_gotRequest = true; } private: bool m_gotRequest; bool m_loadSucceeded; - QWebEnginePage::Feature m_requestedFeature; - QUrl m_requestSecurityOrigin; - + std::unique_ptr<QWebEnginePermission> m_permission; }; void tst_QWebEnginePage::getUserMediaRequest_data() { QTest::addColumn<QString>("call"); - QTest::addColumn<QWebEnginePage::Feature>("feature"); + QTest::addColumn<QWebEnginePermission::Feature>("feature"); QTest::addRow("device audio") - << "getUserMedia({audio: true})" << QWebEnginePage::MediaAudioCapture; + << "getUserMedia({audio: true})" << QWebEnginePermission::MediaAudioCapture; QTest::addRow("device video") - << "getUserMedia({video: true})" << QWebEnginePage::MediaVideoCapture; + << "getUserMedia({video: true})" << QWebEnginePermission::MediaVideoCapture; QTest::addRow("device audio+video") - << "getUserMedia({audio: true, video: true})" << QWebEnginePage::MediaAudioVideoCapture; + << "getUserMedia({audio: true, video: true})" << QWebEnginePermission::MediaAudioVideoCapture; QTest::addRow("desktop video") << "getUserMedia({video: { mandatory: { chromeMediaSource: 'desktop' }}})" - << QWebEnginePage::DesktopVideoCapture; + << QWebEnginePermission::DesktopVideoCapture; QTest::addRow("desktop audio+video") << "getUserMedia({audio: { mandatory: { chromeMediaSource: 'desktop' }}, video: { mandatory: { chromeMediaSource: 'desktop' }}})" - << QWebEnginePage::DesktopAudioVideoCapture; + << QWebEnginePermission::DesktopAudioVideoCapture; QTest::addRow("display video") - << "getDisplayMedia()" << QWebEnginePage::DesktopVideoCapture; + << "getDisplayMedia()" << QWebEnginePermission::DesktopVideoCapture; } void tst_QWebEnginePage::getUserMediaRequest() { QFETCH(QString, call); - QFETCH(QWebEnginePage::Feature, feature); + QFETCH(QWebEnginePermission::Feature, feature); GetUserMediaTestPage page; QWebEngineView view; - if (feature == QWebEnginePage::DesktopVideoCapture || feature == QWebEnginePage::DesktopAudioVideoCapture) { + if (feature == QWebEnginePermission::DesktopVideoCapture || feature == QWebEnginePermission::DesktopAudioVideoCapture) { // Desktop capture needs to be on a desktop. view.setPage(&page); view.resize(640, 480); @@ -1765,7 +1833,7 @@ void tst_QWebEnginePage::getUserMediaRequestSettingDisabled() void tst_QWebEnginePage::getUserMediaRequestDesktopVideoManyPages() { const QString constraints = QStringLiteral("{video: { mandatory: { chromeMediaSource: 'desktop' }}}"); - const QWebEnginePage::Feature feature = QWebEnginePage::DesktopVideoCapture; + const QWebEnginePermission::Feature feature = QWebEnginePermission::DesktopVideoCapture; std::vector<GetUserMediaTestPage> pages(10); // Desktop capture needs to be on a desktop @@ -1798,7 +1866,7 @@ void tst_QWebEnginePage::getUserMediaRequestDesktopVideoManyPages() void tst_QWebEnginePage::getUserMediaRequestDesktopVideoManyRequests() { const QString constraints = QStringLiteral("{video: { mandatory: { chromeMediaSource: 'desktop' }}}"); - const QWebEnginePage::Feature feature = QWebEnginePage::DesktopVideoCapture; + const QWebEnginePermission::Feature feature = QWebEnginePermission::DesktopVideoCapture; GetUserMediaTestPage page; // Desktop capture needs to be on a desktop @@ -2014,8 +2082,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); @@ -2023,35 +2101,23 @@ void tst_QWebEnginePage::quotaRequested() page.load(QUrl("qrc:///resources/content.html")); QVERIFY(loadFinishedSpy.wait()); - connect(&page, &QWebEnginePage::quotaRequested, - [] (QWebEngineQuotaRequest request) - { - if (request.requestedSize() <= 5000) - request.accept(); - else - request.reject(); - }); - - evaluateJavaScriptSync(&page, - "navigator.webkitPersistentStorage.requestQuota(1024, function(grantedSize) {" \ - "console.log(grantedSize);" \ - "});"); + 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.size(), 2); - QTRY_COMPARE(page.messages[1], QString("1024")); - - evaluateJavaScriptSync(&page, - "navigator.webkitPersistentStorage.queryUsageAndQuota(function(usedBytes, grantedBytes) {" \ - "console.log(usedBytes + ', ' + grantedBytes);" \ - "});"); + 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[2], QString("0, 1024")); + QTRY_COMPARE(page.messages[1], QString("0")); + QTRY_VERIFY(page.messages[2].toLongLong() >= 1024); } void tst_QWebEnginePage::symmetricUrl() @@ -2061,14 +2127,14 @@ void tst_QWebEnginePage::symmetricUrl() QVERIFY(view.url().isEmpty()); - QCOMPARE(view.history()->count(), 1); + QCOMPARE(view.history()->count(), 0); QUrl dataUrl("data:text/html,<h1>Test"); view.setUrl(dataUrl); view.show(); QCOMPARE(view.url(), dataUrl); - QCOMPARE(view.history()->count(), 1); + QCOMPARE(view.history()->count(), 0); // loading is _not_ immediate, so the text isn't set just yet. QVERIFY(toPlainTextSync(view.page()).isEmpty()); @@ -2381,7 +2447,7 @@ void tst_QWebEnginePage::setHtmlWithBaseURL() QCOMPARE(evaluateJavaScriptSync(&page, "document.images[0].height").toInt(), 128); // no history item has to be added. - QCOMPARE(m_view->page()->history()->count(), 1); + QCOMPARE(m_view->page()->history()->count(), 0); } class MyPage : public QWebEnginePage @@ -2482,7 +2548,8 @@ void tst_QWebEnginePage::baseUrl() void tst_QWebEnginePage::scrollPosition() { // enlarged image in a small viewport, to provoke the scrollbars to appear - QString html("<html><body><img src='qrc:/image.png' height=500 width=500/></body></html>"); + QString html( + "<html><body><img src='qrc:/resources/image.png' height=500 width=500/></body></html>"); QWebEngineView view; view.setFixedSize(200,200); @@ -2496,8 +2563,8 @@ void tst_QWebEnginePage::scrollPosition() // 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(); @@ -2811,7 +2878,7 @@ void tst_QWebEnginePage::setUrlHistory() int expectedLoadFinishedCount = 0; QSignalSpy spy(m_page, SIGNAL(loadFinished(bool))); - QCOMPARE(m_page->history()->count(), 1); + QCOMPARE(m_page->history()->count(), 0); m_page->setUrl(QUrl()); expectedLoadFinishedCount++; @@ -2885,7 +2952,7 @@ void tst_QWebEnginePage::setUrlUsingStateObject() QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); int expectedUrlChangeCount = 0; - QCOMPARE(m_page->history()->count(), 1); + QCOMPARE(m_page->history()->count(), 0); url = QUrl("qrc:/resources/test1.html"); m_page->setUrl(url); @@ -3064,7 +3131,7 @@ void tst_QWebEnginePage::loadInSignalHandlers() URLSetter setter(m_page, signal, type, urlForSetter); QSignalSpy spy(&setter, &URLSetter::finished); m_page->load(url); - QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 20000); + QTRY_VERIFY_WITH_TIMEOUT(spy.size() >= 1, 20000); QCOMPARE(m_page->url(), urlForSetter); } @@ -3274,23 +3341,6 @@ void tst_QWebEnginePage::mouseMovementProperties() 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.size() != 2) { - qWarning("elementCenter failed."); - return QPoint(); - } - - return QPoint(rectList.at(0).toInt(), rectList.at(1).toInt()); -} - void tst_QWebEnginePage::viewSource() { TestPage page; @@ -3533,6 +3583,8 @@ void tst_QWebEnginePage::devTools() QCOMPARE(inspectedPage2.inspectedPage(), nullptr); QCOMPARE(devToolsPage.devToolsPage(), nullptr); QCOMPARE(devToolsPage.inspectedPage(), nullptr); + + QVERIFY(!inspectedPage1.devToolsId().isEmpty()); } void tst_QWebEnginePage::openLinkInDifferentProfile() @@ -3710,7 +3762,7 @@ void tst_QWebEnginePage::openLinkInNewPage() QCOMPARE(page1.history()->count(), 1); else QCOMPARE(page1.history()->count(), 2); - QCOMPARE(page2.history()->count(), 1); + QCOMPARE(page2.history()->count(), 0); break; case Effect::LoadInOther: QTRY_COMPARE(page2.spy.size(), 1); @@ -3743,20 +3795,32 @@ void tst_QWebEnginePage::dynamicFrame() struct NotificationPage : ConsolePage { Q_OBJECT - const QWebEnginePage::PermissionPolicy policy; + const QWebEnginePermission::State policy; public: - NotificationPage(QWebEnginePage::PermissionPolicy ppolicy) : policy(ppolicy) { + NotificationPage(QWebEnginePermission::State ppolicy) : policy(ppolicy) { connect(this, &QWebEnginePage::loadFinished, [load = spyLoad.ref()] (bool result) mutable { load(result); }); - connect(this, &QWebEnginePage::featurePermissionRequested, - [this] (const QUrl &origin, QWebEnginePage::Feature feature) { - if (feature != QWebEnginePage::Notifications) + connect(this, &QWebEnginePage::permissionRequested, + [this] (QWebEnginePermission permission) { + if (permission.feature() != QWebEnginePermission::Notifications) return; if (spyRequest.wasCalled()) QFAIL("request executed twise!"); - setFeaturePermission(origin, feature, policy); - spyRequest.ref()(origin); + switch (policy) { + case QWebEnginePermission::Granted: + permission.grant(); + break; + case QWebEnginePermission::Denied: + permission.deny(); + break; + case QWebEnginePermission::Ask: + permission.reset(); + break; + default: + break; + } + spyRequest.ref()(permission.origin()); }); load(QStringLiteral("qrc:///shared/notification.html")); @@ -3776,40 +3840,50 @@ public: void tst_QWebEnginePage::notificationPermission_data() { QTest::addColumn<bool>("setOnInit"); - QTest::addColumn<QWebEnginePage::PermissionPolicy>("policy"); + QTest::addColumn<QWebEnginePermission::State>("policy"); QTest::addColumn<QString>("permission"); - QTest::newRow("denyOnInit") << true << QWebEnginePage::PermissionDeniedByUser << "denied"; - QTest::newRow("deny") << false << QWebEnginePage::PermissionDeniedByUser << "denied"; - QTest::newRow("grant") << false << QWebEnginePage::PermissionGrantedByUser << "granted"; - QTest::newRow("grantOnInit") << true << QWebEnginePage::PermissionGrantedByUser << "granted"; + QTest::newRow("denyOnInit") << true << QWebEnginePermission::Denied << "denied"; + QTest::newRow("deny") << false << QWebEnginePermission::Denied << "denied"; + QTest::newRow("grant") << false << QWebEnginePermission::Granted << "granted"; + QTest::newRow("grantOnInit") << true << QWebEnginePermission::Granted << "granted"; } void tst_QWebEnginePage::notificationPermission() { QFETCH(bool, setOnInit); - QFETCH(QWebEnginePage::PermissionPolicy, policy); + QFETCH(QWebEnginePermission::State, policy); QFETCH(QString, permission); QWebEngineProfile otr; + otr.setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions); QWebEnginePage page(&otr, nullptr); QUrl baseUrl("https://www.example.com/somepage.html"); bool permissionRequested = false, errorState = false; - connect(&page, &QWebEnginePage::featurePermissionRequested, &page, [&] (const QUrl &o, QWebEnginePage::Feature f) { - if (f != QWebEnginePage::Notifications) + connect(&page, &QWebEnginePage::permissionRequested, &page, [&] (QWebEnginePermission permission) { + if (permission.feature() != QWebEnginePermission::Notifications) return; - if (permissionRequested || o != baseUrl.url(QUrl::RemoveFilename)) { - qWarning() << "Unexpected case. Can't proceed." << setOnInit << permissionRequested << o; + if (permissionRequested || permission.origin() != baseUrl.url(QUrl::RemoveFilename)) { + qWarning() << "Unexpected case. Can't proceed." << setOnInit << permissionRequested << permission.origin(); errorState = true; return; } permissionRequested = true; - page.setFeaturePermission(o, f, policy); + + if (policy == QWebEnginePermission::Granted) + permission.grant(); + else + permission.deny(); }); - if (setOnInit) - page.setFeaturePermission(baseUrl, QWebEnginePage::Notifications, policy); + QWebEnginePermission permissionObject = otr.getPermission(baseUrl, QWebEnginePermission::Notifications); + if (setOnInit) { + if (policy == QWebEnginePermission::Granted) + permissionObject.grant(); + else + permissionObject.deny(); + } QSignalSpy spy(&page, &QWebEnginePage::loadFinished); page.setHtml(QString("<html><body>Test</body></html>"), baseUrl); @@ -3818,7 +3892,10 @@ void tst_QWebEnginePage::notificationPermission() QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("Notification.permission")), setOnInit ? permission : QLatin1String("default")); if (!setOnInit) { - page.setFeaturePermission(baseUrl, QWebEnginePage::Notifications, policy); + if (policy == QWebEnginePermission::Granted) + permissionObject.grant(); + else + permissionObject.deny(); QTRY_COMPARE(evaluateJavaScriptSync(&page, QStringLiteral("Notification.permission")), permission); } @@ -3832,7 +3909,7 @@ void tst_QWebEnginePage::notificationPermission() void tst_QWebEnginePage::sendNotification() { - NotificationPage page(QWebEnginePage::PermissionGrantedByUser); + NotificationPage page(QWebEnginePermission::Granted); QVERIFY(page.spyLoad.waitForResult()); page.resetPermission(); @@ -3871,6 +3948,176 @@ void tst_QWebEnginePage::sendNotification() QTRY_VERIFY2(page.messages.contains("onclose"), page.messages.join("\n").toLatin1().constData()); } +static QString clipboardPermissionQuery(QString variableName, QString permissionName) +{ + return QString("var %1; navigator.permissions.query({ name:'%2' }).then((p) => { %1 = p.state; " + "});") + .arg(variableName) + .arg(permissionName); +} + + +void tst_QWebEnginePage::clipboardReadWritePermissionInitialState_data() +{ + QTest::addColumn<bool>("canAccessClipboard"); + QTest::addColumn<bool>("canPaste"); + QTest::addColumn<QString>("permission"); + QTest::newRow("access and paste should grant") << true << true << "granted"; + QTest::newRow("no access should prompt") << false << true << "prompt"; + QTest::newRow("no paste should prompt") << true << false << "prompt"; + QTest::newRow("no access or paste should prompt") << false << false << "prompt"; +} + +void tst_QWebEnginePage::clipboardReadWritePermissionInitialState() +{ + QFETCH(bool, canAccessClipboard); + QFETCH(bool, canPaste); + QFETCH(QString, permission); + + QWebEngineProfile otr; + otr.setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions); + QWebEngineView view(&otr); + QWebEnginePage &page = *view.page(); + view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + page.settings()->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, + canAccessClipboard); + page.settings()->setAttribute(QWebEngineSettings::JavascriptCanPaste, canPaste); + + QSignalSpy spy(&page, &QWebEnginePage::loadFinished); + QUrl baseUrl("https://www.example.com/somepage.html"); + page.setHtml(QString("<html><body>Test</body></html>"), baseUrl); + QTRY_COMPARE(spy.size(), 1); + + evaluateJavaScriptSync(&page, clipboardPermissionQuery("readPermission", "clipboard-read")); + QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("readPermission")), permission); + evaluateJavaScriptSync(&page, clipboardPermissionQuery("writePermission", "clipboard-write")); + QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("writePermission")), permission); +} + +void tst_QWebEnginePage::clipboardReadWritePermission_data() +{ + QTest::addColumn<bool>("canAccessClipboard"); + QTest::addColumn<QWebEnginePermission::State>("initialPolicy"); + QTest::addColumn<QString>("initialPermission"); + QTest::addColumn<QWebEnginePermission::State>("requestPolicy"); + QTest::addColumn<QString>("finalPermission"); + + QTest::newRow("noAccessGrantGrant") + << false << QWebEnginePermission::Granted << "granted" + << QWebEnginePermission::Granted << "granted"; + QTest::newRow("noAccessGrantDeny") + << false << QWebEnginePermission::Granted << "granted" + << QWebEnginePermission::Denied << "denied"; + QTest::newRow("noAccessDenyGrant") + << false << QWebEnginePermission::Denied << "denied" + << QWebEnginePermission::Granted << "granted"; + QTest::newRow("noAccessDenyDeny") << false << QWebEnginePermission::Denied << "denied" + << QWebEnginePermission::Denied << "denied"; + QTest::newRow("noAccessAskGrant") << false << QWebEnginePermission::Ask << "prompt" + << QWebEnginePermission::Granted << "granted"; + + // All policies are ignored and overridden by setting JsCanAccessClipboard and JsCanPaste to + // true + QTest::newRow("accessGrantGrant") + << true << QWebEnginePermission::Granted << "granted" + << QWebEnginePermission::Granted << "granted"; + QTest::newRow("accessDenyDeny") << true << QWebEnginePermission::Denied << "granted" + << QWebEnginePermission::Denied << "granted"; + QTest::newRow("accessAskAsk") << true << QWebEnginePermission::Ask << "granted" + << QWebEnginePermission::Ask << "granted"; +} + +void tst_QWebEnginePage::clipboardReadWritePermission() +{ + QFETCH(bool, canAccessClipboard); + QFETCH(QWebEnginePermission::State, initialPolicy); + QFETCH(QString, initialPermission); + QFETCH(QWebEnginePermission::State, requestPolicy); + QFETCH(QString, finalPermission); + + QWebEngineProfile otr; + otr.setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions); + QWebEngineView view(&otr); + QWebEnginePage &page = *view.page(); + view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + page.settings()->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, + canAccessClipboard); + page.settings()->setAttribute(QWebEngineSettings::JavascriptCanPaste, true); + + QUrl baseUrl("https://www.example.com/somepage.html"); + + int permissionRequestCount = 0; + bool errorState = false; + + // if JavascriptCanAccessClipboard is true, this never fires + connect(&page, &QWebEnginePage::permissionRequested, &page, + [&](QWebEnginePermission permission) { + if (permission.feature() != QWebEnginePermission::ClipboardReadWrite) + return; + if (permission.origin() != baseUrl.url(QUrl::RemoveFilename)) { + qWarning() << "Unexpected case. Can't proceed." << permission.origin(); + errorState = true; + return; + } + permissionRequestCount++; + switch (requestPolicy) { + case QWebEnginePermission::Granted: + permission.grant(); + break; + case QWebEnginePermission::Denied: + permission.deny(); + break; + case QWebEnginePermission::Ask: + permission.reset(); + break; + default: + break; + } + }); + + QWebEnginePermission permissionObject = otr.getPermission(baseUrl, QWebEnginePermission::ClipboardReadWrite); + switch (initialPolicy) { + case QWebEnginePermission::Granted: + permissionObject.grant(); + break; + case QWebEnginePermission::Denied: + permissionObject.deny(); + break; + case QWebEnginePermission::Ask: + permissionObject.reset(); + break; + default: + break; + } + + 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); @@ -3899,6 +4146,62 @@ void tst_QWebEnginePage::contentsSize() QCOMPARE(m_page->contentsSize().height(), 1216); } +void tst_QWebEnginePage::localFontAccessPermission_data() +{ + QTest::addColumn<QWebEnginePermission::State>("policy"); + QTest::addColumn<bool>("ignore"); + QTest::addColumn<bool>("shouldBeEmpty"); + + QTest::newRow("ignore") << QWebEnginePermission::Denied << true << true; + QTest::newRow("setDeny") << QWebEnginePermission::Denied << false << true; + QTest::newRow("setGrant") << QWebEnginePermission::Granted << false << false; +} + +void tst_QWebEnginePage::localFontAccessPermission() { + QFETCH(QWebEnginePermission::State, policy); + QFETCH(bool, ignore); + QFETCH(bool, shouldBeEmpty); + + QWebEngineView view; + QWebEnginePage page(&view); + page.profile()->setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions); + view.setPage(&page); + + connect(&page, &QWebEnginePage::permissionRequested, &page, [&] (QWebEnginePermission permission) { + if (permission.feature() != QWebEnginePermission::LocalFontsAccess) + return; + + if (!ignore) { + if (policy == QWebEnginePermission::Granted) + permission.grant(); + else + permission.deny(); + } + }); + + QSignalSpy spy(&page, &QWebEnginePage::loadFinished); + page.load(QUrl("qrc:///resources/fontaccess.html")); + QTRY_COMPARE(spy.size(), 1); + + // Font access is only enabled for visible WebContents. + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + if (evaluateJavaScriptSync(&page, QStringLiteral("!window.queryLocalFonts")).toBool()) + W_QSKIP("Local fonts access is not supported.", SkipSingle); + + // Access to the API requires recent user interaction + QTest::keyPress(view.focusProxy(), Qt::Key_Space); + QTRY_COMPARE(evaluateJavaScriptSync(&page, QStringLiteral("activated")).toBool(), true); + + if (ignore) { + QTRY_COMPARE_NE_WITH_TIMEOUT(evaluateJavaScriptSync(&page, QStringLiteral("done")).toBool(), true, 1000); + } else { + QTRY_VERIFY_WITH_TIMEOUT(evaluateJavaScriptSync(&page, QStringLiteral("done")).toBool() == true, 1000); + QVERIFY((evaluateJavaScriptSync(&page, QStringLiteral("fonts.length")).toInt() == 0) == shouldBeEmpty); + } +} + void tst_QWebEnginePage::setLifecycleState() { qRegisterMetaType<QWebEnginePage::LifecycleState>("LifecycleState"); @@ -5119,6 +5422,392 @@ void tst_QWebEnginePage::localToRemoteNavigation() QVERIFY(!remote.loaded); } +void tst_QWebEnginePage::clientHints_data() +{ + QTest::addColumn<bool>("clientHintsEnabled"); + QTest::addColumn<QString>("arch"); + QTest::addColumn<QString>("platform"); + QTest::addColumn<QString>("model"); + QTest::addColumn<bool>("isMobile"); + QTest::addColumn<QString>("fullVersion"); + QTest::addColumn<QString>("platformVersion"); + QTest::addColumn<QString>("bitness"); + QTest::addColumn<bool>("isWOW64"); + QTest::addColumn<QHash<QString, QString>>("fullVersionList"); + + QTest::newRow("Modify values") << true << "Abc" << "AmigaOS" << "Ultra" << true << "1.99" << "3" << "x64" << true << QHash<QString, QString>({{"APITest", "1.0.0"}, {"App", "5.0"}}); + QTest::newRow("Empty values") << true << "" << "" << "" << false << "" << "" << "" << false << QHash<QString, QString>(); + QTest::newRow("Disable headers") << false << "" << "" << "" << false << "" << "" << "" << false << QHash<QString, QString>(); +} + +void tst_QWebEnginePage::clientHints() +{ + QFETCH(bool, clientHintsEnabled); + QFETCH(QString, arch); + QFETCH(QString, platform); + QFETCH(QString, model); + QFETCH(bool, isMobile); + QFETCH(QString, fullVersion); + QFETCH(QString, platformVersion); + QFETCH(QString, bitness); + QFETCH(bool, isWOW64); + typedef QHash<QString, QString> brandVersionPairs; + QFETCH(brandVersionPairs, fullVersionList); + + QWebEnginePage page; + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + + QWebEngineClientHints *clientHints = page.profile()->clientHints(); + clientHints->setAllClientHintsEnabled(clientHintsEnabled); + + HttpServer server; + int requestCount = 0; + connect(&server, &HttpServer::newRequest, [&] (HttpReqRep *r) { + // Platform and Mobile hints are always sent and can't be disabled with this API + QVERIFY(r->hasRequestHeader("Sec-CH-UA-Platform")); + QVERIFY(r->hasRequestHeader("Sec-CH-UA-Mobile")); + if (!clientHintsEnabled) { + QVERIFY(!r->hasRequestHeader("Sec-CH-UA-Arch")); + QVERIFY(!r->hasRequestHeader("Sec-CH-UA-Model")); + QVERIFY(!r->hasRequestHeader("Sec-CH-UA-Full-Version")); + QVERIFY(!r->hasRequestHeader("Sec-CH-UA-Platform-Version")); + QVERIFY(!r->hasRequestHeader("Sec-CH-UA-Bitness")); + QVERIFY(!r->hasRequestHeader("Sec-CH-UA-Wow64")); + QVERIFY(!r->hasRequestHeader("Sec-CH-UA-Full-Version-List")); + } + + // The first request header won't contain any hints, only after a response with "Accept-CH" + if (requestCount > 1 && clientHintsEnabled) { + // All hint values are lower case in the headers + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Arch")).remove("\""), arch.toLower()); + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Platform")).remove("\""), platform.toLower()); + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Model")).remove("\""), model.toLower()); + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Mobile")).remove("\""), isMobile ? "?1" : "?0"); + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Full-Version")).remove("\""), fullVersion.toLower()); + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Platform-Version")).remove("\""), platformVersion.toLower()); + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Bitness")).remove("\""), bitness.toLower()); + QCOMPARE(QString(r->requestHeader("Sec-CH-UA-Wow64")).remove("\""), isWOW64 ? "?1" : "?0"); + for (auto i = fullVersionList.cbegin(), end = fullVersionList.cend(); i != end; ++i) + QVERIFY(QString(r->requestHeader("Sec-CH-UA-Full-Version-List")).contains(i.key().toLower())); + } + + r->setResponseHeader("Accept-CH", "Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform-Version, Sec-CH-UA-Platform, Sec-CH-UA-Wow64, Sec-CH-UA"); + r->sendResponse(); + requestCount++; + }); + QVERIFY(server.start()); + + clientHints->setArch(arch); + clientHints->setPlatform(platform); + clientHints->setModel(model); + clientHints->setIsMobile(isMobile); + clientHints->setFullVersion(fullVersion); + clientHints->setPlatformVersion(platformVersion); + clientHints->setBitness(bitness); + clientHints->setIsWow64(isWOW64); + clientHints->setFullVersionList(fullVersionList); + + page.setUrl(server.url()); + QTRY_COMPARE(loadSpy.size(), 1); + QVERIFY(loadSpy.takeFirst().value(0).toBool()); + + QCOMPARE(clientHints->arch(), arch); + QCOMPARE(clientHints->platform(), platform); + QCOMPARE(clientHints->model(), model); + QCOMPARE(clientHints->isMobile(), isMobile); + QCOMPARE(clientHints->fullVersion(), fullVersion); + QCOMPARE(clientHints->platformVersion(), platformVersion); + QCOMPARE(clientHints->bitness(), bitness); + QCOMPARE(clientHints->isWow64(), isWOW64); + for (auto i = fullVersionList.cbegin(), end = fullVersionList.cend(); i != end; ++i) + QCOMPARE(clientHints->fullVersionList()[i.key()], i.value()); + + // A new user agent string should not override/disable client hints + page.profile()->setHttpUserAgent(QStringLiteral("Custom user agent")); + page.triggerAction(QWebEnginePage::Reload); + QTRY_COMPARE(loadSpy.size(), 1); + + // Reset all to default values + clientHints->resetAll(); + QCOMPARE_NE(clientHints->arch(), arch); +#ifdef Q_OS_LINUX + QCOMPARE(clientHints->platform().toLower(), "linux"); +#elif defined (Q_OS_MACOS) + QCOMPARE(clientHints->platform().toLower(), "macos"); +#elif defined (Q_OS_WIN) + QCOMPARE(clientHints->platform().toLower(), "windows"); +#endif + QCOMPARE_NE(clientHints->fullVersion(), fullVersion); + QCOMPARE_NE(clientHints->platformVersion(), platformVersion); + QCOMPARE_NE(clientHints->bitness(), bitness); + for (auto i = fullVersionList.cbegin(), end = fullVersionList.cend(); i != end; ++i) + QVERIFY(!clientHints->fullVersionList().contains(i.key())); + QVERIFY(clientHints->fullVersionList().contains("Chromium")); +} + +void tst_QWebEnginePage::childFrameInput() +{ + HttpServer server; + server.setHostDomain("localhost"); + + // The cross-origin policy blocks scripting this frame with QWebEnginePage::runJavaScript. + // Use console messages to validate events. + QString innerHtml( + "<html><head><style>body{height:1200px;width:1200px;}</style></head><body>test<script>" + " let lastX, lastY = 0;" + " document.onscroll = (e) => {" + " if (window.scrollY > lastY) console.log(\"Down\");" + " if (window.scrollY < lastY) console.log(\"Up\");" + " if (window.scrollX > lastX) console.log(\"Right\");" + " if (window.scrollX < lastX) console.log(\"Left\");" + " lastX = window.scrollX;" + " lastY = window.scrollY;" + " };" + " window.onload = () => {console.log('loaded');};" + "</script></body></html>"); + + QVERIFY(server.start()); + connect(&server, &HttpServer::newRequest, [&](HttpReqRep *rr) { + if (rr->requestPath() == "/main.html") { + // the Origin-Agent-Cluster header enables dedicated processes for origins + rr->setResponseHeader("Origin-Agent-Cluster", "?1"); + // the same-site-cross-origin page forces to create the frame in a different process + server.setHostDomain("sub.localhost"); + rr->setResponseBody(("<html><body>" + "<iframe id=\"iframe\" width=90% height=90% src=\"" + + server.url().toString().toUtf8() + + "inner.html\"></iframe>" + "</body></html>")); + } + if (rr->requestPath() == "/inner.html") + rr->setResponseBody(innerHtml.toUtf8()); + rr->sendResponse(); + }); + + QWebEngineView view; + ConsolePage page; + view.setPage(&page); + view.resize(640, 480); + QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished); + page.load(server.url("/main.html")); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); + + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + QTRY_VERIFY(evaluateJavaScriptSync(&page, "window.originAgentCluster").toBool()); + + // make sure the frame is loaded + QTRY_COMPARE(page.messages.size(), 1); + QTRY_COMPARE(page.messages[0], QString("loaded")); + + // focus + evaluateJavaScriptSync(&page, "document.getElementById('iframe').contentWindow.focus()"); + QTRY_COMPARE(evaluateJavaScriptSync(&page, "document.activeElement.id").toString(), + QStringLiteral("iframe")); + + QPoint globalPos = view.windowHandle()->position(); + QPoint p = elementCenter(&page, QString("iframe")); + + // Even if the document is loaded, it is not necessarily drawn. + // Hit-testing (in Viz) for pointer events will be flacky in this scenario. + // Send keyClick events first so the target frame will be cached for wheel events. + QTest::keyClick(view.focusProxy(), Qt::Key_Down); + QTRY_COMPARE(page.messages.size(), 2); + QTRY_COMPARE(page.messages[1], QString("Down")); + + QTest::keyClick(view.focusProxy(), Qt::Key_Up); + QTRY_COMPARE(page.messages.size(), 3); + QTRY_COMPARE(page.messages[2], QString("Up")); + + QTest::keyClick(view.focusProxy(), Qt::Key_Right); + QTRY_COMPARE(page.messages.size(), 4); + QTRY_COMPARE(page.messages[3], QString("Right")); + + QTest::keyClick(view.focusProxy(), Qt::Key_Left); + QTRY_COMPARE(page.messages.size(), 5); + QTRY_COMPARE(page.messages[4], QString("Left")); + + makeScroll(view.focusProxy(), p, globalPos, QPoint(0, -120)); + QTRY_COMPARE(page.messages.size(), 6); + QTRY_COMPARE(page.messages[5], QString("Down")); + + makeScroll(view.focusProxy(), p, globalPos, QPoint(0, 120)); + QTRY_COMPARE(page.messages.size(), 7); + QTRY_COMPARE(page.messages[6], QString("Up")); + + makeScroll(view.focusProxy(), p, globalPos, QPoint(-120, 0)); + QTRY_COMPARE(page.messages.size(), 8); + QTRY_COMPARE(page.messages[7], QString("Right")); + + makeScroll(view.focusProxy(), p, globalPos, QPoint(120, 0)); + QTRY_COMPARE(page.messages.size(), 9); + QTRY_COMPARE(page.messages[8], QString("Left")); +} + +void tst_QWebEnginePage::openLinkInNewPageWithWebWindowType_data() +{ + QTest::addColumn<QWebEnginePage::WebWindowType>("webWindowType"); + QTest::addColumn<QString>("elementId"); + QTest::addColumn<Qt::MouseButton>("button"); + QTest::addColumn<Qt::KeyboardModifier>("keyboardModififer"); + QTest::newRow("webBrowserWindow") + << QWebEnginePage::WebBrowserWindow << "link" << Qt::LeftButton << Qt::ShiftModifier; + QTest::newRow("webBrowserTab") + << QWebEnginePage::WebBrowserTab << "link" << Qt::LeftButton << Qt::NoModifier; + QTest::newRow("webDialog") << QWebEnginePage::WebDialog << "openWindow" << Qt::LeftButton + << Qt::NoModifier; + QTest::newRow("webBrowserBackgroundTab") << QWebEnginePage::WebBrowserBackgroundTab << "link" + << Qt::MiddleButton << Qt::NoModifier; +} + +class WebWindowTypeTestPage : public QWebEnginePage +{ + Q_OBJECT + +public: + WebWindowType windowType; + +signals: + void windowCreated(); + +private: + QWebEnginePage *createWindow(WebWindowType type) override + { + windowType = type; + emit windowCreated(); + return nullptr; + } +}; + +void tst_QWebEnginePage::openLinkInNewPageWithWebWindowType() +{ + QFETCH(QWebEnginePage::WebWindowType, webWindowType); + QFETCH(QString, elementId); + QFETCH(Qt::MouseButton, button); + QFETCH(Qt::KeyboardModifier, keyboardModififer); + + WebWindowTypeTestPage page; + QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + QSignalSpy windowCreatedSpy(&page, &WebWindowTypeTestPage::windowCreated); + QWebEngineView view(&page); + view.resize(640, 480); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + page.settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true); + page.settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true); + QString html = "<html><body>" + "<a id='link' href='hello' target='_blank'>link</a>" + "<br><br>" + "<button id='openWindow' onclick='myFunction()'>Try it</button>" + "<script>" + "function myFunction() {" + " const myWindow = window.open('', '', 'width=300,height=300');" + "}" + "</script>" + "</body></html>"; + + page.setHtml(html); + QVERIFY(loadFinishedSpy.wait()); + + QTest::mouseClick(view.focusProxy(), button, keyboardModififer, + elementCenter(&page, elementId)); + QVERIFY(windowCreatedSpy.wait()); + QCOMPARE(page.windowType, webWindowType); +} + +class DoNothingInterceptor : public QWebEngineUrlRequestInterceptor +{ +public: + DoNothingInterceptor() { } + + void interceptRequest(QWebEngineUrlRequestInfo &) override + { + ran = true; + } + bool ran = false; +}; + +void tst_QWebEnginePage::keepInterceptorAfterNewWindowRequested() +{ + DoNothingInterceptor interceptor; + QWebEnginePage page; + page.setUrlRequestInterceptor(&interceptor); + connect(&page, &QWebEnginePage::newWindowRequested, [&](QWebEngineNewWindowRequest &request) { + request.openIn(&page); + }); + QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + + QWebEngineView view; + view.resize(500, 500); + view.setPage(&page); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + page.setHtml("<html><body>" + "<a id='link' href='hello' target='_blank'>link</a>" + "</body></html>"); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QVERIFY(loadFinishedSpy.takeFirst().value(0).toBool()); + QVERIFY(interceptor.ran); + interceptor.ran = false; + + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, elementCenter(&page, "link")); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QVERIFY(loadFinishedSpy.takeFirst().value(0).toBool()); + QVERIFY(!interceptor.ran); + + page.setHtml("<html><body></body></html>"); + QTRY_COMPARE(loadFinishedSpy.size(), 1); + QVERIFY(loadFinishedSpy.takeFirst().value(0).toBool()); + QVERIFY(interceptor.ran); +} + +void tst_QWebEnginePage::chooseDesktopMedia() +{ +#if QT_CONFIG(webengine_extensions) && QT_CONFIG(webengine_webrtc) + HttpServer server; + server.setHostDomain("localhost"); + connect(&server, &HttpServer::newRequest, &server, [&] (HttpReqRep *r) { + if (r->requestMethod() == "GET") + r->setResponseBody("<html></html>"); + }); + QVERIFY(server.start()); + + QWebEnginePage page; + QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + page.settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true); + page.profile()->setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions); + + bool desktopMediaRequested = false; + bool permissionRequested = false; + + connect(&page, &QWebEnginePage::desktopMediaRequested, + [&](const QWebEngineDesktopMediaRequest &) { + desktopMediaRequested = true; + }); + + connect(&page, &QWebEnginePage::permissionRequested, + [&](QWebEnginePermission permission) { + permissionRequested = true; + // Handle permission to 'complete' the media request + permission.grant(); + }); + + 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) |