summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/auto/CMakeLists.txt6
-rw-r--r--tests/auto/cmake/CMakeLists.txt27
-rw-r--r--tests/auto/core/CMakeLists.txt13
-rw-r--r--tests/auto/core/certificateerror/tst_certificateerror.cpp16
-rw-r--r--tests/auto/core/getdomainandregistry/CMakeLists.txt12
-rw-r--r--tests/auto/core/getdomainandregistry/tst_getdomainandregistry.cpp30
-rw-r--r--tests/auto/core/origins/CMakeLists.txt3
-rw-r--r--tests/auto/core/origins/resources/fetchApi.html14
-rw-r--r--tests/auto/core/origins/resources/link.html13
-rw-r--r--tests/auto/core/origins/resources/mixedSchemes.html29
-rw-r--r--tests/auto/core/origins/resources/mixedSchemes_frame.html9
-rw-r--r--tests/auto/core/origins/resources/red.pngbin0 -> 146 bytes
-rw-r--r--tests/auto/core/origins/tst_origins.cpp465
-rw-r--r--tests/auto/core/qtversion/CMakeLists.txt10
-rw-r--r--tests/auto/core/qtversion/tst_qtversion.cpp34
-rw-r--r--tests/auto/core/qwebengineclientcertificatestore/CMakeLists.txt22
-rw-r--r--tests/auto/core/qwebengineclientcertificatestore/resources/client2.p12bin2549 -> 2710 bytes
-rw-r--r--tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp32
-rw-r--r--tests/auto/core/qwebengineframe/CMakeLists.txt22
-rw-r--r--tests/auto/core/qwebengineframe/resources/frameset.html20
-rw-r--r--tests/auto/core/qwebengineframe/resources/iframes.html17
-rw-r--r--tests/auto/core/qwebengineframe/resources/nesting-iframe.html7
-rw-r--r--tests/auto/core/qwebengineframe/tst_qwebengineframe.cpp194
-rw-r--r--tests/auto/core/qwebengineglobalsettings/CMakeLists.txt30
-rw-r--r--tests/auto/core/qwebengineglobalsettings/cert/RootCA.pem20
-rw-r--r--tests/auto/core/qwebengineglobalsettings/cert/localhost.crt22
-rw-r--r--tests/auto/core/qwebengineglobalsettings/cert/localhost.key28
-rw-r--r--tests/auto/core/qwebengineglobalsettings/tst_qwebengineglobalsettings.cpp130
-rw-r--r--tests/auto/core/qwebengineloadinginfo/CMakeLists.txt10
-rw-r--r--tests/auto/core/qwebengineloadinginfo/tst_qwebengineloadinginfo.cpp93
-rw-r--r--tests/auto/core/qwebenginesettings/CMakeLists.txt1
-rw-r--r--tests/auto/core/qwebenginesettings/tst_qwebenginesettings.cpp109
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/CMakeLists.txt5
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/resources/content3.html6
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/resources/postBodyFile.txt3
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp232
-rw-r--r--tests/auto/core/qwebengineurlrequestjob/CMakeLists.txt22
-rw-r--r--tests/auto/core/qwebengineurlrequestjob/additionalResponseHeadersScript.html12
-rw-r--r--tests/auto/core/qwebengineurlrequestjob/requestBodyScript.html12
-rw-r--r--tests/auto/core/qwebengineurlrequestjob/tst_qwebengineurlrequestjob.cpp187
-rw-r--r--tests/auto/core/webenginedriver/CMakeLists.txt15
-rw-r--r--tests/auto/core/webenginedriver/browser/CMakeLists.txt13
-rw-r--r--tests/auto/core/webenginedriver/browser/main.cpp21
-rw-r--r--tests/auto/core/webenginedriver/resources/input.html5
-rw-r--r--tests/auto/core/webenginedriver/test/CMakeLists.txt26
-rw-r--r--tests/auto/core/webenginedriver/tst_webenginedriver.cpp631
-rw-r--r--tests/auto/httpserver/httpreqrep.cpp5
-rw-r--r--tests/auto/httpserver/httpreqrep.h1
-rw-r--r--tests/auto/httpserver/httpserver.h2
-rw-r--r--tests/auto/httpserver/httpsserver.h12
-rw-r--r--tests/auto/pdf/CMakeLists.txt5
-rw-r--r--tests/auto/pdf/qpdfbookmarkmodel/CMakeLists.txt3
-rw-r--r--tests/auto/pdf/qpdfdocument/CMakeLists.txt6
-rw-r--r--tests/auto/pdf/qpdfdocument/rotated_text.pdf70
-rw-r--r--tests/auto/pdf/qpdfdocument/tagged_mcr_multipage.pdf136
-rw-r--r--tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp92
-rw-r--r--tests/auto/pdf/qpdfpagenavigator/CMakeLists.txt14
-rw-r--r--tests/auto/pdf/qpdfpagenavigator/pdf-sample.bookmarks_pages.pdfbin0 -> 27523 bytes
-rw-r--r--tests/auto/pdf/qpdfpagenavigator/tst_qpdfpagenavigator.cpp70
-rw-r--r--tests/auto/pdf/qpdfpagerenderer/CMakeLists.txt2
-rw-r--r--tests/auto/pdf/qpdfsearchmodel/CMakeLists.txt4
-rw-r--r--tests/auto/pdf/qpdfsearchmodel/rotated_text.pdf70
-rw-r--r--tests/auto/pdf/qpdfsearchmodel/tagged_mcr_multipage.pdf136
-rw-r--r--tests/auto/pdf/qpdfsearchmodel/tst_qpdfsearchmodel.cpp41
-rw-r--r--tests/auto/pdfquick/CMakeLists.txt1
-rw-r--r--tests/auto/pdfquick/multipageview/BLACKLIST10
-rw-r--r--tests/auto/pdfquick/multipageview/CMakeLists.txt4
-rw-r--r--tests/auto/pdfquick/multipageview/data/jumpOnDocumentReady.qml18
-rw-r--r--tests/auto/pdfquick/multipageview/tst_multipageview.cpp107
-rw-r--r--tests/auto/pdfquick/pdfpageimage/CMakeLists.txt30
-rw-r--r--tests/auto/pdfquick/pdfpageimage/data/bookmarksAndLinks.pdf317
-rw-r--r--tests/auto/pdfquick/pdfpageimage/data/pdfPageImage.qml16
-rw-r--r--tests/auto/pdfquick/pdfpageimage/tst_pdfpageimage.cpp131
-rw-r--r--tests/auto/quick/inspectorserver/tst_inspectorserver.cpp20
-rw-r--r--tests/auto/quick/publicapi/tst_publicapi.cpp121
-rw-r--r--tests/auto/quick/qmltests/BLACKLIST5
-rw-r--r--tests/auto/quick/qmltests/CMakeLists.txt11
-rw-r--r--tests/auto/quick/qmltests/data/filesystemapi.html66
-rw-r--r--tests/auto/quick/qmltests/data/test4.html1
-rw-r--r--tests/auto/quick/qmltests/data/tst_action.qml4
-rw-r--r--tests/auto/quick/qmltests/data/tst_dragHandlerUnderView.qml67
-rw-r--r--tests/auto/quick/qmltests/data/tst_faviconDatabase.qml6
-rw-r--r--tests/auto/quick/qmltests/data/tst_filePicker.qml18
-rw-r--r--tests/auto/quick/qmltests/data/tst_filesystem.qml124
-rw-r--r--tests/auto/quick/qmltests/data/tst_findText.qml3
-rw-r--r--tests/auto/quick/qmltests/data/tst_inputTextDirection.qml43
-rw-r--r--tests/auto/quick/qmltests/data/tst_newViewRequest.qml3
-rw-r--r--tests/auto/quick/qmltests/data/tst_save.qml185
-rw-r--r--tests/auto/quick/qmltests/data/tst_scrollPosition.qml7
-rw-r--r--tests/auto/quick/qmltests/data/tst_settings.qml62
-rw-r--r--tests/auto/quick/qmltests/mock-delegates/QtWebEngine/ControlsDelegates/DirectoryPicker.qml18
-rw-r--r--tests/auto/quick/qmltests/mock-delegates/TestParams/FilePickerParams.qml1
-rw-r--r--tests/auto/quick/qmltests/tst_qmltests.cpp9
-rw-r--r--tests/auto/quick/qquickwebengineview/tst_qquickwebengineview.cpp112
-rw-r--r--tests/auto/quick/qquickwebengineviewgraphics/tst_qquickwebengineviewgraphics.cpp1
-rw-r--r--tests/auto/util/util.h2
-rw-r--r--tests/auto/widgets/CMakeLists.txt1
-rw-r--r--tests/auto/widgets/accessibility/BLACKLIST3
-rw-r--r--tests/auto/widgets/accessibility/CMakeLists.txt1
-rw-r--r--tests/auto/widgets/accessibility/tst_accessibility.cpp62
-rw-r--r--tests/auto/widgets/printing/tst_printing.cpp60
-rw-r--r--tests/auto/widgets/proxy/tst_proxy.cpp16
-rw-r--r--tests/auto/widgets/proxypac/tst_proxypac.cpp2
-rw-r--r--tests/auto/widgets/qtbug_110287/CMakeLists.txt11
-rw-r--r--tests/auto/widgets/qtbug_110287/tst_qtbug_110287.cpp41
-rw-r--r--tests/auto/widgets/qwebenginedownloadrequest/tst_qwebenginedownloadrequest.cpp4
-rw-r--r--tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp4
-rw-r--r--tests/auto/widgets/qwebenginepage/BLACKLIST6
-rw-r--r--tests/auto/widgets/qwebenginepage/CMakeLists.txt1
-rw-r--r--tests/auto/widgets/qwebenginepage/resources/fontaccess.html14
-rw-r--r--tests/auto/widgets/qwebenginepage/resources/reload.html2
-rw-r--r--tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp773
-rw-r--r--tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp12
-rw-r--r--tests/auto/widgets/qwebenginescript/resources/test_window_open.html2
-rw-r--r--tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp33
-rw-r--r--tests/auto/widgets/qwebengineview/BLACKLIST3
-rw-r--r--tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp280
-rw-r--r--tests/auto/widgets/spellchecking/CMakeLists.txt1
-rw-r--r--tests/auto/widgets/touchinput/tst_touchinput.cpp4
-rw-r--r--tests/manual/CMakeLists.txt9
-rw-r--r--tests/manual/examples/CMakeLists.txt6
-rw-r--r--tests/manual/examples/quick/CMakeLists.txt4
-rw-r--r--tests/manual/examples/quick/customdialogs/CMakeLists.txt56
-rw-r--r--tests/manual/examples/quick/customdialogs/MessageRectangle.qml18
-rw-r--r--tests/manual/examples/quick/customdialogs/SwitchButton.qml23
-rw-r--r--tests/manual/examples/quick/customdialogs/WebView.qml117
-rw-r--r--tests/manual/examples/quick/customdialogs/customdialogs.pro18
-rw-r--r--tests/manual/examples/quick/customdialogs/customdialogs.qrc24
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/Authentication.qml31
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/AuthenticationForm.ui.qml137
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/ColorCell.qml16
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/ColorPicker.qml31
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/ColorPickerForm.ui.qml186
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/CustomButton.qml61
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/FilePicker.qml44
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/FilePickerForm.ui.qml128
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/FileRow.qml44
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/JavaScript.qml44
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/JavaScriptForm.ui.qml117
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/Menu.qml22
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/MenuForm.ui.qml65
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/TouchSelectionMenu.qml14
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/TouchSelectionMenuForm.ui.qml39
-rw-r--r--tests/manual/examples/quick/customdialogs/forms/forms.qmlproject45
-rw-r--r--tests/manual/examples/quick/customdialogs/icon.svg24
-rw-r--r--tests/manual/examples/quick/customdialogs/index.html51
-rw-r--r--tests/manual/examples/quick/customdialogs/main.cpp32
-rw-r--r--tests/manual/examples/quick/customdialogs/main.qml56
-rw-r--r--tests/manual/examples/quick/customdialogs/server.cpp47
-rw-r--r--tests/manual/examples/quick/customdialogs/server.h29
-rw-r--r--tests/manual/examples/quick/customdialogs/style.css37
-rw-r--r--tests/manual/examples/quick/customtouchhandle/CMakeLists.txt36
-rw-r--r--tests/manual/examples/quick/customtouchhandle/customtouchhandle.pro10
-rw-r--r--tests/manual/examples/quick/customtouchhandle/main.cpp19
-rw-r--r--tests/manual/examples/quick/customtouchhandle/main.qml96
-rw-r--r--tests/manual/examples/quick/customtouchhandle/qml.qrc5
-rw-r--r--tests/manual/examples/quick/minimal/CMakeLists.txt36
-rw-r--r--tests/manual/examples/quick/minimal/main.cpp19
-rw-r--r--tests/manual/examples/quick/minimal/main.qml16
-rw-r--r--tests/manual/examples/quick/minimal/minimal.pro10
-rw-r--r--tests/manual/examples/quick/minimal/qml.qrc6
-rw-r--r--tests/manual/examples/quick/webengineaction/CMakeLists.txt37
-rw-r--r--tests/manual/examples/quick/webengineaction/main.cpp25
-rw-r--r--tests/manual/examples/quick/webengineaction/main.qml119
-rw-r--r--tests/manual/examples/quick/webengineaction/qml.qrc5
-rw-r--r--tests/manual/examples/quick/webengineaction/utils.h (renamed from tests/manual/quick/touchbrowser/utils.h)11
-rw-r--r--tests/manual/examples/quick/webengineaction/webengineaction.pro8
-rw-r--r--tests/manual/examples/widgets/CMakeLists.txt1
-rw-r--r--tests/manual/examples/widgets/minimal/CMakeLists.txt24
-rw-r--r--tests/manual/examples/widgets/minimal/main.cpp28
-rw-r--r--tests/manual/examples/widgets/minimal/minimal.pro8
-rw-r--r--tests/manual/manual.pro7
-rw-r--r--tests/manual/quick/CMakeLists.txt1
-rw-r--r--tests/manual/quick/geopermission/CMakeLists.txt63
-rw-r--r--tests/manual/quick/geopermission/Info.plist32
-rw-r--r--tests/manual/quick/geopermission/geolocation.html32
-rw-r--r--tests/manual/quick/geopermission/main.cpp39
-rw-r--r--tests/manual/quick/geopermission/tst_geopermission.qml28
-rw-r--r--tests/manual/quick/pdf/multipleDocuments.qml10
-rw-r--r--tests/manual/quick/pdf/pdfPageView.qml28
-rw-r--r--tests/manual/quick/pdf/withdoc.qml6
-rw-r--r--tests/manual/quick/quick.pro5
-rw-r--r--tests/manual/quick/touchbrowser/AddressBar.qml4
-rw-r--r--tests/manual/quick/touchbrowser/CMakeLists.txt34
-rw-r--r--tests/manual/quick/touchbrowser/MockTouchPoint.qml5
-rw-r--r--tests/manual/quick/touchbrowser/main.cpp41
-rw-r--r--tests/manual/quick/touchbrowser/resources.qrc (renamed from tests/manual/quick/touchbrowser/qml.qrc)3
-rw-r--r--tests/manual/quick/touchbrowser/touchbrowser.pro22
-rw-r--r--tests/manual/quick/touchbrowser/touchmockingapplication.cpp261
-rw-r--r--tests/manual/quick/touchbrowser/touchmockingapplication.h43
-rw-r--r--tests/manual/touchmocking/touchmockingapplication.cpp78
-rw-r--r--tests/manual/touchmocking/touchmockingapplication.h35
-rw-r--r--tests/manual/touchmocking/touchpoint.png (renamed from tests/manual/quick/touchbrowser/touchpoint.png)bin1331 -> 1331 bytes
-rw-r--r--tests/manual/touchmocking/utils.h25
-rw-r--r--tests/manual/widgets/CMakeLists.txt9
-rw-r--r--tests/manual/widgets/geolocation/CMakeLists.txt55
-rw-r--r--tests/manual/widgets/geolocation/Info.plist32
-rw-r--r--tests/manual/widgets/geolocation/geolocation.html32
-rw-r--r--tests/manual/widgets/geolocation/main.cpp51
-rw-r--r--tests/manual/widgets/inputmethods/colorpicker.h2
-rw-r--r--tests/manual/widgets/inputmethods/controlview.h5
-rw-r--r--tests/manual/widgets/inputmethods/testview.h2
-rw-r--r--tests/manual/widgets/touchbrowser/CMakeLists.txt30
-rw-r--r--tests/manual/widgets/touchbrowser/main.cpp62
-rw-r--r--tests/manual/widgets/touchbrowser/resources.qrc5
-rw-r--r--tests/manual/widgets/touchbrowser/touchbrowser.pro15
-rw-r--r--tests/manual/widgets/webrtc/CMakeLists.txt24
-rw-r--r--tests/manual/widgets/webrtc/index.html86
-rw-r--r--tests/manual/widgets/webrtc/main.cpp112
-rw-r--r--tests/manual/widgets/webrtc/mediaPicker.ui118
-rw-r--r--tests/manual/widgets/webrtc/qrc.qrc5
-rw-r--r--tests/manual/widgets/widgets.pro5
-rw-r--r--tests/quicktestbrowser/ApplicationRoot.qml43
-rw-r--r--tests/quicktestbrowser/BrowserDialog.qml19
-rw-r--r--tests/quicktestbrowser/BrowserWindow.qml507
-rw-r--r--tests/quicktestbrowser/ButtonWithMenu.qml33
-rw-r--r--tests/quicktestbrowser/DownloadView.qml128
-rw-r--r--tests/quicktestbrowser/FeaturePermissionBar.qml67
-rw-r--r--tests/quicktestbrowser/FullScreenNotification.qml62
-rw-r--r--tests/quicktestbrowser/ZoomController.qml65
-rw-r--r--tests/quicktestbrowser/main.cpp71
-rw-r--r--tests/quicktestbrowser/quicktestbrowser.pro26
-rw-r--r--tests/quicktestbrowser/resources.qrc19
-rw-r--r--tests/quicktestbrowser/utils.h29
224 files changed, 8958 insertions, 1813 deletions
diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt
index 14612a5b2..1b0ff3e9d 100644
--- a/tests/auto/CMakeLists.txt
+++ b/tests/auto/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-if(LINUX AND CMAKE_CROSSCOMPILING AND (${TEST_architecture_arch} STREQUAL "arm64"))
- return() # FIXME
-endif()
+add_subdirectory(cmake)
if(TARGET Qt::WebEngineCore)
add_subdirectory(httpserver)
add_subdirectory(util)
@@ -16,5 +14,7 @@ if(TARGET Qt::WebEngineWidgets)
endif()
if(TARGET Qt::Pdf)
add_subdirectory(pdf)
+endif()
+if(TARGET Qt::PdfQuick)
add_subdirectory(pdfquick)
endif()
diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt
index 83ebf5a23..2fa1f915a 100644
--- a/tests/auto/cmake/CMakeLists.txt
+++ b/tests/auto/cmake/CMakeLists.txt
@@ -1,19 +1,30 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-
cmake_minimum_required(VERSION 3.16)
-
-project(qmake_cmake_files)
+project(qtwebengine_cmake_tests)
enable_testing()
-find_package(Qt5Core REQUIRED)
+find_package(Qt6Core REQUIRED)
-include("${_Qt5CTestMacros}")
+include("${_Qt6CTestMacros}")
-if (NOT NO_WIDGETS)
- test_module_includes(
- WebEngineWidgets QWebEngineView
+set(module_includes "")
+if(TARGET Qt6::WebEngineCore)
+ list(APPEND module_includes
+ WebEngineCore QWebEnginePage
+ )
+endif()
+if(TARGET Qt6::WebEngineWidgets)
+ list(APPEND module_includes
+ WebEngineWidgets QWebEngineView
)
endif()
+if(TARGET Qt6::Pdf)
+ list(APPEND module_includes
+ Pdf QPdfDocument
+ )
+endif()
+
+_qt_internal_test_module_includes(${module_includes})
diff --git a/tests/auto/core/CMakeLists.txt b/tests/auto/core/CMakeLists.txt
index 5615d6b2d..eb8e9266f 100644
--- a/tests/auto/core/CMakeLists.txt
+++ b/tests/auto/core/CMakeLists.txt
@@ -2,12 +2,25 @@
# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(qwebenginecookiestore)
+add_subdirectory(qwebengineframe)
+add_subdirectory(qwebengineloadinginfo)
add_subdirectory(qwebenginesettings)
+if(QT_FEATURE_ssl)
+ # only tests doh, and requires ssl
+ add_subdirectory(qwebengineglobalsettings)
+endif()
add_subdirectory(qwebengineurlrequestinterceptor)
+add_subdirectory(qwebengineurlrequestjob)
add_subdirectory(origins)
add_subdirectory(devtools)
+add_subdirectory(getdomainandregistry)
+add_subdirectory(qtversion)
if(QT_FEATURE_ssl)
add_subdirectory(qwebengineclientcertificatestore)
add_subdirectory(certificateerror)
endif()
+
+if(QT_FEATURE_webenginedriver)
+ add_subdirectory(webenginedriver)
+endif()
diff --git a/tests/auto/core/certificateerror/tst_certificateerror.cpp b/tests/auto/core/certificateerror/tst_certificateerror.cpp
index 67e2d8ae4..61201e250 100644
--- a/tests/auto/core/certificateerror/tst_certificateerror.cpp
+++ b/tests/auto/core/certificateerror/tst_certificateerror.cpp
@@ -19,6 +19,7 @@ private Q_SLOTS:
void handleError_data();
void handleError();
void fatalError();
+ void resourceError();
};
struct PageWithCertificateErrorHandler : QWebEnginePage
@@ -130,5 +131,20 @@ void tst_CertificateError::fatalError()
}
}
+void tst_CertificateError::resourceError()
+{
+ PageWithCertificateErrorHandler page(false, false);
+ page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+
+ page.setHtml("<img src=\"https://expired.badssl.com\">");
+ if (!page.loadSpy.wait(10000)) {
+ QVERIFY2(!page.error, "There shouldn't be any certificate error if not loaded due to missing internet access!");
+ QSKIP("Couldn't load page from network, skipping test.");
+ }
+
+ QTRY_VERIFY(page.error);
+ QCOMPARE(page.error->isMainFrame(), false);
+}
+
QTEST_MAIN(tst_CertificateError)
#include <tst_certificateerror.moc>
diff --git a/tests/auto/core/getdomainandregistry/CMakeLists.txt b/tests/auto/core/getdomainandregistry/CMakeLists.txt
new file mode 100644
index 000000000..c2b65f9dc
--- /dev/null
+++ b/tests/auto/core/getdomainandregistry/CMakeLists.txt
@@ -0,0 +1,12 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+include(../../util/util.cmake)
+
+qt_internal_add_test(tst_getdomainandregistry
+ SOURCES
+ tst_getdomainandregistry.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+ Test::Util
+)
diff --git a/tests/auto/core/getdomainandregistry/tst_getdomainandregistry.cpp b/tests/auto/core/getdomainandregistry/tst_getdomainandregistry.cpp
new file mode 100644
index 000000000..e9e0bf105
--- /dev/null
+++ b/tests/auto/core/getdomainandregistry/tst_getdomainandregistry.cpp
@@ -0,0 +1,30 @@
+// 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 <QtTest/QtTest>
+#include <QtWebEngineCore/qtwebenginecoreglobal.h>
+
+class tst_GetDomainAndRegistry final : public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+ void getDomainAndRegistry();
+};
+
+void tst_GetDomainAndRegistry::getDomainAndRegistry() {
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"http://www.google.com/"}), QString("google.com"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"http://www.google.co.uk/"}), QString("google.co.uk"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"http://127.0.0.1/"}), QString());
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://qt.io/"}), QString("qt.io"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://download.qt.io/"}), QString("qt.io"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://foo.fr/"}), QString("foo.fr"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://foo.gouv.fr/"}), QString("foo.gouv.fr"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://bar.foo.gouv.fr/"}), QString("foo.gouv.fr"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://fr.wikipedia.org/wiki/%C3%89l%C3%A9phant"}), QString("wikipedia.org"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://foo.günstigbestellen.de/"}), QString("foo.xn--gnstigbestellen-zvb.de"));
+ QCOMPARE(qWebEngineGetDomainAndRegistry({"https://foo.g\u00fcnstigbestellen.de/"}), QString("foo.xn--gnstigbestellen-zvb.de"));
+}
+
+
+QTEST_MAIN(tst_GetDomainAndRegistry)
+#include "tst_getdomainandregistry.moc"
diff --git a/tests/auto/core/origins/CMakeLists.txt b/tests/auto/core/origins/CMakeLists.txt
index 4a4219267..306074994 100644
--- a/tests/auto/core/origins/CMakeLists.txt
+++ b/tests/auto/core/origins/CMakeLists.txt
@@ -9,6 +9,7 @@ qt_internal_add_test(tst_origins
tst_origins.cpp
LIBRARIES
Qt::WebEngineCore
+ Qt::WebEngineWidgets
Test::HttpServer
Test::Util
)
@@ -32,6 +33,8 @@ set(tst_origins_resource_files
"resources/viewSource.html"
"resources/websocket.html"
"resources/websocket2.html"
+ "resources/red.png"
+ "resources/fetchApi.html"
)
qt_internal_add_resource(tst_origins "tst_origins"
diff --git a/tests/auto/core/origins/resources/fetchApi.html b/tests/auto/core/origins/resources/fetchApi.html
new file mode 100644
index 000000000..7b54416fd
--- /dev/null
+++ b/tests/auto/core/origins/resources/fetchApi.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+<body>
+ <script type="text/javascript">
+ const queryString = window.location.search;
+ const urlParams = new URLSearchParams(queryString);
+ const url = urlParams.get('url');
+ const f = fetch(url);
+ if (urlParams.get('printRes') == 'true') {
+ f.then((r) => r.text()).then((p) => console.log(p));
+ }
+ </script>
+</body>
+</html>
diff --git a/tests/auto/core/origins/resources/link.html b/tests/auto/core/origins/resources/link.html
new file mode 100644
index 000000000..297b9b273
--- /dev/null
+++ b/tests/auto/core/origins/resources/link.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Link</title>
+ </head>
+ <body>
+ <a id="link" href="">Link</a>
+ <script>
+ const urlParams = new URLSearchParams(window.location.search);
+ document.getElementById("link").href = urlParams.get('linkLocation');
+ </script>
+ </body>
+</html>
diff --git a/tests/auto/core/origins/resources/mixedSchemes.html b/tests/auto/core/origins/resources/mixedSchemes.html
index 53c8c83ff..3e50c2c3b 100644
--- a/tests/auto/core/origins/resources/mixedSchemes.html
+++ b/tests/auto/core/origins/resources/mixedSchemes.html
@@ -6,23 +6,34 @@
var result;
var canary;
- function setIFrameUrl(url) {
+ function setIFrameUrl(frameUrl,imgUrl) {
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"; }, 3000);
+ let img = document.createElement('img');
+ img.onerror = function() {
+ console.log("TEST:cannotLoad");
+ console.log("TEST:done");
+ };
+ img.onload = function() {
+ document.getElementById("iframe").setAttribute("src", frameUrl);
+ };
+ img.src = imgUrl
}
addEventListener("load", function() {
document.getElementById("iframe").addEventListener("load", function() {
- if (canary && window[0].canary)
- result = "canLoadAndAccess";
- else
- result = "canLoadButNotAccess";
+ if (canary && window[0].canary) {
+ console.log("TEST:canLoadAndAccess");
+ console.log("TEST:done");
+ } else {
+ console.log("TEST:canLoadButNotAccess");
+ console.log("TEST:done");
+ }
});
});
+ window.onerror = function(message, url, line, col, errorObj) {
+ return true;
+ };
</script>
</head>
<body>
diff --git a/tests/auto/core/origins/resources/mixedSchemes_frame.html b/tests/auto/core/origins/resources/mixedSchemes_frame.html
index 40ace2d2f..9499caa1f 100644
--- a/tests/auto/core/origins/resources/mixedSchemes_frame.html
+++ b/tests/auto/core/origins/resources/mixedSchemes_frame.html
@@ -3,9 +3,12 @@
<head>
<title>Mixed - Frame</title>
<script>
- console.log('Frame Loaded');
- var canary = true;
- parent.canary = true;
+ try{
+ var canary = true;
+ parent.canary = true;
+ }catch(exception){
+ };
+
</script>
</head>
<body></body>
diff --git a/tests/auto/core/origins/resources/red.png b/tests/auto/core/origins/resources/red.png
new file mode 100644
index 000000000..5ae85192b
--- /dev/null
+++ b/tests/auto/core/origins/resources/red.png
Binary files differ
diff --git a/tests/auto/core/origins/tst_origins.cpp b/tests/auto/core/origins/tst_origins.cpp
index 71285dcf0..81385701f 100644
--- a/tests/auto/core/origins/tst_origins.cpp
+++ b/tests/auto/core/origins/tst_origins.cpp
@@ -13,6 +13,7 @@
#include <QtWebEngineCore/qwebenginesettings.h>
#include <QtWebEngineCore/qwebengineprofile.h>
#include <QtWebEngineCore/qwebenginepage.h>
+#include <QtWebEngineWidgets/qwebengineview.h>
#if defined(WEBSOCKETS)
#include <QtWebSockets/qwebsocket.h>
@@ -150,7 +151,15 @@ void registerSchemes()
scheme.setFlags(QWebEngineUrlScheme::LocalScheme | QWebEngineUrlScheme::CorsEnabled);
QWebEngineUrlScheme::registerScheme(scheme);
}
-
+ {
+ QWebEngineUrlScheme scheme("fetchapi-allowed");
+ scheme.setFlags(QWebEngineUrlScheme::CorsEnabled | QWebEngineUrlScheme::FetchApiAllowed);
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
+ {
+ QWebEngineUrlScheme scheme("fetchapi-not-allowed");
+ QWebEngineUrlScheme::registerScheme(scheme);
+ }
}
Q_CONSTRUCTOR_FUNCTION(registerSchemes)
@@ -262,6 +271,23 @@ public:
messages << message;
qCDebug(lc) << message;
}
+
+ bool logContainsDoneMarker() const { return messages.contains("TEST:done"); }
+
+ QString findResultInLog() const
+ {
+ // make sure we do not have some extra logs from blink
+ for (auto message : messages) {
+ QStringList s = message.split(':');
+ if (s.size() > 1 && s[0] == "TEST")
+ return s[1];
+ }
+ return QString();
+ }
+
+ void clearLog() { messages.clear(); }
+
+private:
QStringList messages;
};
@@ -281,6 +307,9 @@ private Q_SLOTS:
void subdirWithoutAccess();
void fileAccessRemoteUrl_data();
void fileAccessRemoteUrl();
+ void fileAccessLocalUrl_data();
+ void fileAccessLocalUrl();
+ void mixedSchemes_data();
void mixedSchemes();
void mixedSchemesWithCsp();
void mixedXHR_data();
@@ -305,6 +334,9 @@ private Q_SLOTS:
void redirectInterceptorSecure();
void redirectInterceptorFile();
void redirectInterceptorHttp();
+ void fetchApiCustomUrl_data();
+ void fetchApiCustomUrl();
+ void fetchApiHttpUrl();
private:
bool verifyLoad(const QUrl &url)
@@ -577,13 +609,22 @@ void tst_Origins::subdirWithoutAccess()
void tst_Origins::fileAccessRemoteUrl_data()
{
QTest::addColumn<bool>("EnableAccess");
- QTest::addRow("enabled") << true;
- QTest::addRow("disabled") << false;
+ QTest::addColumn<bool>("UserGesture");
+ QTest::addRow("enabled, XHR") << true << false;
+ QTest::addRow("enabled, link click") << true << true;
+ QTest::addRow("disabled, XHR") << false << false;
+ QTest::addRow("disabled, link click") << false << true;
}
void tst_Origins::fileAccessRemoteUrl()
{
QFETCH(bool, EnableAccess);
+ QFETCH(bool, UserGesture);
+
+ QWebEngineView view;
+ view.setPage(m_page);
+ view.resize(800, 600);
+ view.show();
HttpServer server;
server.setResourceDirs({ QDir(QT_TESTCASE_SOURCEDIR).canonicalPath() + "/resources" });
@@ -592,11 +633,88 @@ void tst_Origins::fileAccessRemoteUrl()
ScopedAttribute sa1(m_page->settings(), QWebEngineSettings::LocalContentCanAccessRemoteUrls, EnableAccess);
ScopedAttribute sa2(m_page->settings(), QWebEngineSettings::ErrorPageEnabled, false);
- QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
- + "/resources/mixedXHR.html"));
+ if (UserGesture) {
+ QString remoteUrl(server.url("/link.html").toString());
+#ifdef Q_OS_WIN
+ QString localUrl("file:///" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/link.html?linkLocation=" + remoteUrl);
+#else
+ QString localUrl("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/link.html?linkLocation=" + remoteUrl);
+#endif
+
+ QVERIFY(verifyLoad(localUrl));
+
+ QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, elementCenter(m_page, "link"));
+ // Succeed independently of EnableAccess == false
+ QTRY_COMPARE(m_page->url(), remoteUrl);
+
+ // Back/forward navigation is also allowed, however they are not user gesture
+ m_page->triggerAction(QWebEnginePage::Back);
+ QTRY_COMPARE(m_page->url(), localUrl);
+ m_page->triggerAction(QWebEnginePage::Forward);
+ QTRY_COMPARE(m_page->url(), remoteUrl);
+ } else {
+ QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/mixedXHR.html"));
+ eval("sendXHR('" + server.url("/mixedXHR.txt").toString() + "')");
+ QTRY_COMPARE(eval("result"), (EnableAccess ? QString("ok") : QString("error")));
+ }
+}
+
+void tst_Origins::fileAccessLocalUrl_data()
+{
+ QTest::addColumn<bool>("EnableAccess");
+ QTest::addColumn<bool>("UserGesture");
+ QTest::addRow("enabled, XHR") << true << false;
+ QTest::addRow("enabled, link click") << true << true;
+ QTest::addRow("disabled, XHR") << false << false;
+ QTest::addRow("disabled, link click") << false << true;
+}
+
+void tst_Origins::fileAccessLocalUrl()
+{
+ QFETCH(bool, EnableAccess);
+ QFETCH(bool, UserGesture);
+
+ QWebEngineView view;
+ view.setPage(m_page);
+ view.resize(800, 600);
+ view.show();
+
+ ScopedAttribute sa1(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, EnableAccess);
+ ScopedAttribute sa2(m_page->settings(), QWebEngineSettings::ErrorPageEnabled, false);
+
+ if (UserGesture) {
+#ifdef Q_OS_WIN
+ QString localUrl1("file:///" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/link.html?linkLocation=link.html");
+ QString localUrl2("file:///" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/link.html");
+#else
+ QString localUrl1("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/link.html?linkLocation=link.html");
+ QString localUrl2("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/link.html");
+#endif
- eval("sendXHR('" + server.url("/mixedXHR.txt").toString() + "')");
- QTRY_COMPARE(eval("result"), (EnableAccess ? QString("ok") : QString("error")));
+ QVERIFY(verifyLoad(localUrl1));
+ QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, elementCenter(m_page, "link"));
+ // Succeed independently of EnableAccess == false
+ QTRY_COMPARE(m_page->url(), localUrl2);
+
+ // Back/forward navigation is also allowed, however they are not user gesture
+ m_page->triggerAction(QWebEnginePage::Back);
+ QTRY_COMPARE(m_page->url(), localUrl1);
+ m_page->triggerAction(QWebEnginePage::Forward);
+ QTRY_COMPARE(m_page->url(), localUrl2);
+ } else {
+ QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/mixedXHR.html"));
+ eval("sendXHR('file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + "/resources/mixedXHR.txt" + "')");
+ QTRY_COMPARE(eval("result"), (EnableAccess ? QString("ok") : QString("error")));
+ }
}
// Load the main page over one scheme with an iframe over another scheme.
@@ -607,89 +725,135 @@ void tst_Origins::fileAccessRemoteUrl()
// 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()
+void tst_Origins::mixedSchemes_data()
{
- ScopedAttribute sa(m_page->settings(), QWebEngineSettings::ErrorPageEnabled, false);
+ QTest::addColumn<QString>("schemeFrom");
+ QTest::addColumn<QVariantMap>("testPairs");
- QVERIFY(verifyLoad("file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
- + "/resources/mixedSchemes.html"));
- eval("setIFrameUrl('file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
- + "/resources/mixedSchemes_frame.html')");
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess")));
- eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
- eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad")));
-
- QVERIFY(verifyLoad(QSL("qrc:/resources/mixedSchemes.html")));
- eval("setIFrameUrl('file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
- + "/resources/mixedSchemes_frame.html')");
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad")));
- eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess")));
- eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
+ QVariant SLF = QVariant(QSL("canLoadAndAccess")), OK = QVariant(QSL("canLoadButNotAccess")),
+ ERR = QVariant(QSL("cannotLoad"));
+ std::vector<std::pair<const char *, std::vector<std::pair<const char *, QVariant>>>> data = {
+ { "file",
+ {
+ { "file", SLF },
+ { "qrc", OK },
+ { "tst", ERR },
+ } },
+ { "qrc",
+ {
+ { "file", ERR },
+ { "qrc", SLF },
+ { "tst", OK },
+ } },
+ { "tst",
+ {
+ { "file", ERR },
+ { "qrc", OK },
+ { "tst", SLF },
+ } },
+ { "PathSyntax",
+ {
+ { "PathSyntax", SLF },
+ { "PathSyntax-Local", ERR },
+ { "PathSyntax-LocalAccessAllowed", OK },
+ { "PathSyntax-NoAccessAllowed", OK },
+ } },
+ { "PathSyntax-LocalAccessAllowed",
+ {
+ { "PathSyntax", OK },
+ { "PathSyntax-Local", OK },
+ { "PathSyntax-LocalAccessAllowed", SLF },
+ { "PathSyntax-NoAccessAllowed", OK },
+ } },
+ { "PathSyntax-NoAccessAllowed",
+ {
+ { "PathSyntax", OK },
+ { "PathSyntax-Local", ERR },
+ { "PathSyntax-LocalAccessAllowed", OK },
+ { "PathSyntax-NoAccessAllowed", OK },
+ } },
+ { "HostSyntax://a",
+ {
+ { "HostSyntax://a", SLF },
+ { "HostSyntax://b", OK },
+ } },
+ { "local-localaccess",
+ {
+ { "local-cors", OK },
+ { "local-localaccess", SLF },
+ { "local", OK },
+ } },
+ { "local-cors",
+ {
+ { "local", OK },
+ { "local-cors", SLF },
+ } },
+ };
- QVERIFY(verifyLoad(QSL("tst:/resources/mixedSchemes.html")));
- eval("setIFrameUrl('file:" + QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
- + "/resources/mixedSchemes_frame.html')");
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad")));
- eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
- eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess")));
+ for (auto &&d : data) {
+ auto schemeFrom = d.first;
+ QVariantMap testPairs;
+ for (auto &&destSchemes : d.second) {
+ auto &&destScheme = destSchemes.first;
+ testPairs[destScheme] = destSchemes.second;
+ }
+ QTest::addRow("%s", schemeFrom) << schemeFrom << testPairs;
+ }
+}
- QVERIFY(verifyLoad(QSL("PathSyntax:/resources/mixedSchemes.html")));
- eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess")));
- eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad")));
- eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
- eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
+static QStringList protocolAndHost(const QString scheme)
+{
+ static QString srcDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath());
+ QStringList result;
+ if (scheme == QSL("file")) {
+ return QStringList{ scheme, srcDir };
+ }
+ if (scheme.contains(QSL("HostSyntax:"))) {
+ const QStringList &res = scheme.split(':');
+ Q_ASSERT(res.size() == 2);
+ return res;
+ }
+ return QStringList{ scheme, "" };
+}
- QVERIFY(verifyLoad(QSL("PathSyntax-LocalAccessAllowed:/resources/mixedSchemes.html")));
- eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
- eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
- eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess")));
- eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
+void tst_Origins::mixedSchemes()
+{
+ QFETCH(QString, schemeFrom);
+ QFETCH(QVariantMap, testPairs);
- QVERIFY(verifyLoad(QSL("PathSyntax-NoAccessAllowed:/resources/mixedSchemes.html")));
- eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
- eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad")));
- eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
- eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
+ ScopedAttribute sa(m_page->settings(), QWebEngineSettings::ErrorPageEnabled, false);
+ QString srcDir(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath());
+ QString host;
+ auto pah = protocolAndHost(schemeFrom);
+ auto loadUrl = QString("%1:%2/resources/mixedSchemes.html").arg(pah[0]).arg(pah[1]);
+ QVERIFY(verifyLoad(loadUrl));
- 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")));
- eval(QSL("setIFrameUrl('HostSyntax://b/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
+ QStringList schemesTo, expected, results;
+ for (auto it = testPairs.begin(), end = testPairs.end(); it != end; ++it) {
- QVERIFY(verifyLoad(QSL("local-localaccess:/resources/mixedSchemes.html")));
- eval("setIFrameUrl('local-cors:/resources/mixedSchemes_frame.html')");
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
- eval(QSL("setIFrameUrl('local-localaccess:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess")));
- eval(QSL("setIFrameUrl('local:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
+ auto schemeTo = it.key();
+ auto pah = protocolAndHost(schemeTo);
+ auto expectedResult = it.value().toString();
+ auto frameUrl = QString("%1:%2/resources/mixedSchemes_frame.html").arg(pah[0]).arg(pah[1]);
+ auto imgUrl = QString("%1:%2/resources/red.png").arg(pah[0]).arg(pah[1]);
- QVERIFY(verifyLoad(QSL("local-cors:/resources/mixedSchemes.html")));
- eval("setIFrameUrl('local:/resources/mixedSchemes_frame.html')");
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
- eval(QSL("setIFrameUrl('local-cors:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess")));
- eval(QSL("setIFrameUrl('local:/resources/mixedSchemes_frame.html')"));
- QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess")));
+ eval(QString("setIFrameUrl('%1','%2')").arg(frameUrl).arg(imgUrl));
+
+ // wait for token in the log
+ QTRY_VERIFY(m_page->logContainsDoneMarker());
+ const QString result = m_page->findResultInLog();
+ m_page->clearLog();
+ schemesTo.append(schemeTo.rightJustified(20));
+ results.append(result.rightJustified(20));
+ expected.append(expectedResult.rightJustified(20));
+ }
+
+ QVERIFY2(results == expected,
+ qPrintable(QString("\nFrom '%1' to:\n\tScheme: %2\n\tActual: %3\n\tExpect: %4")
+ .arg(schemeFrom)
+ .arg(schemesTo.join(' '))
+ .arg(results.join(' '))
+ .arg(expected.join(' '))));
}
// Like mixedSchemes but adds a Content-Security-Policy: frame-src 'none' header.
@@ -1005,12 +1169,17 @@ void tst_Origins::mixedContent()
auto setIFrameUrl = [&] (const QString &scheme) {
if (scheme == "data")
- return QString("setIFrameUrl('data:,<script>var canary = true; parent.canary = true</script>')");
+ return QString("setIFrameUrl('data:,<script>var canary = true; parent.canary = "
+ "true</script>','data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA"
+ "AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/"
+ "w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==')");
auto frameUrl = QString("%1:%2/resources/mixedSchemes_frame.html").arg(scheme).arg(scheme == "file" ? srcDir : "");
- return QString("setIFrameUrl('%1')").arg(frameUrl);
+ auto imgUrl =
+ QString("%1:%2/resources/red.png").arg(scheme).arg(scheme == "file" ? srcDir : "");
+ return QString("setIFrameUrl('%1','%2')").arg(frameUrl).arg(imgUrl);
};
- m_page->messages.clear();
+ m_page->clearLog();
QStringList schemesTo, expected, results;
for (auto it = testPairs.begin(), end = testPairs.end(); it != end; ++it) {
@@ -1019,15 +1188,10 @@ void tst_Origins::mixedContent()
eval(setIFrameUrl(schemeTo));
- QTRY_COMPARE(eval(QSL("result !== undefined")), QVariant(true));
- auto result = eval(QSL("result")).toString();
- // Work-around some combinations missing JS loaded signals:
- if (m_page->messages.size() > 0) {
- if (m_page->messages[0] == QSL("Frame Loaded") && result == QSL("cannotLoad"))
- result = QSL("canLoadButNotAccess");
- m_page->messages.clear();
- }
-
+ // wait for token in the log
+ QTRY_VERIFY(m_page->logContainsDoneMarker());
+ const QString result = m_page->findResultInLog();
+ m_page->clearLog();
schemesTo.append(schemeTo.rightJustified(20));
results.append(result.rightJustified(20));
expected.append(expectedResult.rightJustified(20));
@@ -1447,5 +1611,120 @@ void tst_Origins::localMediaBlock()
}
+class FetchApiHandler : public QWebEngineUrlSchemeHandler
+{
+ Q_OBJECT
+public:
+ FetchApiHandler(QByteArray schemeName, QObject *parent = nullptr)
+ : QWebEngineUrlSchemeHandler(parent), m_schemeName(schemeName)
+ {
+ }
+
+ void requestStarted(QWebEngineUrlRequestJob *job) override
+ {
+ QCOMPARE(job->requestUrl(), QUrl(m_schemeName + ":about"));
+ fetchWasAllowed = true;
+ }
+
+ bool fetchWasAllowed = false;
+
+private:
+ QByteArray m_schemeName;
+};
+
+class FetchApiPage : public QWebEnginePage
+{
+ Q_OBJECT
+
+signals:
+ void jsCalled();
+
+public:
+ FetchApiPage(QWebEngineProfile *profile, QObject *parent = nullptr)
+ : QWebEnginePage(profile, parent)
+ {
+ }
+
+protected:
+ void javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel,
+ const QString &message, int, const QString &) override
+ {
+ qCritical() << "js:" << message;
+ emit jsCalled();
+ }
+};
+
+void tst_Origins::fetchApiCustomUrl_data()
+{
+ QTest::addColumn<QUrl>("url");
+ QTest::addColumn<QByteArray>("fetchApiScheme");
+ QTest::addColumn<bool>("expectedFetchWasAllowed");
+
+ QTest::newRow("custom url with fetch allowed flag")
+ << QUrl("qrc:///resources/fetchApi.html?printRes=false&url=fetchapi-allowed:about")
+ << QBAL("fetchapi-allowed") << true;
+ QTest::newRow("custom url without fetch allowed flag")
+ << QUrl("qrc:///resources/fetchApi.html?printRes=false&url=fetchapi-not-allowed:about")
+ << QBAL("fetchapi-not-allowed") << false;
+}
+
+void tst_Origins::fetchApiCustomUrl()
+{
+ QFETCH(QUrl, url);
+ QFETCH(QByteArray, fetchApiScheme);
+ QFETCH(bool, expectedFetchWasAllowed);
+
+ QWebEngineProfile profile;
+ FetchApiHandler handler(fetchApiScheme);
+
+ profile.installUrlSchemeHandler(fetchApiScheme, &handler);
+
+ FetchApiPage page(&profile);
+ QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
+ QSignalSpy jsSpy(&page, SIGNAL(jsCalled()));
+
+ if (fetchApiScheme == "fetchapi-not-allowed") {
+ QTest::ignoreMessage(QtCriticalMsg, QRegularExpression("Failed to fetch"));
+ QTest::ignoreMessage(
+ QtCriticalMsg,
+ QRegularExpression("Fetch API cannot load fetchapi-not-allowed:about."));
+ }
+
+ page.load(url);
+ QTRY_VERIFY(loadSpy.count() > 0);
+ QTRY_COMPARE(handler.fetchWasAllowed, expectedFetchWasAllowed);
+
+ if (fetchApiScheme == "fetchapi-not-allowed") {
+ QTRY_VERIFY(jsSpy.count() > 0);
+ }
+}
+
+void tst_Origins::fetchApiHttpUrl()
+{
+ HttpServer httpServer;
+ QObject::connect(&httpServer, &HttpServer::newRequest, this, [](HttpReqRep *rr) {
+ rr->setResponseBody(QBAL("Fetch Was Allowed"));
+ rr->setResponseHeader(QBAL("Access-Control-Allow-Origin"), QBAL("*"));
+ rr->sendResponse();
+ });
+ QVERIFY(httpServer.start());
+
+ QWebEngineProfile profile;
+ FetchApiPage page(&profile);
+
+ QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
+ QSignalSpy jsSpy(&page, SIGNAL(jsCalled()));
+
+ QTest::ignoreMessage(QtCriticalMsg, QRegularExpression("Fetch Was Allowed"));
+
+ const QByteArray fullUrl = QByteArray("qrc:///resources/fetchApi.html?printRes=true&url=")
+ + httpServer.url("/somepage.html").toEncoded();
+ page.load(QUrl(fullUrl));
+
+ QTRY_VERIFY(loadSpy.count() > 0);
+ QTRY_VERIFY(jsSpy.count() > 0);
+ QVERIFY(httpServer.stop());
+}
+
QTEST_MAIN(tst_Origins)
#include "tst_origins.moc"
diff --git a/tests/auto/core/qtversion/CMakeLists.txt b/tests/auto/core/qtversion/CMakeLists.txt
new file mode 100644
index 000000000..9a5e89266
--- /dev/null
+++ b/tests/auto/core/qtversion/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_internal_add_test(tst_qtversion
+ SOURCES
+ tst_qtversion.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+)
+
diff --git a/tests/auto/core/qtversion/tst_qtversion.cpp b/tests/auto/core/qtversion/tst_qtversion.cpp
new file mode 100644
index 000000000..44c2d4e5c
--- /dev/null
+++ b/tests/auto/core/qtversion/tst_qtversion.cpp
@@ -0,0 +1,34 @@
+// Copyright (C) 2023 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 <QtWebEngineCore/qwebenginepage.h>
+
+class tst_QtVersion : public QObject
+{
+ Q_OBJECT
+signals:
+ void done();
+private Q_SLOTS:
+ void checkVersion();
+};
+
+void tst_QtVersion::checkVersion()
+{
+ QWebEnginePage page;
+ QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished);
+ QSignalSpy doneSpy(this, &tst_QtVersion::done);
+ page.load(QUrl("chrome://qt"));
+ QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 12000);
+ page.toPlainText([this](const QString &result) {
+ QVERIFY(result.contains(qWebEngineVersion()));
+ QVERIFY(result.contains(qWebEngineChromiumVersion()));
+ QVERIFY(result.contains(qWebEngineChromiumSecurityPatchVersion()));
+ emit done();
+ });
+ QTRY_VERIFY(doneSpy.size());
+}
+
+QTEST_MAIN(tst_QtVersion)
+
+#include "tst_qtversion.moc"
diff --git a/tests/auto/core/qwebengineclientcertificatestore/CMakeLists.txt b/tests/auto/core/qwebengineclientcertificatestore/CMakeLists.txt
index 5b920f999..8cee7f630 100644
--- a/tests/auto/core/qwebengineclientcertificatestore/CMakeLists.txt
+++ b/tests/auto/core/qwebengineclientcertificatestore/CMakeLists.txt
@@ -34,40 +34,46 @@ qt_internal_add_resource(tst_qwebengineclientcertificatestore "tst_qwebenginecli
${tst_qwebengineclientcertificatestore_resource_files}
)
-if(LINUX)
+if(LINUX AND NOT CMAKE_CROSSCOMPILING)
get_filename_component(homePath $ENV{HOME} ABSOLUTE)
find_program(pk12util_EXECUTABLE NAMES pk12util)
+ find_program(certutil_EXECUTABLE NAMES certutil)
- if(pk12util_EXECUTABLE)
+ if(pk12util_EXECUTABLE AND certutil_EXECUTABLE)
add_custom_command(
DEPENDS resources/client2.p12
- COMMAND ${pk12util_EXECUTABLE}
- -d sql:"${homePath}/.pki/nssdb"
- -n qwebengineclientcertificatestore
+ COMMAND test -e "${homePath}/.pki/nssdb" || ${CMAKE_COMMAND} -E make_directory
+ "${homePath}/.pki/nssdb"
+ COMMAND test -e "${homePath}/.pki/nssdb/cert9.db" || ${certutil_EXECUTABLE}
+ -N --empty-password -d sql:${homePath}/.pki/nssdb
+ COMMAND test -e "${homePath}/.pki/nssdb/cert9.db" && ${pk12util_EXECUTABLE}
+ -d sql:${homePath}/.pki/nssdb
-i "${CMAKE_CURRENT_LIST_DIR}/resources/client2.p12"
- -W \"\"
+ -W ""
COMMAND ${CMAKE_COMMAND} -E touch pk12util.stamp
OUTPUT pk12util.stamp
+ VERBATIM
+ USES_TERMINAL
)
add_custom_target(
add-user-personal-certificate
DEPENDS pk12util.stamp
)
qt_internal_extend_target(tst_qwebengineclientcertificatestore DEFINES TEST_NSS)
- add_dependencies(tst_qwebengineclientcertificatestore add-user-personal-certificate)
+ add_dependencies(tst_qwebengineclientcertificatestore add-user-personal-certificate)
endif()
find_program(certutil_EXECUTABLE NAMES certutil)
if(certutil_EXECUTABLE)
add_custom_target(remove-user-personal-certificate
+ COMMAND ${CMAKE_COMMAND} -E remove pk12util.stamp
COMMAND ${certutil_EXECUTABLE}
-d sql:"${homePath}/.pki/nssdb"
-D
-n qwebengineclientcertificatestore
- COMMAND ${CMAKE_COMMAND} -E remove pk12util.stamp
)
endif()
endif()
diff --git a/tests/auto/core/qwebengineclientcertificatestore/resources/client2.p12 b/tests/auto/core/qwebengineclientcertificatestore/resources/client2.p12
index feccd77e1..81e7eb624 100644
--- a/tests/auto/core/qwebengineclientcertificatestore/resources/client2.p12
+++ b/tests/auto/core/qwebengineclientcertificatestore/resources/client2.p12
Binary files differ
diff --git a/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp b/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp
index 8b6822148..3fff2cd45 100644
--- a/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp
+++ b/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp
@@ -34,6 +34,7 @@ private Q_SLOTS:
// as it checks storage manipulation without navigation
void setAndDeleteCookie();
+ void setInvalidCookie();
void cookieSignals();
void batchCookieTasks();
void basicFilter();
@@ -148,6 +149,37 @@ void tst_QWebEngineCookieStore::setAndDeleteCookie()
QWE_TRY_COMPARE(cookieRemovedSpy.size(), 2);
}
+void tst_QWebEngineCookieStore::setInvalidCookie()
+{
+ QWebEnginePage page(m_profile);
+ QWebEngineCookieStore *client = m_profile->cookieStore();
+
+ QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
+ QSignalSpy cookieAddedSpy(client, SIGNAL(cookieAdded(const QNetworkCookie &)));
+ QSignalSpy cookieRemovedSpy(client, SIGNAL(cookieRemoved(const QNetworkCookie &)));
+
+ QNetworkCookie goodCookie(
+ QNetworkCookie::parseCookies(
+ QByteArrayLiteral("khaos=I9GX8CWI; Domain=.example.com; Path=/docs"))
+ .first());
+ QNetworkCookie badCookie(
+ QNetworkCookie::parseCookies(QByteArrayLiteral("TestCookie=foo\tbar;")).first());
+
+ // force to init storage as it's done lazily upon first navigation
+ client->loadAllCookies();
+ // /* FIXME remove 'blank' navigation once loadAllCookies api is fixed
+ page.load(QUrl("about:blank"));
+ QWE_TRY_COMPARE(loadSpy.size(), 1);
+ // */
+
+ client->setCookie(badCookie);
+ client->setCookie(goodCookie);
+ client->deleteCookie(goodCookie);
+ // by the time the second cookie is removed, only one cookie should have been added
+ QWE_TRY_COMPARE(cookieRemovedSpy.size(), 1);
+ QWE_TRY_COMPARE(cookieAddedSpy.size(), 1);
+}
+
void tst_QWebEngineCookieStore::batchCookieTasks()
{
QWebEnginePage page(m_profile);
diff --git a/tests/auto/core/qwebengineframe/CMakeLists.txt b/tests/auto/core/qwebengineframe/CMakeLists.txt
new file mode 100644
index 000000000..d02b4307d
--- /dev/null
+++ b/tests/auto/core/qwebengineframe/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+include(../../util/util.cmake)
+
+qt_internal_add_test(tst_qwebengineframe
+ SOURCES
+ tst_qwebengineframe.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+ Qt::WebEngineWidgets
+ Test::Util
+)
+
+qt_internal_add_resource(tst_qwebengineframe "tst_qwebengineframe"
+ PREFIX
+ "/"
+ FILES
+ "resources/frameset.html"
+ "resources/iframes.html"
+ "resources/nesting-iframe.html"
+)
diff --git a/tests/auto/core/qwebengineframe/resources/frameset.html b/tests/auto/core/qwebengineframe/resources/frameset.html
new file mode 100644
index 000000000..53f5e6638
--- /dev/null
+++ b/tests/auto/core/qwebengineframe/resources/frameset.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head><title>Test-title</title></head>
+ <script>
+ window.name = 'test-main-frame'
+ onload = (e) => {
+ const frames = window.frames;
+ for (let i = 0; i < frames.length; i++) {
+ frames[i].name = 'test-subframe' + i;
+ }
+ };
+ </script>
+ <frameset cols="50%, 50%">
+ <frameset cols="50%, 50%">
+ <frame style="border: red dashed 1em;"/>
+ <frame style="border: green dashed 1em;"/>
+ </frameset>
+ <frame style="border: blue solid 1em;"/>
+ </frameset>
+</html>
diff --git a/tests/auto/core/qwebengineframe/resources/iframes.html b/tests/auto/core/qwebengineframe/resources/iframes.html
new file mode 100644
index 000000000..648acb166
--- /dev/null
+++ b/tests/auto/core/qwebengineframe/resources/iframes.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+ <head><title>Test-title</title></head>
+ <script>
+ window.name = 'test-main-frame'
+ onload = (e) => {
+ const frames = window.frames;
+ for (let i = 0; i < frames.length; i++) {
+ frames[i].name = 'test-subframe' + i;
+ }
+ };
+ </script>
+ <body>
+ <iframe name="iframe0-300x200" width="300" height="200"></iframe>
+ <iframe name="iframe1-350x250" width="350" height="250"></iframe>
+ </body>
+</html>
diff --git a/tests/auto/core/qwebengineframe/resources/nesting-iframe.html b/tests/auto/core/qwebengineframe/resources/nesting-iframe.html
new file mode 100644
index 000000000..cd784e3dd
--- /dev/null
+++ b/tests/auto/core/qwebengineframe/resources/nesting-iframe.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<html>
+ <head><title>Test-title</title></head>
+ <body>
+ <iframe name="iframe2-parent" src="iframes.html"></iframe>
+ </body>
+</html>
diff --git a/tests/auto/core/qwebengineframe/tst_qwebengineframe.cpp b/tests/auto/core/qwebengineframe/tst_qwebengineframe.cpp
new file mode 100644
index 000000000..7cd075443
--- /dev/null
+++ b/tests/auto/core/qwebengineframe/tst_qwebengineframe.cpp
@@ -0,0 +1,194 @@
+/*
+ Copyright (C) 2024 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 <QtWebEngineCore/qwebengineframe.h>
+
+class tst_QWebEngineFrame : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void mainFrame();
+ void findFrameByName();
+ void isValid();
+ void name();
+ void htmlName();
+ void children();
+ void childrenOfInvalidFrame();
+ void url();
+ void size();
+ void runJavaScript();
+
+private:
+};
+
+void tst_QWebEngineFrame::mainFrame()
+{
+ QWebEnginePage page;
+ QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) };
+ page.load(QUrl("qrc:/resources/frameset.html"));
+ QTRY_COMPARE(loadSpy.size(), 1);
+ auto frame = page.mainFrame();
+ QVERIFY(frame.isValid());
+}
+
+void tst_QWebEngineFrame::findFrameByName()
+{
+ QWebEnginePage page;
+ QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) };
+ page.load(QUrl("qrc:/resources/iframes.html"));
+ QTRY_COMPARE(loadSpy.size(), 1);
+ auto maybeFrame = page.findFrameByName("test-subframe0");
+ QVERIFY(maybeFrame.has_value());
+ QCOMPARE(maybeFrame->name(), "test-subframe0");
+ QVERIFY(!page.findFrameByName("foobar").has_value());
+}
+
+void tst_QWebEngineFrame::isValid()
+{
+ QWebEnginePage page;
+ QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) };
+ page.load(QUrl("qrc:/resources/iframes.html"));
+ QTRY_COMPARE(loadSpy.size(), 1);
+ auto firstPageSubframe = page.findFrameByName("test-subframe0");
+ QVERIFY(firstPageSubframe && firstPageSubframe->isValid());
+
+ page.load(QUrl("qrc:/resources/frameset.html"));
+ QTRY_COMPARE(loadSpy.size(), 2);
+ QVERIFY(!firstPageSubframe->isValid());
+}
+
+void tst_QWebEngineFrame::name()
+{
+ QWebEnginePage page;
+ QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) };
+ page.load(QUrl("qrc:/resources/frameset.html"));
+ QTRY_COMPARE(loadSpy.size(), 1);
+ QCOMPARE(page.mainFrame().name(), "test-main-frame");
+ auto children = page.mainFrame().children();
+ QCOMPARE(children.at(0).name(), "test-subframe0");
+ QCOMPARE(children.at(1).name(), "test-subframe1");
+ QCOMPARE(children.at(2).name(), "test-subframe2");
+
+ page.load(QUrl("qrc:/resources/iframes.html"));
+ QTRY_COMPARE(loadSpy.size(), 2);
+ QVERIFY(!children.at(0).isValid());
+ QCOMPARE(children.at(0).name(), QString());
+}
+
+void tst_QWebEngineFrame::htmlName()
+{
+ QWebEnginePage page;
+ QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) };
+ page.load(QUrl("qrc:/resources/iframes.html"));
+ QTRY_COMPARE(loadSpy.size(), 1);
+ auto children = page.mainFrame().children();
+ QCOMPARE(children.at(0).name(), "test-subframe0");
+ QCOMPARE(children.at(0).htmlName(), "iframe0-300x200");
+ QCOMPARE(children.at(1).name(), "test-subframe1");
+ QCOMPARE(children.at(1).htmlName(), "iframe1-350x250");
+
+ page.load(QUrl("qrc:/resources/frameset.html"));
+ QTRY_COMPARE(loadSpy.size(), 2);
+ QVERIFY(!children.at(0).isValid());
+ QCOMPARE(children.at(0).htmlName(), QString());
+}
+
+void tst_QWebEngineFrame::children()
+{
+ QWebEnginePage page;
+ QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) };
+ page.load(QUrl("qrc:/resources/frameset.html"));
+ QTRY_COMPARE(loadSpy.size(), 1);
+ auto frame = page.mainFrame();
+ auto children = frame.children();
+ QCOMPARE(children.size(), 3);
+ for (auto child : children) {
+ QVERIFY(child.isValid());
+ }
+}
+
+void tst_QWebEngineFrame::childrenOfInvalidFrame()
+{
+ QWebEnginePage page;
+ QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) };
+ page.load(QUrl("qrc:/resources/nesting-iframe.html"));
+ QTRY_COMPARE(loadSpy.size(), 1);
+ auto nestedFrame = page.mainFrame().children().at(0);
+ QCOMPARE(nestedFrame.children().size(), 2);
+
+ page.load(QUrl("qrc:/resources/frameset.html"));
+ QTRY_COMPARE(loadSpy.size(), 2);
+ QVERIFY(!nestedFrame.isValid());
+ QVERIFY(nestedFrame.children().empty());
+}
+
+void tst_QWebEngineFrame::url()
+{
+ QWebEnginePage page;
+ QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) };
+ page.load(QUrl("qrc:/resources/nesting-iframe.html"));
+ QTRY_COMPARE(loadSpy.size(), 1);
+ auto children = page.mainFrame().children();
+ QCOMPARE(children.at(0).url(), QUrl("qrc:/resources/iframes.html"));
+
+ page.load(QUrl("qrc:/resources/frameset.html"));
+ QTRY_COMPARE(loadSpy.size(), 2);
+ QVERIFY(!children.at(0).isValid());
+ QCOMPARE(children.at(0).url(), QUrl());
+}
+
+void tst_QWebEngineFrame::size()
+{
+ QWebEnginePage page;
+ QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) };
+ page.load(QUrl("qrc:/resources/iframes.html"));
+ QTRY_COMPARE(loadSpy.size(), 1);
+ auto frame1 = *page.findFrameByName("test-subframe0");
+ auto size1 = frame1.size();
+ auto size2 = page.findFrameByName("test-subframe1")->size();
+ QCOMPARE(size1, QSizeF(300, 200));
+ QCOMPARE(size2, QSizeF(350, 250));
+
+ page.load(QUrl("qrc:/resources/frameset.html"));
+ QTRY_COMPARE(loadSpy.size(), 2);
+ QVERIFY(!frame1.isValid());
+ QCOMPARE(frame1.size(), QSizeF());
+}
+
+void tst_QWebEngineFrame::runJavaScript()
+{
+ QWebEnginePage page;
+ QSignalSpy loadSpy{ &page, SIGNAL(loadFinished(bool)) };
+ page.load(QUrl("qrc:/resources/iframes.html"));
+ QTRY_COMPARE(loadSpy.size(), 1);
+ auto children = page.mainFrame().children();
+ CallbackSpy<QVariant> spy;
+ children[0].runJavaScript("window.name", spy.ref());
+ auto result = spy.waitForResult();
+ QCOMPARE(result, QString("test-subframe0"));
+}
+
+QTEST_MAIN(tst_QWebEngineFrame)
+
+#include "tst_qwebengineframe.moc"
diff --git a/tests/auto/core/qwebengineglobalsettings/CMakeLists.txt b/tests/auto/core/qwebengineglobalsettings/CMakeLists.txt
new file mode 100644
index 000000000..dd2f28857
--- /dev/null
+++ b/tests/auto/core/qwebengineglobalsettings/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+include(../../httpserver/httpserver.cmake)
+include(../../util/util.cmake)
+
+qt_internal_add_test(tst_qwebengineglobalsettings
+ SOURCES
+ tst_qwebengineglobalsettings.cpp
+ LIBRARIES
+ Qt::Network
+ Qt::WebEngineCore
+ Test::HttpServer
+ Qt::WebEngineWidgets
+ Test::Util
+)
+
+# Resources:
+set(tst_qwebengineglobalsettings_resource_files
+ "cert/localhost.crt"
+ "cert/localhost.key"
+ "cert/RootCA.pem"
+)
+
+qt_add_resources(tst_qwebengineglobalsettings "tst_qwebengineglobalsettings"
+ PREFIX
+ "/"
+ FILES
+ ${tst_qwebengineglobalsettings_resource_files}
+)
diff --git a/tests/auto/core/qwebengineglobalsettings/cert/RootCA.pem b/tests/auto/core/qwebengineglobalsettings/cert/RootCA.pem
new file mode 100644
index 000000000..16be384bb
--- /dev/null
+++ b/tests/auto/core/qwebengineglobalsettings/cert/RootCA.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDTzCCAjegAwIBAgIUNYpmREIW67Fh7WNzCwPL6nnSgRMwDQYJKoZIhvcNAQEL
+BQAwNzELMAkGA1UEBhMCREUxKDAmBgNVBAMMH1dlYkVuZ2luZUdsb2JhbFNldHRp
+bmdzLVRlc3QtQ0EwHhcNMjMwMzI5MTYwMzMzWhcNMjYwMTE2MTYwMzMzWjA3MQsw
+CQYDVQQGEwJERTEoMCYGA1UEAwwfV2ViRW5naW5lR2xvYmFsU2V0dGluZ3MtVGVz
+dC1DQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJhZ9DwcdbVBzMyY
+/nEqt5KUi74LFwEnS0G2ne8IYco9+Pbkpb8wV5u6n43IsQ2c3u8D/KVtu1Vy3tf2
+G3aKOwhFzaj7GWLE9FweZyMoL6ASOtWEa55myT5zAysVQtHAkePu0smAPP0gVq3E
+vjSTwV1W1mVXv4wMwffR8AvNGhKrJIa3L2/uYKGbzEmaCk2kt0vIqfrx8095RlXC
+lUcwTMJ6/d/e/DMDtqQ1ypUuz5QYQybIVKwuqkhojT2DXbitv0rE4HLQub8CxOZ+
+9GcQjeAt8Tzrlp1UP6c9OtlsMoo37gJYzb/XDE6OPnk42chQXDxGQjtVRs+60kcT
+Dx/YHG0CAwEAAaNTMFEwHQYDVR0OBBYEFP1FK1U9CUHQEp7coaab7IdR18zDMB8G
+A1UdIwQYMBaAFP1FK1U9CUHQEp7coaab7IdR18zDMA8GA1UdEwEB/wQFMAMBAf8w
+DQYJKoZIhvcNAQELBQADggEBAGTlcxmRsuwBeRW0CsjX/qbdcB0OWkIC857Jn8RU
+6yGa7P9i6EQb1O/DEF+Z7dkASx5zfN6LPIrph6J56/mmcNBeqArovWJwxQUTNO9i
+1kOU3xoH5n/ya+gdBr3reA90bAMKWXwa6uI3smPJKy+2hOkdDaSBa5KECYWhniH0
+yRxL7YdhQhuCc7Ijf+S6WzeHRwdLkdiV8c2vAGWdunDFuGT2iYVOZ1qbp6O/tmjv
+TxWAnvP4+0ykFIlMor0vYWD8xbnq28263VmNh7mrFwkBYnHJiY/nTDwxaL+g6Uji
+9n6+VuxUDgfQWX1YRHC4a89zI/Zvnn2z/92To7zRmNd73RQ=
+-----END CERTIFICATE-----
diff --git a/tests/auto/core/qwebengineglobalsettings/cert/localhost.crt b/tests/auto/core/qwebengineglobalsettings/cert/localhost.crt
new file mode 100644
index 000000000..c063bf268
--- /dev/null
+++ b/tests/auto/core/qwebengineglobalsettings/cert/localhost.crt
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDtDCCApygAwIBAgIURotPFTfDJxwaqhZsr0IpAahl2EMwDQYJKoZIhvcNAQEL
+BQAwNzELMAkGA1UEBhMCREUxKDAmBgNVBAMMH1dlYkVuZ2luZUdsb2JhbFNldHRp
+bmdzLVRlc3QtQ0EwHhcNMjMwMzI5MTYwNDI1WhcNMjYwMTE2MTYwNDI1WjB0MQsw
+CQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xKTAn
+BgNVBAoMIFFXZWJFbmdpbmVHbG9iYWxTZXR0aW5ncy1FeGFtcGxlMRgwFgYDVQQD
+DA9sb2NhbGhvc3QubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCY9FX/8YetuJKSFSoYPxMKPvjt2zJ5g13DywqZtbDzLAIASCxn4iad3qgxaoWB
+uI0g4ykzrhUa98YHU8fDH4T4Vwhwu72SRYW4+MgT9ohc1oCKBX05b+BWSuTSeuHy
+leqdL78bj3bu5TtFs2dJt2t8eA6SNR9lDa5g4v7oA4xVp93gMo2YqZwaLONmxIKY
+cI4lcETnHHsvc6+dB2UqWHJEN75UkdC/XnDLM/VbL3/4zxU+9x34nvvfSJwCHVnE
+u+zYwrZXkbiDVDovT855phVC/K5skVgBL2miz3eygljuw1tIwnmVix/e/xHZyqMg
+Lje/LZN53v5G61Wut6bbdkeVAgMBAAGjezB5MB8GA1UdIwQYMBaAFP1FK1U9CUHQ
+Ep7coaab7IdR18zDMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMB8GA1UdEQQYMBaC
+CWxvY2FsaG9zdIIJMTI3LjAuMC4xMB0GA1UdDgQWBBQv20ImViFtvMm5gHqTeML6
+pi5BtTANBgkqhkiG9w0BAQsFAAOCAQEAGsL7eOms30+IPdKQZ2pjtX22GfM6EiUs
+xsQfsX/Q3bus30B2m9GJ6AVIwVUJimOGiMauDCLjDeXWCMZpihzodExhC0D/X1B+
+FsCLagcjlgfWwekKEo8sUWUZp0DNCyacPtTPxqoS2RA7foEzQhRLViLSvf+UXU8g
+jZAwWGB/5V849zcbbNBcWKzRsPvNOqeENWEn1ByGcsWhas2V0KzRcUODuA9UHv1x
++eDlLZYsWV9c1MuL8a1VDEluIR19eR/Gl9axjPZY2oiPvlp2b7I4z1bY+wV2i6vh
+NeDCAxAxJ42tXeb86vtnVXfSDbzedDbLv0l2kEhcywVN3MwhsEpmpg==
+-----END CERTIFICATE-----
diff --git a/tests/auto/core/qwebengineglobalsettings/cert/localhost.key b/tests/auto/core/qwebengineglobalsettings/cert/localhost.key
new file mode 100644
index 000000000..49502ab9a
--- /dev/null
+++ b/tests/auto/core/qwebengineglobalsettings/cert/localhost.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCY9FX/8YetuJKS
+FSoYPxMKPvjt2zJ5g13DywqZtbDzLAIASCxn4iad3qgxaoWBuI0g4ykzrhUa98YH
+U8fDH4T4Vwhwu72SRYW4+MgT9ohc1oCKBX05b+BWSuTSeuHyleqdL78bj3bu5TtF
+s2dJt2t8eA6SNR9lDa5g4v7oA4xVp93gMo2YqZwaLONmxIKYcI4lcETnHHsvc6+d
+B2UqWHJEN75UkdC/XnDLM/VbL3/4zxU+9x34nvvfSJwCHVnEu+zYwrZXkbiDVDov
+T855phVC/K5skVgBL2miz3eygljuw1tIwnmVix/e/xHZyqMgLje/LZN53v5G61Wu
+t6bbdkeVAgMBAAECggEAF7ADX5NivUsv29LORZoDE1ukRoXjX8Ex9MANoLdsM4S1
+vKBwzBfQfjN83cZO7cOMi7LSby//EcGcmAboEXZgq+siof7ZQX1l07snlTvha2tG
+1dk6xvnmBscrf9NLCbwg7P33fUevFhlHICjEDr0KtuiK7Sav+YDwaA3Ph1QBWERd
+GO3sVlnuGsDpf/0GijlwqEGuDKUePEEANOhXcByh693VmvlKVf9SbrimYeunKW1O
+FvvcAiBMzqurhZotb9/juiq+fIMID29OULCCxlZZSySRYw0REpnAAxgWvaSZqyWd
+tGosSKEgc4SptPTmC8DzRDDfN/zqvXmkmYnN9o4qMwKBgQC3tVYdEPn3fHX973Df
+Ukp55cRpZFuNxjOiV3Qo1aTAKqpbmJ4/x8tUL7rhsmJXSxlW7xdNQ/WIHM1PJlZx
+UAIr5eBq1MUVd5OENP8PuVIdAIumHXICB5FioJR/WnXRXLJEbGxSRr5gwaTw3sXd
+ObPRQEUOrJtK+W0aeBKePRtIiwKBgQDVJN7A26vy8PMcE4TcDp75vAY/qasZl6ES
+oksaHf3c4/jsnr70wRoOXi3fPo/DpWmVFylttMSEnzh3nfnOkDXZD3mQx3sdVw8l
+12++t59733hllJZlwqk5OAc990kE1X44UW/gPA+5Vb9kpo6ahpFtqwhDmqa4RjtZ
+0R/1H/KUXwKBgGjR7Qq0rwwJVgHIZ2zlNV2MPp+sBZlFaBzPLZZHILQNJBsTX+gg
+heHJQiaZdAc+8Hxr+624gxZg6LyqsVQCRNrrVTtfn/x5uBANdSNxqGqn7wafcne5
+/bh6y4BHC0akT4s/Gidv+hyXIRfW5Ksvy2wv8bdHwWvsGdaqgGUNlM21AoGAe9Vc
+BbibAh6zYBCHFEL6YiW3i61L1yadUnIwKBBcucVJjk/8qb63ILne9OEoLYcg/Jnk
+W/S2aEcJS5Xg2P44CtBO1KrRAI7gIiA0sB2G7zU6gen+J0kdgDzpGDtflQtktdu6
+oBDFIeyLsjKCj4y3WXwQ5RYo3s8PFHPHmWbiTQkCgYBViqAHaAaPJQZG7Q6/Xqhf
+rFosC0DeZk5PHrEDbpAnuJbySafGZ4LxY5Oq05+aM8BeR+IuGopciBBDWXoh7msO
+N8WItu7WI86lIu7JZRbeju2w59tgj0EA+GyU1udPjTjseP5qpB27X1JEGV6TYBNs
+LMmQSgdGWqwteKsc50UrkA==
+-----END PRIVATE KEY-----
diff --git a/tests/auto/core/qwebengineglobalsettings/tst_qwebengineglobalsettings.cpp b/tests/auto/core/qwebengineglobalsettings/tst_qwebengineglobalsettings.cpp
new file mode 100644
index 000000000..5b6b64778
--- /dev/null
+++ b/tests/auto/core/qwebengineglobalsettings/tst_qwebengineglobalsettings.cpp
@@ -0,0 +1,130 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QtTest>
+#include <widgetutil.h>
+#include <QNetworkAccessManager>
+#include <QNetworkRequest>
+#include <QWebEngineProfile>
+#include <QWebEnginePage>
+#include <QWebEngineGlobalSettings>
+#include <QWebEngineLoadingInfo>
+
+#include "httpsserver.h"
+#include "httpreqrep.h"
+
+class tst_QWebEngineGlobalSettings : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QWebEngineGlobalSettings() { }
+ ~tst_QWebEngineGlobalSettings() { }
+
+public Q_SLOTS:
+ void init() { }
+ void cleanup() { }
+
+private Q_SLOTS:
+ void initTestCase() { }
+ void cleanupTestCase() { }
+ void dnsOverHttps_data();
+ void dnsOverHttps();
+};
+
+Q_LOGGING_CATEGORY(lc, "qt.webengine.tests")
+
+void tst_QWebEngineGlobalSettings::dnsOverHttps_data()
+{
+ QTest::addColumn<QWebEngineGlobalSettings::SecureDnsMode>("dnsMode");
+ QTest::addColumn<QString>("uriTemplate");
+ QTest::addColumn<bool>("isMockDnsServerCalledExpected");
+ QTest::addColumn<bool>("isDnsResolutionSuccessExpected");
+ QTest::addColumn<bool>("isConfigurationSuccessExpected");
+ QTest::newRow("DnsMode::SystemOnly (no DoH server)")
+ << QWebEngineGlobalSettings::SecureDnsMode::SystemOnly << QStringLiteral("") << false
+ << true << true;
+ QTest::newRow("DnsMode::SecureOnly (mock DoH server)")
+ << QWebEngineGlobalSettings::SecureDnsMode::SecureOnly
+ << QStringLiteral("https://127.0.0.1:3000/dns-query{?dns}") << true << false << true;
+ QTest::newRow("DnsMode::SecureOnly (real DoH server)")
+ << QWebEngineGlobalSettings::SecureDnsMode::SecureOnly
+ << QStringLiteral("https://dns.google/dns-query{?dns}") << false << true << true;
+ QTest::newRow("DnsMode::SecureOnly (Empty URI Templates)")
+ << QWebEngineGlobalSettings::SecureDnsMode::SecureOnly << QStringLiteral("") << false
+ << false << false;
+ // Note: In the following test, we can't verify that the DoH server is called first and
+ // afterwards insecure DNS is tried, because for the DoH server to ever be used when the DNS
+ // mode is set to DnsMode::WithFallback, Chromium starts an asynchronous DoH server DnsProbe and
+ // requires that the connection is successful. That is, we'd have to implement a correct
+ // DNS response, which in turn requires that certificate errors aren't ignored and
+ // non-self-signed certificates are used for correct encryption. Instead of implementing
+ // all of that, this test verifies that Chromium tries probing the configured DoH server only.
+ QTest::newRow("DnsMode::SecureWithFallback (mock DoH server)")
+ << QWebEngineGlobalSettings::SecureDnsMode::SecureWithFallback
+ << QStringLiteral("https://127.0.0.1:3000/dns-query{?dns}") << true << true << true;
+ QTest::newRow("DnsMode::SecureWithFallback (Empty URI Templates)")
+ << QWebEngineGlobalSettings::SecureDnsMode::SecureWithFallback << QStringLiteral("")
+ << false << false << false;
+}
+
+void tst_QWebEngineGlobalSettings::dnsOverHttps()
+{
+ const QUrl url = QStringLiteral("https://google.com/");
+ // Verify network access with NAM because the result of loadFinished signal
+ // is used to verify that the DNS resolution was successful.
+ QNetworkAccessManager nam;
+ QSignalSpy namSpy(&nam, &QNetworkAccessManager::finished);
+ QScopedPointer<QNetworkReply> reply(nam.get(QNetworkRequest(url)));
+ if (!namSpy.wait(20000) || reply->error() != QNetworkReply::NoError)
+ QSKIP("Couldn't load page from network, skipping test.");
+
+ QFETCH(QWebEngineGlobalSettings::SecureDnsMode, dnsMode);
+ QFETCH(QString, uriTemplate);
+ QFETCH(bool, isMockDnsServerCalledExpected);
+ QFETCH(bool, isDnsResolutionSuccessExpected);
+ QFETCH(bool, isConfigurationSuccessExpected);
+ bool isMockDnsServerCalled = false;
+ bool isLoadSuccessful = false;
+
+ bool configurationSuccess =
+ QWebEngineGlobalSettings::setDnsMode({ dnsMode, QStringList{ uriTemplate } });
+ QCOMPARE(configurationSuccess, isConfigurationSuccessExpected);
+
+ if (!configurationSuccess) {
+ // In this case, DNS has invalid configuration, so the DNS change transaction is not
+ // triggered and the result of the DNS resolution depends on the current DNS mode, which is
+ // set by the previous run of this function.
+ return;
+ }
+ HttpsServer httpsServer(":/cert/localhost.crt", ":/cert/localhost.key", ":/cert/RootCA.pem",
+ 3000, this);
+ QObject::connect(&httpsServer, &HttpsServer::newRequest, this,
+ [&isMockDnsServerCalled](HttpReqRep *rr) {
+ QVERIFY(rr->requestPath().contains(QByteArrayLiteral("/dns-query?dns=")));
+ isMockDnsServerCalled = true;
+ rr->close();
+ });
+ QVERIFY(httpsServer.start());
+ httpsServer.setExpectError(isMockDnsServerCalledExpected);
+ httpsServer.setVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone);
+
+ QWebEngineProfile profile;
+ QWebEnginePage page(&profile);
+ QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
+
+ connect(&page, &QWebEnginePage::loadFinished, this,
+ [&isLoadSuccessful](bool ok) { isLoadSuccessful = ok; });
+
+ page.load(url);
+ QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000);
+
+ QTRY_COMPARE(isMockDnsServerCalled, isMockDnsServerCalledExpected);
+ QCOMPARE(isLoadSuccessful, isDnsResolutionSuccessExpected);
+ QVERIFY(httpsServer.stop());
+}
+
+static QByteArrayList params = QByteArrayList() << "--ignore-certificate-errors";
+
+W_QTEST_MAIN(tst_QWebEngineGlobalSettings, params)
+#include "tst_qwebengineglobalsettings.moc"
diff --git a/tests/auto/core/qwebengineloadinginfo/CMakeLists.txt b/tests/auto/core/qwebengineloadinginfo/CMakeLists.txt
new file mode 100644
index 000000000..09d9c30f5
--- /dev/null
+++ b/tests/auto/core/qwebengineloadinginfo/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_internal_add_test(tst_qwebengineloadinginfo
+ SOURCES
+ tst_qwebengineloadinginfo.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+ Test::HttpServer
+)
diff --git a/tests/auto/core/qwebengineloadinginfo/tst_qwebengineloadinginfo.cpp b/tests/auto/core/qwebengineloadinginfo/tst_qwebengineloadinginfo.cpp
new file mode 100644
index 000000000..ccae7436b
--- /dev/null
+++ b/tests/auto/core/qwebengineloadinginfo/tst_qwebengineloadinginfo.cpp
@@ -0,0 +1,93 @@
+// Copyright (C) 2023 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 <QtWebEngineCore/qwebengineprofile.h>
+#include <QtWebEngineCore/qwebenginepage.h>
+#include <QtWebEngineCore/qwebengineloadinginfo.h>
+#include <QtWebEngineCore/qwebenginehttprequest.h>
+#include <QtWebEngineCore/qwebenginesettings.h>
+
+#include <httpserver.h>
+#include <httpreqrep.h>
+
+typedef QMultiMap<QByteArray, QByteArray> Map;
+
+class tst_QWebEngineLoadingInfo : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QWebEngineLoadingInfo() { }
+
+public slots:
+ void loadingInfoChanged(QWebEngineLoadingInfo loadingInfo)
+ {
+ const auto responseHeaders = loadingInfo.responseHeaders();
+ QFETCH(Map, expected);
+
+ if (loadingInfo.status() == QWebEngineLoadingInfo::LoadSucceededStatus
+ || loadingInfo.status() == QWebEngineLoadingInfo::LoadFailedStatus) {
+ if (!expected.empty())
+ QCOMPARE(responseHeaders, expected);
+ } else {
+ QVERIFY(responseHeaders.size() == 0);
+ }
+ }
+
+private Q_SLOTS:
+ void responseHeaders_data()
+ {
+ QTest::addColumn<int>("responseCode");
+ QTest::addColumn<Map>("input");
+ QTest::addColumn<Map>("expected");
+
+ const Map empty;
+ const Map input {
+ std::make_pair("header1", "value1"),
+ std::make_pair("header2", "value2")
+ };
+ const Map expected {
+ std::make_pair("header1", "value1"),
+ std::make_pair("header2", "value2"),
+ std::make_pair("Connection", "close")
+ };
+
+
+ QTest::newRow("with headers HTTP 200") << 200 << input << expected;
+ QTest::newRow("with headers HTTP 500") << 500 << input << expected;
+ QTest::newRow("without headers HTTP 200") << 200 << empty << empty;
+ QTest::newRow("without headers HTTP 500") << 500 << empty << empty;
+ }
+
+ void responseHeaders()
+ {
+ HttpServer httpServer;
+
+ QFETCH(Map, input);
+ QFETCH(int, responseCode);
+ QObject::connect(&httpServer, &HttpServer::newRequest, this, [&](HttpReqRep *rr) {
+ for (auto it = input.cbegin(); it != input.cend(); ++it)
+ rr->setResponseHeader(it.key(), it.value());
+
+ rr->sendResponse(responseCode);
+ });
+ QVERIFY(httpServer.start());
+
+ QWebEngineProfile profile;
+ QWebEnginePage page(&profile);
+ page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+ QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
+ QObject::connect(&page, &QWebEnginePage::loadingChanged, this, &tst_QWebEngineLoadingInfo::loadingInfoChanged);
+
+
+ QWebEngineHttpRequest request(httpServer.url("/somepage.html"));
+ page.load(request);
+
+ QTRY_VERIFY(spy.count() > 0);
+ QVERIFY(httpServer.stop());
+ }
+};
+
+QTEST_MAIN(tst_QWebEngineLoadingInfo)
+#include "tst_qwebengineloadinginfo.moc"
diff --git a/tests/auto/core/qwebenginesettings/CMakeLists.txt b/tests/auto/core/qwebenginesettings/CMakeLists.txt
index 44e8f5252..756b99bbb 100644
--- a/tests/auto/core/qwebenginesettings/CMakeLists.txt
+++ b/tests/auto/core/qwebenginesettings/CMakeLists.txt
@@ -8,5 +8,6 @@ qt_internal_add_test(tst_qwebenginesettings
tst_qwebenginesettings.cpp
LIBRARIES
Qt::WebEngineCore
+ Qt::WebEngineWidgets
Test::Util
)
diff --git a/tests/auto/core/qwebenginesettings/tst_qwebenginesettings.cpp b/tests/auto/core/qwebenginesettings/tst_qwebenginesettings.cpp
index e0bb604e2..e856dd094 100644
--- a/tests/auto/core/qwebenginesettings/tst_qwebenginesettings.cpp
+++ b/tests/auto/core/qwebenginesettings/tst_qwebenginesettings.cpp
@@ -27,6 +27,7 @@
#include <QtGui/qclipboard.h>
#include <QtGui/qguiapplication.h>
+#include <QtWebEngineWidgets/qwebengineview.h>
class tst_QWebEngineSettings: public QObject {
Q_OBJECT
@@ -38,6 +39,10 @@ private Q_SLOTS:
void javascriptClipboard_data();
void javascriptClipboard();
void setInAcceptNavigationRequest();
+ void disableReadingFromCanvas_data();
+ void disableReadingFromCanvas();
+ void forceDarkMode();
+ void forceDarkModeMultiView();
};
void tst_QWebEngineSettings::resetAttributes()
@@ -193,6 +198,110 @@ void tst_QWebEngineSettings::setInAcceptNavigationRequest()
QCOMPARE(toPlainTextSync(&page), QStringLiteral("PASS"));
}
+void tst_QWebEngineSettings::disableReadingFromCanvas_data()
+{
+ QTest::addColumn<bool>("disableReadingFromCanvas");
+ QTest::addColumn<bool>("result");
+ QTest::newRow("disabled") << false << true;
+ QTest::newRow("enabled") << true << false;
+}
+
+void tst_QWebEngineSettings::disableReadingFromCanvas()
+{
+ QFETCH(bool, disableReadingFromCanvas);
+ QFETCH(bool, result);
+
+ QWebEnginePage page;
+ QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool)));
+ page.settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
+ page.settings()->setAttribute(QWebEngineSettings::ReadingFromCanvasEnabled,
+ !disableReadingFromCanvas);
+ page.setHtml("<html><body>"
+ "<canvas id='myCanvas' width='200' height='40' style='border:1px solid "
+ "#000000;'></canvas>"
+ "</body></html>");
+ QVERIFY(loadFinishedSpy.wait());
+ QCOMPARE(page.settings()->testAttribute(QWebEngineSettings::ReadingFromCanvasEnabled),
+ !disableReadingFromCanvas);
+
+ const QString jsCode("(function(){"
+ " var canvas = document.getElementById(\"myCanvas\");"
+ " var ctx = canvas.getContext(\"2d\");"
+ " ctx.fillStyle = \"rgb(255,0,255)\";"
+ " ctx.fillRect(0, 0, 200, 40);"
+ " try {"
+ " src = canvas.toDataURL();"
+ " }"
+ " catch(err) {"
+ " src = \"\";"
+ " }"
+ " return src.length ? true : false;"
+ "})();");
+ QCOMPARE(evaluateJavaScriptSync(&page, jsCode).toBool(), result);
+}
+
+void tst_QWebEngineSettings::forceDarkMode()
+{
+ QWebEnginePage page;
+ QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool)));
+ page.settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
+
+ // based on: https://developer.chrome.com/blog/auto-dark-theme/#detecting-auto-dark-theme
+ page.setHtml("<html><body>"
+ "<div id=\"detection\", style=\"display: none; background-color: canvas; color-scheme: light\"</div>"
+ "</body></html>");
+
+ const QString isAutoDark("(() => {"
+ " const detectionDiv = document.querySelector('#detection');"
+ " return getComputedStyle(detectionDiv).backgroundColor != 'rgb(255, 255, 255)';"
+ "})()");
+
+ QVERIFY(loadFinishedSpy.wait());
+ QTRY_COMPARE(evaluateJavaScriptSync(&page, isAutoDark).toBool(), false);
+ page.settings()->setAttribute(QWebEngineSettings::ForceDarkMode, true);
+ QTRY_COMPARE(evaluateJavaScriptSync(&page, isAutoDark).toBool(), true);
+}
+
+void tst_QWebEngineSettings::forceDarkModeMultiView()
+{
+ QWebEngineView view1;
+ QWebEngineView view2;
+ QWebEnginePage *page1 = view1.page();
+ QWebEnginePage *page2 = view2.page();
+ page1->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
+ page2->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
+ view1.resize(300,300);
+ view2.resize(300,300);
+ view1.show();
+ view2.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&view1));
+ QVERIFY(QTest::qWaitForWindowExposed(&view2));
+
+ QSignalSpy loadFinishedSpy(page1, SIGNAL(loadFinished(bool)));
+ QSignalSpy loadFinishedSpy2(page2, SIGNAL(loadFinished(bool)));
+ QString html("<html><body>"
+ "<div id=\"detection\", style=\"display: none; background-color: canvas; color-scheme: light\"</div>"
+ "</body></html>");
+
+ const QString isAutoDark("(() => {"
+ " const detectionDiv = document.querySelector('#detection');"
+ " return getComputedStyle(detectionDiv).backgroundColor != 'rgb(255, 255, 255)';"
+ "})()");
+
+ view1.setHtml(html);
+ QVERIFY(loadFinishedSpy.wait());
+ view2.setHtml(html);
+ QVERIFY(loadFinishedSpy2.wait());
+
+ // both views has light color-scheme
+ QTRY_COMPARE(evaluateJavaScriptSync(page1, isAutoDark).toBool(), false);
+ QTRY_COMPARE(evaluateJavaScriptSync(page2, isAutoDark).toBool(), false);
+ view1.settings()->setAttribute(QWebEngineSettings::ForceDarkMode, true);
+ // dark mode should apply only for view1
+ QTRY_COMPARE(evaluateJavaScriptSync(page1, isAutoDark).toBool(), true);
+ QTRY_COMPARE(evaluateJavaScriptSync(page2, isAutoDark).toBool(), false);
+}
+
QTEST_MAIN(tst_QWebEngineSettings)
#include "tst_qwebenginesettings.moc"
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/CMakeLists.txt b/tests/auto/core/qwebengineurlrequestinterceptor/CMakeLists.txt
index 60add4567..c12c0c45c 100644
--- a/tests/auto/core/qwebengineurlrequestinterceptor/CMakeLists.txt
+++ b/tests/auto/core/qwebengineurlrequestinterceptor/CMakeLists.txt
@@ -9,12 +9,16 @@ qt_internal_add_test(tst_qwebengineurlrequestinterceptor
tst_qwebengineurlrequestinterceptor.cpp
LIBRARIES
Qt::WebEngineCore
+ Qt::WebEngineCorePrivate
+ Qt::CorePrivate
Test::HttpServer
Test::Util
)
set(tst_qwebengineurlrequestinterceptor_resource_files
"resources/content.html"
+ "resources/content2.html"
+ "resources/content3.html"
"resources/favicon.html"
"resources/firstparty.html"
"resources/fontawesome.woff"
@@ -34,6 +38,7 @@ set(tst_qwebengineurlrequestinterceptor_resource_files
"resources/style.css"
"resources/sw.html"
"resources/sw.js"
+ "resources/postBodyFile.txt"
)
qt_internal_add_resource(tst_qwebengineurlrequestinterceptor "tst_qwebengineurlrequestinterceptor"
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/resources/content3.html b/tests/auto/core/qwebengineurlrequestinterceptor/resources/content3.html
new file mode 100644
index 000000000..84bf55036
--- /dev/null
+++ b/tests/auto/core/qwebengineurlrequestinterceptor/resources/content3.html
@@ -0,0 +1,6 @@
+<html>
+<head><link rel="icon" href="data:,"></head>
+<body>
+<a>Simple test page without favicon (meaning no separate request from http server)</a>
+</body>
+</html>
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/resources/postBodyFile.txt b/tests/auto/core/qwebengineurlrequestinterceptor/resources/postBodyFile.txt
new file mode 100644
index 000000000..7729c4e0a
--- /dev/null
+++ b/tests/auto/core/qwebengineurlrequestinterceptor/resources/postBodyFile.txt
@@ -0,0 +1,3 @@
+{
+"test": "1234"
+}
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp b/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp
index 79de46d40..7cea14c0c 100644
--- a/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp
+++ b/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp
@@ -1,9 +1,12 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include <util.h>
#include <QtTest/QtTest>
#include <QtWebEngineCore/qwebengineurlrequestinfo.h>
+#include <QtWebEngineCore/private/qwebengineurlrequestinfo_p.h>
#include <QtWebEngineCore/qwebengineurlrequestinterceptor.h>
#include <QtWebEngineCore/qwebenginesettings.h>
#include <QtWebEngineCore/qwebengineprofile.h>
@@ -47,6 +50,11 @@ private Q_SLOTS:
void replaceInterceptor_data();
void replaceInterceptor();
void replaceOnIntercept();
+ void multipleRedirects();
+ void postWithBody_data();
+ void postWithBody();
+ void profilePreventsPageInterception_data();
+ void profilePreventsPageInterception();
};
tst_QWebEngineUrlRequestInterceptor::tst_QWebEngineUrlRequestInterceptor()
@@ -133,7 +141,7 @@ public:
// MEMO avoid unintentionally changing request when it is not needed for test logic
// since api behavior depends on 'changed' state of the info object
- Q_ASSERT(info.changed() == (block || redirect || !headers.empty()));
+ Q_ASSERT(info.changed() == (block || redirect));
}
bool shouldSkipRequest(const RequestInfo &requestInfo)
@@ -181,6 +189,29 @@ public:
}
};
+class TestMultipleRedirectsInterceptor : public QWebEngineUrlRequestInterceptor {
+public:
+ QList<RequestInfo> requestInfos;
+ QMap<QUrl, QUrl> redirectPairs;
+ int redirectCount = 0;
+ void interceptRequest(QWebEngineUrlRequestInfo &info) override
+ {
+ QVERIFY(QThread::currentThread() == QCoreApplication::instance()->thread());
+ qCDebug(lc) << this << "Type:" << info.resourceType() << info.requestMethod() << "Navigation:" << info.navigationType()
+ << info.requestUrl() << "Initiator:" << info.initiator();
+ auto redirectUrl = redirectPairs.constFind(info.requestUrl());
+ if (redirectUrl != redirectPairs.constEnd()) {
+ info.redirect(redirectUrl.value());
+ requestInfos.append(info);
+ redirectCount++;
+ }
+ }
+
+ TestMultipleRedirectsInterceptor()
+ {
+ }
+};
+
class ConsolePage : public QWebEnginePage {
Q_OBJECT
public:
@@ -888,5 +919,204 @@ void tst_QWebEngineUrlRequestInterceptor::replaceOnIntercept()
QCOMPARE(profileInterceptor.requestInfos.size(), pageInterceptor2.requestInfos.size());
}
+void tst_QWebEngineUrlRequestInterceptor::multipleRedirects()
+{
+ HttpServer server;
+ server.setResourceDirs({ ":/resources" });
+ QVERIFY(server.start());
+
+ TestMultipleRedirectsInterceptor multiInterceptor;
+ multiInterceptor.redirectPairs.insert(QUrl(server.url("/content.html")), QUrl(server.url("/content2.html")));
+ multiInterceptor.redirectPairs.insert(QUrl(server.url("/content2.html")), QUrl(server.url("/content3.html")));
+
+ QWebEngineProfile profile;
+ profile.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+ profile.setUrlRequestInterceptor(&multiInterceptor);
+ QWebEnginePage page(&profile);
+ QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
+
+ page.setUrl(server.url("/content.html"));
+
+ QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 20000);
+ QTRY_COMPARE(multiInterceptor.redirectCount, 2);
+ QTRY_COMPARE(multiInterceptor.requestInfos.size(), 2);
+}
+
+class TestPostRequestInterceptor : public QWebEngineUrlRequestInterceptor
+{
+public:
+ TestPostRequestInterceptor(QString expected, bool isAppendFile, QObject *parent = nullptr)
+ : QWebEngineUrlRequestInterceptor(parent)
+ , m_expected(expected)
+ , m_isAppendFile(isAppendFile)
+ {};
+
+ void interceptRequest(QWebEngineUrlRequestInfo &info) override
+ {
+ info.block(true);
+ isCalled = true;
+
+ QIODevice *requestBodyDevice = info.requestBody();
+
+ if (m_isAppendFile) {
+ info.d_ptr->appendFileToResourceRequestBodyForTest(":/resources/postBodyFile.txt");
+ }
+
+ requestBodyDevice->open(QIODevice::ReadOnly);
+
+ const QString webKitBoundary = requestBodyDevice->read(40);
+ QVERIFY(webKitBoundary.contains("------WebKitFormBoundary"));
+
+ const QString fullBodyWithoutBoundaries = (webKitBoundary + requestBodyDevice->readAll())
+ .replace(webKitBoundary, "")
+ .replace("\r", "")
+ .replace("\n", "")
+ .replace(" ", "");
+
+ QCOMPARE(fullBodyWithoutBoundaries, m_expected);
+
+ requestBodyDevice->close();
+ }
+
+ bool isCalled = false;
+ QString m_expected;
+ bool m_isAppendFile;
+};
+
+void tst_QWebEngineUrlRequestInterceptor::postWithBody_data()
+{
+ QTest::addColumn<QString>("input");
+ QTest::addColumn<QString>("output");
+ QTest::addColumn<bool>("isAppendFile");
+ QTest::addRow("FormData append (DataElementByte)")
+ << "fd.append('userId', 1);"
+ "fd.append('title',' Test123');"
+ "fd.append('completed', false);"
+ << "Content-Disposition:form-data;name=\"userId"
+ "\"1Content-Disposition:form-data"
+ ";name=\"title\"Test123Content-Di"
+ "sposition:form-data;name=\"completed\"f"
+ "alse--"
+ << false;
+ QTest::addRow("FormData blob (DataElementPipe)")
+ << "const blob1 = new Blob(['blob1thisisablob'],"
+ "{type: 'text/plain'});"
+ "fd.append('blob1', blob1);"
+ << "Content-Disposition:form-data;name=\"blob1"
+ "\";filename=\"blob\"Content-Type:text/plai"
+ "nblob1thisisablob--"
+ << false;
+ QTest::addRow("Append file (DataElementFile)") << ""
+ << "--{\"test\":\"1234\"}\"1234\"}" << true;
+ QTest::addRow("All combined") << "fd.append('userId', 1);"
+ "fd.append('title', 'Test123');"
+ "fd.append('completed', false);"
+ "const blob1 = new Blob(['blob1thisisablob'],"
+ "{type: 'text/plain'});"
+ "const blob2 = new Blob(['blob2thisisanotherblob'],"
+ "{type: 'text/plain'});"
+ "fd.append('blob1', blob1);"
+ "fd.append('userId', 2);"
+ "fd.append('title', 'Test456');"
+ "fd.append('completed', true);"
+ "fd.append('blob2', blob2);"
+ << "Content-Disposition:form-data;name=\"userId\""
+ "1Content-Disposition:form-data;na"
+ "me=\"title\"Test123Content-Disposit"
+ "ion:form-data;name=\"completed\"false"
+ "Content-Disposition:form-data;name=\"blob1\";"
+ "filename=\"blob\"Content-Type:text/plain"
+ "blob1thisisablobContent-Disposition:form-"
+ "data;name=\"userId\"2Content-Dispos"
+ "ition:form-data;name=\"title\"Test456"
+ "Content-Disposition:form-data;name=\"complete"
+ "d\"trueContent-Disposition:form-da"
+ "ta;name=\"blob2\";filename=\"blob\"Content-Ty"
+ "pe:text/plainblob2thisisanotherblob--"
+ "{\"test\":\"1234\"}\"1234\"}"
+ << true;
+}
+
+void tst_QWebEngineUrlRequestInterceptor::postWithBody()
+{
+ QFETCH(QString, input);
+ QFETCH(QString, output);
+ QFETCH(bool, isAppendFile);
+
+ QString script;
+ script.append("const fd = new FormData();");
+ script.append(input);
+ script.append("fetch('http://127.0.0.1', {method: 'POST',body: fd});");
+
+ QWebEngineProfile profile;
+ profile.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+ TestPostRequestInterceptor interceptor(output, isAppendFile);
+ profile.setUrlRequestInterceptor(&interceptor);
+ QWebEnginePage page(&profile);
+ bool ok = false;
+
+ page.runJavaScript(script, [&ok](const QVariant) { ok = true; });
+
+ QTRY_VERIFY(ok);
+ QVERIFY(interceptor.isCalled);
+}
+
+class PageOrProfileInterceptor : public QWebEngineUrlRequestInterceptor
+{
+public:
+ PageOrProfileInterceptor(const QString &profileAction)
+ : profileAction(profileAction)
+ {
+ }
+
+ void interceptRequest(QWebEngineUrlRequestInfo &info) override
+ {
+ if (profileAction == "block")
+ info.block(true);
+ else if (profileAction == "redirect")
+ info.redirect(QUrl("data:text/html,<p>redirected"));
+ else if (profileAction == "add header")
+ info.setHttpHeader("Custom-Header", "Value");
+ else
+ QVERIFY(info.httpHeaders().contains("Custom-Header"));
+ ran = true;
+ }
+
+ QString profileAction;
+ bool ran = false;
+};
+
+void tst_QWebEngineUrlRequestInterceptor::profilePreventsPageInterception_data()
+{
+ QTest::addColumn<QString>("profileAction");
+ QTest::addColumn<bool>("interceptInProfile");
+ QTest::addColumn<bool>("interceptInPage");
+ QTest::newRow("block") << "block" << true << false;
+ QTest::newRow("redirect") << "redirect" << true << false;
+ QTest::newRow("add header") << "add header" << true << true;
+}
+
+void tst_QWebEngineUrlRequestInterceptor::profilePreventsPageInterception()
+{
+ QFETCH(QString, profileAction);
+ QFETCH(bool, interceptInProfile);
+ QFETCH(bool, interceptInPage);
+
+ QWebEngineProfile profile;
+ PageOrProfileInterceptor profileInterceptor(profileAction);
+ profile.setUrlRequestInterceptor(&profileInterceptor);
+ profile.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+
+ QWebEnginePage page(&profile);
+ PageOrProfileInterceptor pageInterceptor("");
+ page.setUrlRequestInterceptor(&pageInterceptor);
+ QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
+
+ page.load(QUrl("qrc:///resources/index.html"));
+ QTRY_COMPARE(loadSpy.size(), 1);
+ QCOMPARE(profileInterceptor.ran, interceptInProfile);
+ QCOMPARE(pageInterceptor.ran, interceptInPage);
+}
+
QTEST_MAIN(tst_QWebEngineUrlRequestInterceptor)
#include "tst_qwebengineurlrequestinterceptor.moc"
diff --git a/tests/auto/core/qwebengineurlrequestjob/CMakeLists.txt b/tests/auto/core/qwebengineurlrequestjob/CMakeLists.txt
new file mode 100644
index 000000000..02b668313
--- /dev/null
+++ b/tests/auto/core/qwebengineurlrequestjob/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_internal_add_test(tst_qwebengineurlrequestjob
+ SOURCES
+ tst_qwebengineurlrequestjob.cpp
+ LIBRARIES
+ Qt::WebEngineCore
+)
+
+# Resources:
+set(tst_qwebengineurlrequestjob_resource_files
+ "additionalResponseHeadersScript.html"
+ "requestBodyScript.html"
+)
+
+qt_add_resources(tst_qwebengineurlrequestjob "tst_qwebengineurlrequestjob"
+ PREFIX
+ "/"
+ FILES
+ ${tst_qwebengineurlrequestjob_resource_files}
+)
diff --git a/tests/auto/core/qwebengineurlrequestjob/additionalResponseHeadersScript.html b/tests/auto/core/qwebengineurlrequestjob/additionalResponseHeadersScript.html
new file mode 100644
index 000000000..a1b8a92d3
--- /dev/null
+++ b/tests/auto/core/qwebengineurlrequestjob/additionalResponseHeadersScript.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+<body>
+ <script type="text/javascript">
+ var request = new XMLHttpRequest();
+ request.open('GET', 'additionalresponseheadershandler:about', /* async = */ false);
+ request.send();
+ var headers = request.getAllResponseHeaders();
+ console.log("TST_ADDITIONALRESPONSEHEADERS;" + headers);
+ </script>
+</body>
+</html>
diff --git a/tests/auto/core/qwebengineurlrequestjob/requestBodyScript.html b/tests/auto/core/qwebengineurlrequestjob/requestBodyScript.html
new file mode 100644
index 000000000..95b7ff1bf
--- /dev/null
+++ b/tests/auto/core/qwebengineurlrequestjob/requestBodyScript.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+<body>
+ <script type="text/javascript">
+ var request = new XMLHttpRequest();
+ request.open('POST', 'requestbodyhandler:about', /* async = */ false);
+ request.setRequestHeader('Content-Type', 'text/plain');
+ request.send('reading request body successful');
+ console.log("TST_REQUESTBODY;" + request.response);
+ </script>
+</body>
+</html>
diff --git a/tests/auto/core/qwebengineurlrequestjob/tst_qwebengineurlrequestjob.cpp b/tests/auto/core/qwebengineurlrequestjob/tst_qwebengineurlrequestjob.cpp
new file mode 100644
index 000000000..d48da2c44
--- /dev/null
+++ b/tests/auto/core/qwebengineurlrequestjob/tst_qwebengineurlrequestjob.cpp
@@ -0,0 +1,187 @@
+// Copyright (C) 2023 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 <QtWebEngineCore/qwebengineurlschemehandler.h>
+#include <QtWebEngineCore/qwebengineurlscheme.h>
+#include <QtWebEngineCore/qwebengineurlrequestjob.h>
+#include <QtWebEngineCore/qwebengineprofile.h>
+#include <QtWebEngineCore/qwebenginepage.h>
+
+class CustomPage : public QWebEnginePage
+{
+ Q_OBJECT
+
+public:
+ CustomPage(QWebEngineProfile *profile, QString compareStringPrefix, QString compareStringSuffix,
+ QObject *parent = nullptr)
+ : QWebEnginePage(profile, parent)
+ , m_compareStringPrefix(compareStringPrefix)
+ , m_compareStringSuffix(compareStringSuffix)
+ , m_comparedMessageCount(0)
+ {
+ }
+
+ int comparedMessageCount() const { return m_comparedMessageCount; }
+
+signals:
+ void receivedMessage();
+
+protected:
+ void javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel level,
+ const QString &message, int lineNumber,
+ const QString &sourceID) override
+ {
+ Q_UNUSED(level);
+ Q_UNUSED(lineNumber);
+ Q_UNUSED(sourceID);
+
+ auto splitMessage = message.split(";");
+ if (splitMessage[0] == m_compareStringPrefix) {
+ QCOMPARE(splitMessage[1], m_compareStringSuffix);
+ m_comparedMessageCount++;
+ emit receivedMessage();
+ }
+ }
+
+private:
+ QString m_compareStringPrefix;
+ QString m_compareStringSuffix;
+ int m_comparedMessageCount;
+};
+
+class AdditionalResponseHeadersHandler : public QWebEngineUrlSchemeHandler
+{
+ Q_OBJECT
+public:
+ AdditionalResponseHeadersHandler(bool addAdditionalResponseHeaders, QObject *parent = nullptr)
+ : QWebEngineUrlSchemeHandler(parent)
+ , m_addAdditionalResponseHeaders(addAdditionalResponseHeaders)
+ {
+ }
+
+ void requestStarted(QWebEngineUrlRequestJob *job) override
+ {
+ QCOMPARE(job->requestUrl(), QUrl(schemeName + ":about"));
+
+ QMultiMap<QByteArray, QByteArray> additionalResponseHeaders;
+ if (m_addAdditionalResponseHeaders) {
+ additionalResponseHeaders.insert(QByteArray::fromStdString("test1"),
+ QByteArray::fromStdString("test1VALUE"));
+ additionalResponseHeaders.insert(QByteArray::fromStdString("test2"),
+ QByteArray::fromStdString("test2VALUE"));
+ }
+ job->setAdditionalResponseHeaders(additionalResponseHeaders);
+
+ QFile *file = new QFile(QStringLiteral(":additionalResponseHeadersScript.html"), job);
+ file->open(QIODevice::ReadOnly);
+
+ job->reply(QByteArrayLiteral("text/html"), file);
+ }
+
+ static void registerUrlScheme()
+ {
+ QWebEngineUrlScheme webUiScheme(schemeName);
+ webUiScheme.setFlags(QWebEngineUrlScheme::CorsEnabled);
+ QWebEngineUrlScheme::registerScheme(webUiScheme);
+ }
+
+ const static inline QByteArray schemeName =
+ QByteArrayLiteral("additionalresponseheadershandler");
+
+private:
+ bool m_addAdditionalResponseHeaders;
+};
+
+class RequestBodyHandler : public QWebEngineUrlSchemeHandler
+{
+ Q_OBJECT
+public:
+ void requestStarted(QWebEngineUrlRequestJob *job) override
+ {
+ QCOMPARE(job->requestUrl(), QUrl(schemeName + ":about"));
+ QCOMPARE(job->requestMethod(), QByteArrayLiteral("POST"));
+
+ QIODevice *requestBodyDevice = job->requestBody();
+ requestBodyDevice->open(QIODevice::ReadOnly);
+ QByteArray requestBody = requestBodyDevice->readAll();
+ requestBodyDevice->close();
+
+ QBuffer *buf = new QBuffer(job);
+ buf->open(QBuffer::ReadWrite);
+ buf->write(requestBody);
+ job->reply(QByteArrayLiteral("text/plain"), buf);
+ buf->close();
+ }
+
+ static void registerUrlScheme()
+ {
+ QWebEngineUrlScheme webUiScheme(schemeName);
+ webUiScheme.setFlags(QWebEngineUrlScheme::CorsEnabled
+ | QWebEngineUrlScheme::FetchApiAllowed);
+ QWebEngineUrlScheme::registerScheme(webUiScheme);
+ }
+
+ const static inline QByteArray schemeName = QByteArrayLiteral("requestbodyhandler");
+};
+
+class tst_QWebEngineUrlRequestJob : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QWebEngineUrlRequestJob() { }
+
+private Q_SLOTS:
+ void initTestCase()
+ {
+ AdditionalResponseHeadersHandler::registerUrlScheme();
+ RequestBodyHandler::registerUrlScheme();
+ }
+
+ void withAdditionalResponseHeaders_data()
+ {
+ QTest::addColumn<bool>("withHeaders");
+ QTest::addColumn<QString>("expectedHeaders");
+ QTest::newRow("headers enabled")
+ << true << "content-type: text/html\r\ntest1: test1value\r\ntest2: test2value\r\n";
+ QTest::newRow("headers disabled") << false << "content-type: text/html\r\n";
+ }
+
+ void withAdditionalResponseHeaders()
+ {
+ QFETCH(bool, withHeaders);
+ QFETCH(QString, expectedHeaders);
+
+ QWebEngineProfile profile;
+
+ AdditionalResponseHeadersHandler handler(withHeaders);
+ profile.installUrlSchemeHandler(AdditionalResponseHeadersHandler::schemeName, &handler);
+
+ CustomPage page(&profile, "TST_ADDITIONALRESPONSEHEADERS", expectedHeaders);
+ QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
+
+ page.load(QUrl("qrc:///additionalResponseHeadersScript.html"));
+ QVERIFY(spy.wait());
+ QCOMPARE(page.comparedMessageCount(), 1);
+ }
+
+ void requestBody()
+ {
+ QWebEngineProfile profile;
+
+ RequestBodyHandler handler;
+ profile.installUrlSchemeHandler(RequestBodyHandler::schemeName, &handler);
+
+ const QString expected = "reading request body successful";
+ CustomPage page(&profile, "TST_REQUESTBODY", expected);
+ QSignalSpy spy(&page, SIGNAL(receivedMessage()));
+
+ page.load(QUrl("qrc:///requestBodyScript.html"));
+ QVERIFY(spy.wait());
+ QCOMPARE(page.comparedMessageCount(), 1);
+ }
+};
+
+QTEST_MAIN(tst_QWebEngineUrlRequestJob)
+#include "tst_qwebengineurlrequestjob.moc"
diff --git a/tests/auto/core/webenginedriver/CMakeLists.txt b/tests/auto/core/webenginedriver/CMakeLists.txt
new file mode 100644
index 000000000..c8cf8b3ab
--- /dev/null
+++ b/tests/auto/core/webenginedriver/CMakeLists.txt
@@ -0,0 +1,15 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_webenginedriver LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+add_subdirectory(test)
+add_subdirectory(browser)
+
+add_dependencies(tst_webenginedriver
+ testbrowser
+)
diff --git a/tests/auto/core/webenginedriver/browser/CMakeLists.txt b/tests/auto/core/webenginedriver/browser/CMakeLists.txt
new file mode 100644
index 000000000..25e162e7b
--- /dev/null
+++ b/tests/auto/core/webenginedriver/browser/CMakeLists.txt
@@ -0,0 +1,13 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_internal_add_executable(testbrowser
+ SOURCES
+ main.cpp
+ LIBRARIES
+ Qt::Core
+ Qt::Gui
+ Qt::WebEngineWidgets
+ OUTPUT_DIRECTORY
+ ${CMAKE_CURRENT_BINARY_DIR}/..
+)
diff --git a/tests/auto/core/webenginedriver/browser/main.cpp b/tests/auto/core/webenginedriver/browser/main.cpp
new file mode 100644
index 000000000..4b8f3513f
--- /dev/null
+++ b/tests/auto/core/webenginedriver/browser/main.cpp
@@ -0,0 +1,21 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QtWebEngineCore/qwebenginepage.h>
+#include <QtWebEngineWidgets/qwebengineview.h>
+#include <QtWidgets/qapplication.h>
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+
+ QWebEngineView view;
+ QObject::connect(view.page(), &QWebEnginePage::windowCloseRequested, &app, &QApplication::quit);
+ QObject::connect(&app, &QApplication::aboutToQuit,
+ []() { fprintf(stderr, "Test browser is about to quit.\n"); });
+
+ view.resize(100, 100);
+ view.show();
+
+ return app.exec();
+}
diff --git a/tests/auto/core/webenginedriver/resources/input.html b/tests/auto/core/webenginedriver/resources/input.html
new file mode 100644
index 000000000..c21458350
--- /dev/null
+++ b/tests/auto/core/webenginedriver/resources/input.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ <input type="text" id="text_input">
+</body>
+</html>
diff --git a/tests/auto/core/webenginedriver/test/CMakeLists.txt b/tests/auto/core/webenginedriver/test/CMakeLists.txt
new file mode 100644
index 000000000..041bf955b
--- /dev/null
+++ b/tests/auto/core/webenginedriver/test/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+include(../../../util/util.cmake)
+
+qt_internal_add_test(tst_webenginedriver
+ SOURCES
+ ../tst_webenginedriver.cpp
+ LIBRARIES
+ Qt::Network
+ Qt::WebEngineCore
+ Qt::WebEngineWidgets
+ Test::Util
+ OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/.."
+)
+
+set(tst_webenginedriver_resource_files
+ "../resources/input.html"
+)
+
+qt_internal_add_resource(tst_webenginedriver "tst_webenginedriver"
+ PREFIX
+ "/"
+ FILES
+ ${tst_webenginedriver_resource_files}
+)
diff --git a/tests/auto/core/webenginedriver/tst_webenginedriver.cpp b/tests/auto/core/webenginedriver/tst_webenginedriver.cpp
new file mode 100644
index 000000000..cd3098b25
--- /dev/null
+++ b/tests/auto/core/webenginedriver/tst_webenginedriver.cpp
@@ -0,0 +1,631 @@
+// Copyright (C) 2023 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 <QtCore/qjsondocument.h>
+#include <QtCore/qlibraryinfo.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qprocess.h>
+#include <QtGui/qimage.h>
+#include <QtNetwork/qnetworkaccessmanager.h>
+#include <QtNetwork/qnetworkreply.h>
+#include <QtNetwork/qnetworkrequest.h>
+#include <QtWebEngineCore/qtwebenginecoreglobal.h>
+#include <QtWebEngineCore/qwebenginepage.h>
+#include <QtWebEngineCore/qwebengineprofile.h>
+#include <QtWebEngineWidgets/qwebengineview.h>
+
+#include <widgetutil.h>
+
+#define REMOTE_DEBUGGING_PORT 12345
+#define WEBENGINEDRIVER_PORT 9515
+
+class DriverServer : public QObject
+{
+ Q_OBJECT
+
+public:
+ DriverServer(QProcessEnvironment processEnvironment = {}, QStringList processArguments = {})
+ : m_serverURL(QUrl(
+ QLatin1String("http://localhost:%1").arg(QString::number(WEBENGINEDRIVER_PORT))))
+ {
+ QString driverPath = QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath)
+ + QLatin1String("/webenginedriver");
+#if defined(Q_OS_WIN)
+ driverPath += QLatin1String(".exe");
+#endif
+ m_process.setProgram(driverPath);
+
+ if (processArguments.isEmpty())
+ processArguments
+ << QLatin1String("--port=%1").arg(QString::number(WEBENGINEDRIVER_PORT));
+ m_process.setArguments(processArguments);
+
+ if (!processEnvironment.isEmpty())
+ m_process.setProcessEnvironment(processEnvironment);
+
+ connect(&m_process, &QProcess::errorOccurred, [this](QProcess::ProcessError error) {
+ qWarning() << "WebEngineDriver error occurred:" << error;
+ dumpConsoleMessages();
+ });
+
+ connect(&m_process, &QProcess::readyReadStandardError, [this]() {
+ QProcess::ProcessChannel tmp = m_process.readChannel();
+ m_process.setReadChannel(QProcess::StandardError);
+ while (m_process.canReadLine()) {
+ QString line = QString::fromUtf8(m_process.readLine());
+ if (line.endsWith(QLatin1Char('\n')))
+ line.truncate(line.size() - 1);
+ m_stderr << line;
+ }
+ m_process.setReadChannel(tmp);
+ });
+
+ connect(&m_process, &QProcess::readyReadStandardOutput, [this]() {
+ QProcess::ProcessChannel tmp = m_process.readChannel();
+ m_process.setReadChannel(QProcess::StandardOutput);
+ while (m_process.canReadLine()) {
+ QString line = QString::fromUtf8(m_process.readLine());
+ if (line.endsWith(QLatin1Char('\n')))
+ line.truncate(line.size() - 1);
+ m_stdout << line;
+ }
+ m_process.setReadChannel(tmp);
+ });
+ }
+
+ ~DriverServer()
+ {
+ if (m_process.state() != QProcess::Running)
+ return;
+
+ if (!m_sessionId.isEmpty())
+ deleteSession();
+
+ shutdown();
+ }
+
+ bool start()
+ {
+ if (!QFileInfo::exists(m_process.program())) {
+ qWarning() << "WebEngineDriver executable not found:" << m_process.program();
+ return false;
+ }
+
+ connect(&m_process, &QProcess::finished,
+ [this](int exitCode, QProcess::ExitStatus exitStatus) {
+ qWarning().nospace()
+ << "WebEngineDriver exited unexpectedly (exitCode: " << exitCode
+ << " exitStatus: " << exitStatus << "):";
+ dumpConsoleMessages();
+ });
+
+ m_process.start();
+
+ bool started = m_process.waitForStarted();
+ if (!started) {
+ qWarning() << "Failed to start WebEngineDriver:" << m_process.errorString();
+ return false;
+ }
+
+ bool ready = QTest::qWaitFor(
+ [this]() {
+ if (m_process.state() != QProcess::Running)
+ return true;
+
+ for (QString line : m_stdout) {
+ if (line.contains(
+ QLatin1String("WebEngineDriver was started successfully.")))
+ return true;
+ }
+
+ return false;
+ },
+ 10000);
+
+ if (ready && m_process.state() != QProcess::Running) {
+ // Warning is already reported by handler of QProcess::finished() signal.
+ return false;
+ }
+
+ if (!ready) {
+ if (m_stdout.empty())
+ qWarning("Starting WebEngineDriver timed out.");
+ else
+ qWarning("Something went wrong while starting WebEngineDriver:");
+
+ dumpConsoleMessages();
+ return false;
+ }
+
+ return true;
+ }
+
+ bool shutdown()
+ {
+ // Do not warn about unexpected exit.
+ disconnect(&m_process, &QProcess::finished, nullptr, nullptr);
+
+ bool sent = sendCommand(QLatin1String("/shutdown"));
+
+ bool finished = (m_process.state() == QProcess::NotRunning) || m_process.waitForFinished();
+ if (!finished || !sent)
+ qWarning() << "Failed to properly shutdown WebEngineDriver:" << m_process.errorString();
+
+ return finished;
+ }
+
+ bool sendCommand(QString command, const QJsonDocument &params = {},
+ QJsonDocument *result = nullptr)
+ {
+ if (command.contains(QLatin1String(":sessionId"))) {
+ if (m_sessionId.isEmpty()) {
+ qWarning("Unable to execute session command without session.");
+ return false;
+ }
+
+ QStringList commandList = command.split(QLatin1Char('/'));
+ for (int i = 0; i < commandList.size(); ++i) {
+ if (commandList[i] == QLatin1String(":sessionId")) {
+ commandList[i] = m_sessionId;
+ break;
+ }
+ }
+
+ command = commandList.join(QLatin1Char('/'));
+ }
+
+ QNetworkReply::NetworkError replyError = QNetworkReply::NoError;
+ QString replyString;
+
+ connect(
+ &m_qnam, &QNetworkAccessManager::finished, this,
+ [&replyError, &replyString](QNetworkReply *reply) {
+ replyError = reply->error();
+ replyString = QString::fromUtf8(reply->readAll());
+ },
+ static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
+
+ QNetworkRequest request;
+ QUrl requestURL = m_serverURL;
+
+ requestURL.setPath(command);
+ request.setUrl(requestURL);
+
+ if (params.isEmpty()) {
+ m_qnam.get(request);
+ } else {
+ request.setHeader(QNetworkRequest::ContentTypeHeader,
+ QVariant::fromValue(QStringLiteral("application/json")));
+ m_qnam.post(request, params.toJson(QJsonDocument::Compact));
+ }
+
+ bool ready = QTest::qWaitFor(
+ [&replyError, &replyString]() {
+ return replyError != QNetworkReply::NoError || !replyString.isEmpty();
+ },
+ 10000);
+
+ if (!ready) {
+ qWarning() << "Command" << command << "timed out.";
+ dumpConsoleMessages();
+ return false;
+ }
+
+ if (replyError != QNetworkReply::NoError) {
+ qWarning() << "Network error:" << replyError;
+ if (!replyString.isEmpty()) {
+ QJsonDocument errorReply = QJsonDocument::fromJson(replyString.toLatin1());
+ if (!errorReply.isNull()) {
+ QString error = errorReply["value"]["error"].toString();
+ QString message = errorReply["value"]["message"].toString();
+ if (!error.isEmpty() || message.isEmpty()) {
+ qWarning() << "error:" << error;
+ qWarning() << "message:" << message;
+ return false;
+ }
+ }
+
+ qWarning() << replyString;
+ return false;
+ }
+
+ dumpConsoleMessages();
+ return false;
+ }
+
+ if (result) {
+ if (replyString.isEmpty()) {
+ qWarning("Network reply is empty.");
+ return false;
+ }
+
+ QJsonParseError jsonError;
+ *result = QJsonDocument::fromJson(replyString.toLatin1(), &jsonError);
+
+ if (jsonError.error != QJsonParseError::NoError) {
+ qWarning() << "Unable to parse reply:" << jsonError.errorString();
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool createSession(QJsonObject chromeOptions = {}, QString *sessionId = nullptr)
+ {
+ if (!m_sessionId.isEmpty()) {
+ qWarning("A session already exists.");
+ return false;
+ }
+
+ QJsonObject root;
+
+ if (chromeOptions.isEmpty()) {
+ // Connect to the test by default.
+ chromeOptions.insert(
+ QLatin1String("debuggerAddress"),
+ QLatin1String("localhost:%1").arg(QString::number(REMOTE_DEBUGGING_PORT)));
+ chromeOptions.insert(QLatin1String("w3c"), true);
+ }
+
+ QJsonObject alwaysMatch;
+ alwaysMatch.insert(QLatin1String("goog:chromeOptions"), chromeOptions);
+
+ QJsonObject seOptions;
+ seOptions.insert(QLatin1String("loggingPrefs"), QJsonObject());
+ alwaysMatch.insert(QLatin1String("se:options"), seOptions);
+
+ QJsonObject capabilities;
+ capabilities.insert(QLatin1String("alwaysMatch"), alwaysMatch);
+ root.insert(QLatin1String("capabilities"), capabilities);
+
+ QJsonDocument params;
+ params.setObject(root);
+
+ QJsonDocument sessionReply;
+ bool sent = sendCommand(QLatin1String("/session"), params, &sessionReply);
+ if (sent) {
+ m_sessionId = sessionReply["value"]["sessionId"].toString();
+ if (sessionId)
+ *sessionId = m_sessionId;
+ }
+
+ return sent;
+ }
+
+ bool deleteSession()
+ {
+ if (m_sessionId.isEmpty()) {
+ qWarning("There is no active session.");
+ return false;
+ }
+
+ QNetworkReply::NetworkError replyError = QNetworkReply::NoError;
+ QString replyString;
+
+ connect(
+ &m_qnam, &QNetworkAccessManager::finished, this,
+ [&replyError, &replyString](QNetworkReply *reply) {
+ replyError = reply->error();
+ replyString = QString::fromUtf8(reply->readAll());
+ },
+ static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
+
+ QNetworkRequest request;
+ QUrl requestURL = m_serverURL;
+ requestURL.setPath(QLatin1String("/session/%1").arg(m_sessionId));
+ request.setUrl(requestURL);
+ m_qnam.deleteResource(request);
+
+ bool ready = QTest::qWaitFor(
+ [&replyError, &replyString]() {
+ return replyError != QNetworkReply::NoError || !replyString.isEmpty();
+ },
+ 10000);
+
+ if (!ready) {
+ qWarning("Deleting session timed out.");
+ return false;
+ }
+
+ if (replyError != QNetworkReply::NoError) {
+ qWarning() << "Network error:" << replyError;
+ return false;
+ }
+
+ return true;
+ }
+
+ QStringList stderrLines() const { return m_stderr; }
+
+ bool waitForMessageOnStderr(const QRegularExpression &re, QString *message = nullptr) const
+ {
+ return QTest::qWaitFor(
+ [this, &re, message]() {
+ for (QString line : m_stderr) {
+ if (line.contains(re)) {
+ if (message)
+ *message = line;
+ return true;
+ }
+ }
+
+ return false;
+ },
+ 10000);
+ }
+
+private:
+ void dumpConsoleMessages()
+ {
+ auto dumpLines = [](QStringList *lines) {
+ if (lines->empty())
+ return;
+
+ for (QString line : *lines)
+ qWarning() << qPrintable(line);
+
+ lines->clear();
+ };
+
+ dumpLines(&m_stdout);
+ dumpLines(&m_stderr);
+ }
+
+ QProcess m_process;
+ QStringList m_stdout;
+ QStringList m_stderr;
+
+ QUrl m_serverURL;
+ QString m_sessionId;
+
+ QNetworkAccessManager m_qnam;
+};
+
+class tst_WebEngineDriver : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void status();
+ void startBrowser();
+ void navigate();
+ void typeElement();
+ void executeScript();
+ void screenshot();
+};
+
+void tst_WebEngineDriver::status()
+{
+ DriverServer driverServer;
+ QVERIFY(driverServer.start());
+
+ QJsonDocument statusReply;
+ QVERIFY(driverServer.sendCommand(QLatin1String("/status"), {}, &statusReply));
+
+ QString versionString = statusReply["value"]["build"]["version"].toString();
+ QCOMPARE(qWebEngineChromiumVersion(), versionString.split(QLatin1Char(' '))[0]);
+
+ bool ready = statusReply["value"]["ready"].toBool();
+ QVERIFY(ready);
+}
+
+void tst_WebEngineDriver::startBrowser()
+{
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ env.insert(QLatin1String("QTWEBENGINE_REMOTE_DEBUGGING"),
+ QString::number(REMOTE_DEBUGGING_PORT));
+
+ QStringList args;
+ args << QLatin1String("--port=%1").arg(QString::number(WEBENGINEDRIVER_PORT));
+ args << QLatin1String("--log-level=ALL");
+
+ DriverServer driverServer(env, args);
+ QVERIFY(driverServer.start());
+
+ QString testBrowserPath =
+ QCoreApplication::applicationDirPath() + QLatin1String("/testbrowser");
+#if defined(Q_OS_WIN)
+ testBrowserPath += QLatin1String(".exe");
+#endif
+
+ QJsonArray chromeArgs;
+ chromeArgs.append(QLatin1String("enable-logging=stderr"));
+ chromeArgs.append(QLatin1String("v=1"));
+ // To force graceful shutdown.
+ chromeArgs.append(QLatin1String("log-net-log"));
+
+ QJsonObject chromeOptions;
+ chromeOptions.insert(QLatin1String("binary"), testBrowserPath);
+ chromeOptions.insert(QLatin1String("args"), chromeArgs);
+ chromeOptions.insert(QLatin1String("w3c"), true);
+ QString sessionId;
+ QVERIFY(driverServer.createSession(chromeOptions, &sessionId));
+
+ // Test if the browser is started.
+ QVERIFY(driverServer.waitForMessageOnStderr(QRegularExpression(
+ QLatin1String("^DevTools listening on ws://127.0.0.1:%1/devtools/browser/")
+ .arg(QString::number(REMOTE_DEBUGGING_PORT)))));
+ QVERIFY(driverServer.waitForMessageOnStderr(QRegularExpression(
+ QLatin1String("^Remote debugging server started successfully. "
+ "Try pointing a Chromium-based browser to http://127.0.0.1:%1")
+ .arg(QString::number(REMOTE_DEBUGGING_PORT)))));
+
+ // Check custom chromeArgs via logging.
+ QVERIFY(driverServer.waitForMessageOnStderr(QRegularExpression(QLatin1String("VERBOSE1"))));
+
+ QJsonDocument statusReply;
+ QVERIFY(driverServer.sendCommand(QLatin1String("/status"), {}, &statusReply));
+ bool ready = statusReply["value"]["ready"].toBool();
+ QVERIFY(ready);
+
+ QVERIFY(driverServer.deleteSession());
+
+ // Test if the browser is stopped.
+ QVERIFY(driverServer.waitForMessageOnStderr(
+ QRegularExpression(QLatin1String("Test browser is about to quit."))));
+ QVERIFY(driverServer.waitForMessageOnStderr(
+ QRegularExpression(QLatin1String("\\[%1\\] RESPONSE Quit").arg(sessionId))));
+}
+
+void tst_WebEngineDriver::navigate()
+{
+ DriverServer driverServer;
+ QVERIFY(driverServer.start());
+
+ QWebEnginePage page;
+ QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished);
+
+ page.load(QUrl(QLatin1String("about:blank")));
+ QTRY_COMPARE(loadSpy.size(), 1);
+
+ QVERIFY(driverServer.createSession());
+
+ QUrl url = QUrl(QLatin1String("qrc:///resources/input.html"));
+ QString paramsString = QLatin1String("{\"url\": \"%1\"}").arg(url.toString());
+ QJsonDocument params = QJsonDocument::fromJson(paramsString.toUtf8());
+ QVERIFY(driverServer.sendCommand(QLatin1String("/session/:sessionId/url"), params));
+ QTRY_COMPARE(loadSpy.size(), 2);
+ QVERIFY(loadSpy.at(1).at(0).toBool());
+ QCOMPARE(url, page.url());
+
+ QVERIFY(driverServer.deleteSession());
+}
+
+void tst_WebEngineDriver::typeElement()
+{
+ DriverServer driverServer;
+ QVERIFY(driverServer.start());
+
+ QWebEngineView view;
+ view.resize(300, 100);
+ view.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
+
+ QUrl url = QUrl(QLatin1String("qrc:///resources/input.html"));
+ QSignalSpy loadSpy(view.page(), &QWebEnginePage::loadFinished);
+ view.load(url);
+ QTRY_COMPARE(loadSpy.size(), 1);
+
+ QVERIFY(driverServer.createSession());
+
+ // Find <input id="text_input"> element and extract its id from the reply.
+ QString textInputId;
+ {
+ QString paramsString = QLatin1String(
+ "{\"using\": \"css selector\", \"value\": \"[id=\\\"text_input\\\"]\"}");
+ QJsonDocument params = QJsonDocument::fromJson(paramsString.toUtf8());
+ QJsonDocument elementReply;
+ QVERIFY(driverServer.sendCommand(QLatin1String("/session/:sessionId/element"), params,
+ &elementReply));
+
+ QVERIFY(!elementReply.isEmpty());
+ QJsonObject value = elementReply["value"].toObject();
+ QVERIFY(!value.isEmpty());
+ QStringList keys = value.keys();
+ QCOMPARE(keys.size(), 1);
+ textInputId = value[keys[0]].toString();
+ QVERIFY(!textInputId.isEmpty());
+ }
+
+ // Type text into the input field.
+ QString inputText = QLatin1String("WebEngineDriver");
+ {
+ QString command = QLatin1String("/session/:sessionId/element/%1/value").arg(textInputId);
+
+ QJsonObject root;
+ root.insert(QLatin1String("text"), inputText);
+ QJsonArray value;
+ for (QChar ch : inputText) {
+ value.append(QJsonValue(ch));
+ }
+ root.insert(QLatin1String("value"), value);
+ root.insert(QLatin1String("id"), textInputId);
+ QJsonDocument params;
+ params.setObject(root);
+
+ QVERIFY(driverServer.sendCommand(command, params));
+
+ QTRY_COMPARE(
+ evaluateJavaScriptSync(view.page(), "document.getElementById('text_input').value")
+ .toString(),
+ inputText);
+ }
+
+ QVERIFY(driverServer.deleteSession());
+}
+
+void tst_WebEngineDriver::executeScript()
+{
+ DriverServer driverServer;
+ QVERIFY(driverServer.start());
+
+ QUrl url = QUrl(QLatin1String("qrc:///resources/input.html"));
+ QWebEnginePage page;
+ QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished);
+
+ page.load(url);
+ QTRY_COMPARE(loadSpy.size(), 1);
+ QCOMPARE(page.title(), url.toString());
+ QSignalSpy titleSpy(&page, &QWebEnginePage::titleChanged);
+
+ QString newTitle = QLatin1String("WebEngineDriver Test Page");
+ QString script = QLatin1String("document.title = '%1';"
+ "return document.title;")
+ .arg(newTitle);
+
+ QVERIFY(driverServer.createSession());
+ QString paramsString = QLatin1String("{\"script\": \"%1\", \"args\": []}").arg(script);
+ QJsonDocument params = QJsonDocument::fromJson(paramsString.toUtf8());
+ QJsonDocument executeReply;
+ QVERIFY(driverServer.sendCommand(QLatin1String("/session/:sessionId/execute/sync"), params,
+ &executeReply));
+
+ QTRY_COMPARE(titleSpy.size(), 1);
+ QCOMPARE(executeReply["value"].toString(), newTitle);
+ QCOMPARE(page.title(), newTitle);
+
+ QVERIFY(driverServer.deleteSession());
+}
+
+void tst_WebEngineDriver::screenshot()
+{
+ DriverServer driverServer;
+ QVERIFY(driverServer.start());
+
+ QWebEngineView view;
+ view.resize(300, 100);
+ view.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
+
+ QSignalSpy loadSpy(view.page(), &QWebEnginePage::loadFinished);
+
+ view.setHtml(QLatin1String("<html><head><style>"
+ "html {background-color:red;}"
+ "</style></head><body></body></html>"));
+ QTRY_COMPARE(loadSpy.size(), 1);
+
+ QVERIFY(driverServer.createSession());
+ QJsonDocument screenshotReply;
+ QVERIFY(driverServer.sendCommand(QLatin1String("/session/:sessionId/screenshot"), {},
+ &screenshotReply));
+
+ QByteArray base64 = screenshotReply["value"].toString().toLocal8Bit();
+ QVERIFY(!base64.isEmpty());
+ QImage screenshot;
+ screenshot.loadFromData(QByteArray::fromBase64(base64));
+ QVERIFY(!screenshot.isNull());
+ QCOMPARE(screenshot.pixel(screenshot.width() / 2, screenshot.height() / 2), 0xFFFF0000);
+
+ QVERIFY(driverServer.deleteSession());
+}
+
+#define STRINGIFY_LITERAL(x) #x
+#define STRINGIFY_EXPANDED(x) STRINGIFY_LITERAL(x)
+static QByteArrayList params = QByteArrayList()
+ << "--remote-debugging-port=" STRINGIFY_EXPANDED(REMOTE_DEBUGGING_PORT);
+W_QTEST_MAIN(tst_WebEngineDriver, params)
+
+#include "tst_webenginedriver.moc"
diff --git a/tests/auto/httpserver/httpreqrep.cpp b/tests/auto/httpserver/httpreqrep.cpp
index 34e148678..8b338ce4e 100644
--- a/tests/auto/httpserver/httpreqrep.cpp
+++ b/tests/auto/httpserver/httpreqrep.cpp
@@ -57,6 +57,11 @@ QByteArray HttpReqRep::requestHeader(const QByteArray &key) const
return {};
}
+bool HttpReqRep::hasRequestHeader(const QByteArray &key) const
+{
+ return m_requestHeaders.find(key.toLower()) != m_requestHeaders.end();
+}
+
void HttpReqRep::handleReadyRead()
{
while (m_socket->canReadLine()) {
diff --git a/tests/auto/httpserver/httpreqrep.h b/tests/auto/httpserver/httpreqrep.h
index a8482536d..774c08eb1 100644
--- a/tests/auto/httpserver/httpreqrep.h
+++ b/tests/auto/httpserver/httpreqrep.h
@@ -25,6 +25,7 @@ public:
QByteArray requestMethod() const { return m_requestMethod; }
QByteArray requestPath() const { return m_requestPath; }
QByteArray requestHeader(const QByteArray &key) const;
+ bool hasRequestHeader(const QByteArray &key) const;
// Response parameters (can be set until sendResponse()/close()).
diff --git a/tests/auto/httpserver/httpserver.h b/tests/auto/httpserver/httpserver.h
index 270b6265f..201eef4c6 100644
--- a/tests/auto/httpserver/httpserver.h
+++ b/tests/auto/httpserver/httpserver.h
@@ -59,6 +59,8 @@ public:
Q_INVOKABLE void setHostDomain(const QString &host) { m_url.setHost(host); }
+ Q_INVOKABLE QTcpServer *getTcpServer() const { return m_tcpServer; }
+
Q_SIGNALS:
// Emitted after a HTTP request has been successfully parsed.
void newRequest(HttpReqRep *reqRep);
diff --git a/tests/auto/httpserver/httpsserver.h b/tests/auto/httpserver/httpsserver.h
index 10deeb322..d029851aa 100644
--- a/tests/auto/httpserver/httpsserver.h
+++ b/tests/auto/httpserver/httpsserver.h
@@ -55,11 +55,19 @@ static QSslServer *createServer(const QString &certificateFileName, const QStrin
struct HttpsServer : HttpServer
{
HttpsServer(const QString &certPath, const QString &keyPath, const QString &ca,
- QObject *parent = nullptr)
- : HttpServer(createServer(certPath, keyPath, ca), "https", QHostAddress::LocalHost, 0,
+ quint16 port = 0, QObject *parent = nullptr)
+ : HttpServer(createServer(certPath, keyPath, ca), "https", QHostAddress::LocalHost, port,
parent)
{
}
+
+ void setVerifyMode(const QSslSocket::PeerVerifyMode verifyMode)
+ {
+ QSslServer *server = static_cast<QSslServer *>(getTcpServer());
+ QSslConfiguration config = server->sslConfiguration();
+ config.setPeerVerifyMode(verifyMode);
+ server->setSslConfiguration(config);
+ }
};
#endif
diff --git a/tests/auto/pdf/CMakeLists.txt b/tests/auto/pdf/CMakeLists.txt
index 8bda0c3c3..205bd24d0 100644
--- a/tests/auto/pdf/CMakeLists.txt
+++ b/tests/auto/pdf/CMakeLists.txt
@@ -2,8 +2,11 @@
# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(qpdfbookmarkmodel)
-#add_subdirectory(qpdfpagenavigator)
+if (TARGET Qt::PdfWidgets)
+ add_subdirectory(qpdfpagenavigator)
+endif()
add_subdirectory(qpdfpagerenderer)
+add_subdirectory(qpdfsearchmodel)
if(TARGET Qt::PrintSupport)
add_subdirectory(qpdfdocument)
endif()
diff --git a/tests/auto/pdf/qpdfbookmarkmodel/CMakeLists.txt b/tests/auto/pdf/qpdfbookmarkmodel/CMakeLists.txt
index d0dc966f0..729bc9138 100644
--- a/tests/auto/pdf/qpdfbookmarkmodel/CMakeLists.txt
+++ b/tests/auto/pdf/qpdfbookmarkmodel/CMakeLists.txt
@@ -8,5 +8,8 @@ qt_internal_add_test(tst_qpdfbookmarkmodel
Qt::Gui
Qt::Network
Qt::Pdf
+ TESTDATA
+ pdf-sample.bookmarks.pdf
+ pdf-sample.bookmarks_pages.pdf
)
diff --git a/tests/auto/pdf/qpdfdocument/CMakeLists.txt b/tests/auto/pdf/qpdfdocument/CMakeLists.txt
index 0d49bc6f1..b8300ef27 100644
--- a/tests/auto/pdf/qpdfdocument/CMakeLists.txt
+++ b/tests/auto/pdf/qpdfdocument/CMakeLists.txt
@@ -9,4 +9,10 @@ qt_internal_add_test(tst_qpdfdocument
Qt::Network
Qt::PrintSupport
Qt::Pdf
+ TESTDATA
+ pdf-sample.protected.pdf
+ pdf-sample.metadata.pdf
+ rotated_text.pdf
+ tagged_mcr_multipage.pdf
+ test.pdf
)
diff --git a/tests/auto/pdf/qpdfdocument/rotated_text.pdf b/tests/auto/pdf/qpdfdocument/rotated_text.pdf
new file mode 100644
index 000000000..d6d8db84e
--- /dev/null
+++ b/tests/auto/pdf/qpdfdocument/rotated_text.pdf
@@ -0,0 +1,70 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+ /Type /Catalog
+ /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+ /Type /Pages
+ /MediaBox [ 0 0 200 200 ]
+ /Count 1
+ /Kids [ 3 0 R ]
+>>
+endobj
+3 0 obj <<
+ /Type /Page
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 4 0 R
+ >>
+ >>
+ /Contents 5 0 R
+>>
+endobj
+4 0 obj <<
+ /Type /Font
+ /Subtype /Type1
+ /BaseFont /Times-Roman
+>>
+endobj
+5 0 obj <<
+ /Length 406
+>>
+stream
+BT
+0 0 Td
+/F1 12 Tf
+0.70710678118 -0.70710678118 0.70710678118 0.70710678118 100 100 Tm
+(Hello,) Tj
+0 0 Td
+/F1 12 Tf
+-0.70710678118 -0.70710678118 0.70710678118 -0.70710678118 100 100 Tm
+( world!\r\n) Tj
+0 0 Td
+/F1 12 Tf
+-0.70710678118 0.70710678118 -0.70710678118 -0.70710678118 100 100 Tm
+(Goodbye,) Tj
+0 0 Td
+/F1 12 Tf
+0.70710678118 0.70710678118 -0.70710678118 0.70710678118 100 100 Tm
+( world!) Tj
+ET
+endstream
+endobj
+xref
+0 6
+0000000000 65535 f
+0000000015 00000 n
+0000000068 00000 n
+0000000161 00000 n
+0000000287 00000 n
+0000000365 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 6
+>>
+startxref
+823
+%%EOF
diff --git a/tests/auto/pdf/qpdfdocument/tagged_mcr_multipage.pdf b/tests/auto/pdf/qpdfdocument/tagged_mcr_multipage.pdf
new file mode 100644
index 000000000..fcc5fafda
--- /dev/null
+++ b/tests/auto/pdf/qpdfdocument/tagged_mcr_multipage.pdf
@@ -0,0 +1,136 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+ /Type /Catalog
+ /MarkInfo <<
+ /Type /MarkInfo
+ /Marked true
+ >>
+ /Pages 2 0 R
+ /StructTreeRoot 8 0 R
+>>
+endobj
+2 0 obj <<
+ /Type /Pages
+ /CropBox [ 10.8197 8.459 605.705 801.639 ]
+ /MediaBox [ 0.0 0.0 616.721 809.902 ]
+ /Count 2
+ /Kids [
+ 4 0 R
+ 6 0 R
+ ]
+>>
+endobj
+3 0 obj <<
+ /Type /Font
+ /Subtype /Type1
+ /BaseFont /Times-Roman
+>>
+endobj
+4 0 obj <<
+ /Type /Page
+ /Tabs /S
+ /Parent 2 0 R
+ /StructParents 0
+ /Contents 5 0 R
+ /Resources <<
+ /ProcSet [/PDF /Text]
+ /Font <<
+ /F1 3 0 R
+ >>
+ >>
+>>
+endobj
+5 0 obj <<
+ /Length 83
+>>
+stream
+BT
+/Document <</MCID 0 >>BDC
+0 i
+/F1 1 Tf
+12 0 0 12 43.073 771.625 Tm
+(1)Tj
+EMC
+ET
+endstream
+endobj
+6 0 obj <<
+ /Type /Page
+ /Tabs /S
+ /Parent 2 0 R
+ /StructParents 1
+ /Contents 7 0 R
+ /Resources <<
+ /ProcSet [/PDF /Text]
+ /Font <<
+ /F1 3 0 R
+ >>
+ >>
+>>
+endobj
+7 0 obj <<
+ /Length 83
+>>
+stream
+BT
+/Document <</MCID 0 >>BDC
+0 i
+/F1 1 Tf
+12 0 0 12 43.073 771.625 Tm
+(2)Tj
+EMC
+ET
+endstream
+endobj
+8 0 obj <<
+ /Type /StructTreeRoot
+ /K 10 0 R
+ /ParentTree 9 0 R
+ /ParentTreeNextKey 2
+>>
+endobj
+9 0 obj <<
+ /Nums [
+ 0
+ [10 0 R]
+ 1
+ [10 0 R]
+ ]
+>>
+endobj
+10 0 obj <<
+ /T ()
+ /S /Document
+ /P 8 0 R
+ /Pg 4 0 R
+ /K [
+ 0
+ <<
+ /MCID 0
+ /Pg 6 0 R
+ /Type /MCR
+ >>
+ ]
+>>
+%endobj
+xref
+0 11
+0000000000 65535 f
+0000000015 00000 n
+0000000149 00000 n
+0000000315 00000 n
+0000000393 00000 n
+0000000575 00000 n
+0000000709 00000 n
+0000000891 00000 n
+0000001025 00000 n
+0000001125 00000 n
+0000001198 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 11
+>>
+startxref
+1345
+%%EOF
diff --git a/tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp b/tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp
index 96a0e265e..d222bff0c 100644
--- a/tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp
+++ b/tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp
@@ -7,7 +7,9 @@
#include <QPainter>
#include <QPdfDocument>
#include <QPrinter>
+#include <QDateTime>
#include <QTemporaryFile>
+#include <QTimeZone>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
@@ -34,6 +36,10 @@ private slots:
void passwordClearedOnClose();
void metaData();
void pageLabels();
+ void getSelection_data();
+ void getSelection();
+ void getSelectionAtIndex_data();
+ void getSelectionAtIndex();
private:
void consistencyCheck(QPdfDocument &doc) const;
@@ -376,8 +382,10 @@ void tst_QPdfDocument::metaData()
QCOMPARE(doc.metaData(QPdfDocument::MetaDataField::Keywords).toString(), QString::fromLatin1("meta data keywords"));
QCOMPARE(doc.metaData(QPdfDocument::MetaDataField::Producer).toString(), QString::fromLatin1("LibreOffice 5.1"));
QCOMPARE(doc.metaData(QPdfDocument::MetaDataField::Creator).toString(), QString::fromLatin1("Writer"));
- QCOMPARE(doc.metaData(QPdfDocument::MetaDataField::CreationDate).toDateTime(), QDateTime(QDate(2016, 8, 7), QTime(7, 3, 6), Qt::UTC));
- QCOMPARE(doc.metaData(QPdfDocument::MetaDataField::ModificationDate).toDateTime(), QDateTime(QDate(2016, 8, 8), QTime(8, 3, 6), Qt::UTC));
+ QCOMPARE(doc.metaData(QPdfDocument::MetaDataField::CreationDate).toDateTime(),
+ QDateTime(QDate(2016, 8, 7), QTime(7, 3, 6), QTimeZone::UTC));
+ QCOMPARE(doc.metaData(QPdfDocument::MetaDataField::ModificationDate).toDateTime(),
+ QDateTime(QDate(2016, 8, 8), QTime(8, 3, 6), QTimeZone::UTC));
}
void tst_QPdfDocument::pageLabels()
@@ -390,6 +398,86 @@ void tst_QPdfDocument::pageLabels()
QCOMPARE(doc.pageLabel(2), "i"); // i of the tiger!
}
+void tst_QPdfDocument::getSelection_data()
+{
+ QTest::addColumn<QString>("pdfPath");
+ QTest::addColumn<int>("page");
+ QTest::addColumn<QPointF>("start");
+ QTest::addColumn<QPointF>("end");
+ QTest::addColumn<QString>("expectedText");
+ QTest::addColumn<int>("expectedStartIndex");
+ QTest::addColumn<int>("expectedEndIndex");
+ QTest::addColumn<QRect>("expectedBounds");
+ QTest::addColumn<int>("expectedPolygonCount");
+
+ QTest::newRow("raid") << QFINDTESTDATA("test.pdf")
+ << 1 << QPointF(316.4, 206) << QPointF(339, 201)
+ << "raid" << 80 << 84 << QRect(316, 201, 21, 12) << 1;
+ QTest::newRow("rotated text") << QFINDTESTDATA("rotated_text.pdf")
+ << 0 << QPointF(102, 94) << QPointF(125, 73)
+ << "world!" << 25 << 31 << QRect(98, 70, 26, 28) << 1;
+}
+
+void tst_QPdfDocument::getSelection()
+{
+ QFETCH(QString, pdfPath);
+ QFETCH(int, page);
+ QFETCH(QPointF, start);
+ QFETCH(QPointF, end);
+ QFETCH(QString, expectedText);
+ QFETCH(int, expectedStartIndex);
+ QFETCH(int, expectedEndIndex);
+ QFETCH(QRect, expectedBounds);
+ QFETCH(int, expectedPolygonCount);
+
+ QPdfDocument doc;
+ QCOMPARE(doc.load(pdfPath), QPdfDocument::Error::None);
+
+ QPdfSelection sel = doc.getSelection(page, start, end);
+ QCOMPARE(sel.text(), expectedText);
+ QCOMPARE(sel.startIndex(), expectedStartIndex);
+ QCOMPARE(sel.endIndex(), expectedEndIndex);
+ QCOMPARE(sel.boundingRectangle().toRect(), expectedBounds);
+ QCOMPARE(sel.bounds().size(), expectedPolygonCount);
+}
+
+void tst_QPdfDocument::getSelectionAtIndex_data()
+{
+ QTest::addColumn<QString>("pdfPath");
+ QTest::addColumn<int>("page");
+ QTest::addColumn<int>("start");
+ QTest::addColumn<int>("maxLen");
+ QTest::addColumn<QString>("expectedText");
+ QTest::addColumn<QRect>("expectedBounds");
+ QTest::addColumn<int>("expectedPolygonCount");
+
+ QTest::newRow("raid") << QFINDTESTDATA("test.pdf")
+ << 1 << 80 << 4 << "raid" << QRect(316, 201, 21, 12) << 1;
+ QTest::newRow("rotated text") << QFINDTESTDATA("rotated_text.pdf")
+ << 0 << 7 << 6 << "world!" << QRect(76, 102, 26, 28) << 1;
+ QTest::newRow("displaced text") << QFINDTESTDATA("tagged_mcr_multipage.pdf")
+ << 0 << 0 << 10 << "1" << QRect(34, 22, 3, 8) << 1;
+}
+
+void tst_QPdfDocument::getSelectionAtIndex()
+{
+ QFETCH(QString, pdfPath);
+ QFETCH(int, page);
+ QFETCH(int, start);
+ QFETCH(int, maxLen);
+ QFETCH(QString, expectedText);
+ QFETCH(QRect, expectedBounds);
+ QFETCH(int, expectedPolygonCount);
+
+ QPdfDocument doc;
+ QCOMPARE(doc.load(pdfPath), QPdfDocument::Error::None);
+
+ QPdfSelection sel = doc.getSelectionAtIndex(page, start, maxLen);
+ QCOMPARE(sel.text(), expectedText);
+ QCOMPARE(sel.boundingRectangle().toRect(), expectedBounds);
+ QCOMPARE(sel.bounds().size(), expectedPolygonCount);
+}
+
QTEST_MAIN(tst_QPdfDocument)
#include "tst_qpdfdocument.moc"
diff --git a/tests/auto/pdf/qpdfpagenavigator/CMakeLists.txt b/tests/auto/pdf/qpdfpagenavigator/CMakeLists.txt
new file mode 100644
index 000000000..3e8b8f416
--- /dev/null
+++ b/tests/auto/pdf/qpdfpagenavigator/CMakeLists.txt
@@ -0,0 +1,14 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_internal_add_test(tst_qpdfpagenavigator.cpp
+ SOURCES
+ tst_qpdfpagenavigator.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Network
+ Qt::Pdf
+ Qt::PdfWidgets
+ TESTDATA
+ pdf-sample.bookmarks_pages.pdf
+)
diff --git a/tests/auto/pdf/qpdfpagenavigator/pdf-sample.bookmarks_pages.pdf b/tests/auto/pdf/qpdfpagenavigator/pdf-sample.bookmarks_pages.pdf
new file mode 100644
index 000000000..c4e1aa36e
--- /dev/null
+++ b/tests/auto/pdf/qpdfpagenavigator/pdf-sample.bookmarks_pages.pdf
Binary files differ
diff --git a/tests/auto/pdf/qpdfpagenavigator/tst_qpdfpagenavigator.cpp b/tests/auto/pdf/qpdfpagenavigator/tst_qpdfpagenavigator.cpp
new file mode 100644
index 000000000..327a9f36a
--- /dev/null
+++ b/tests/auto/pdf/qpdfpagenavigator/tst_qpdfpagenavigator.cpp
@@ -0,0 +1,70 @@
+// Copyright (C) 2016 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>
+
+#include <QPdfDocument>
+#include <QPdfView>
+#include <QPdfPageNavigator>
+
+class tst_QPdfPageNavigator: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void offScreenSignals();
+};
+
+void tst_QPdfPageNavigator::offScreenSignals()
+{
+ QPdfDocument document;
+ QPdfView pdfView;
+ QPdfPageNavigator *navigator = pdfView.pageNavigator();
+
+ QSignalSpy currentPageChanged(navigator, &QPdfPageNavigator::currentPageChanged);
+ QSignalSpy currentLocationChanged(navigator, &QPdfPageNavigator::currentLocationChanged);
+ QSignalSpy backAvailableChanged(navigator, &QPdfPageNavigator::backAvailableChanged);
+ QSignalSpy forwardAvailableChanged(navigator, &QPdfPageNavigator::forwardAvailableChanged);
+ QSignalSpy jumped(navigator, &QPdfPageNavigator::jumped);
+
+ QCOMPARE(document.load(QFINDTESTDATA("pdf-sample.bookmarks_pages.pdf")), QPdfDocument::Error::None);
+ QVERIFY2(document.pageCount() == 3, "Test document has changed! 3 pages expected.");
+ pdfView.setDocument(&document);
+
+ // Start with a clean history
+ QCOMPARE(forwardAvailableChanged.count(), 0);
+ QCOMPARE(backAvailableChanged.count(), 0);
+
+ navigator->jump(3, QPoint());
+ QCOMPARE(forwardAvailableChanged.count(), 0);
+ QCOMPARE(backAvailableChanged.count(), 1);
+ QCOMPARE(currentPageChanged.count(), 1);
+ QCOMPARE(currentLocationChanged.count(), 0);
+ QCOMPARE(jumped.count(), 1);
+
+ navigator->jump(1, QPoint());
+ QCOMPARE(forwardAvailableChanged.count(), 0);
+ QCOMPARE(backAvailableChanged.count(), 1);
+ QCOMPARE(currentPageChanged.count(), 2);
+ QCOMPARE(currentLocationChanged.count(), 0);
+ QCOMPARE(jumped.count(), 2);
+
+ navigator->back();
+ QCOMPARE(forwardAvailableChanged.count(), 1);
+ QCOMPARE(backAvailableChanged.count(), 1);
+ QCOMPARE(currentPageChanged.count(), 3);
+ QCOMPARE(currentLocationChanged.count(), 0);
+ QCOMPARE(jumped.count(), 3);
+
+ navigator->forward();
+ QCOMPARE(forwardAvailableChanged.count(), 2);
+ QCOMPARE(backAvailableChanged.count(), 1);
+ QCOMPARE(currentPageChanged.count(), 4);
+ QCOMPARE(currentLocationChanged.count(), 0);
+ QCOMPARE(jumped.count(), 4);
+}
+
+QTEST_MAIN(tst_QPdfPageNavigator)
+
+#include "tst_qpdfpagenavigator.moc"
diff --git a/tests/auto/pdf/qpdfpagerenderer/CMakeLists.txt b/tests/auto/pdf/qpdfpagerenderer/CMakeLists.txt
index 029563071..53a68fe59 100644
--- a/tests/auto/pdf/qpdfpagerenderer/CMakeLists.txt
+++ b/tests/auto/pdf/qpdfpagerenderer/CMakeLists.txt
@@ -8,5 +8,7 @@ qt_internal_add_test(tst_qpdfpagerenderer
Qt::Gui
Qt::Network
Qt::Pdf
+ TESTDATA
+ pdf-sample.pagerenderer.pdf
)
diff --git a/tests/auto/pdf/qpdfsearchmodel/CMakeLists.txt b/tests/auto/pdf/qpdfsearchmodel/CMakeLists.txt
index 15fecd21b..668d1ea36 100644
--- a/tests/auto/pdf/qpdfsearchmodel/CMakeLists.txt
+++ b/tests/auto/pdf/qpdfsearchmodel/CMakeLists.txt
@@ -8,4 +8,8 @@ qt_internal_add_test(tst_qpdfsearchmodel
Qt::Gui
Qt::Network
Qt::Pdf
+ TESTDATA
+ rotated_text.pdf
+ tagged_mcr_multipage.pdf
+ test.pdf
)
diff --git a/tests/auto/pdf/qpdfsearchmodel/rotated_text.pdf b/tests/auto/pdf/qpdfsearchmodel/rotated_text.pdf
new file mode 100644
index 000000000..d6d8db84e
--- /dev/null
+++ b/tests/auto/pdf/qpdfsearchmodel/rotated_text.pdf
@@ -0,0 +1,70 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+ /Type /Catalog
+ /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+ /Type /Pages
+ /MediaBox [ 0 0 200 200 ]
+ /Count 1
+ /Kids [ 3 0 R ]
+>>
+endobj
+3 0 obj <<
+ /Type /Page
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 4 0 R
+ >>
+ >>
+ /Contents 5 0 R
+>>
+endobj
+4 0 obj <<
+ /Type /Font
+ /Subtype /Type1
+ /BaseFont /Times-Roman
+>>
+endobj
+5 0 obj <<
+ /Length 406
+>>
+stream
+BT
+0 0 Td
+/F1 12 Tf
+0.70710678118 -0.70710678118 0.70710678118 0.70710678118 100 100 Tm
+(Hello,) Tj
+0 0 Td
+/F1 12 Tf
+-0.70710678118 -0.70710678118 0.70710678118 -0.70710678118 100 100 Tm
+( world!\r\n) Tj
+0 0 Td
+/F1 12 Tf
+-0.70710678118 0.70710678118 -0.70710678118 -0.70710678118 100 100 Tm
+(Goodbye,) Tj
+0 0 Td
+/F1 12 Tf
+0.70710678118 0.70710678118 -0.70710678118 0.70710678118 100 100 Tm
+( world!) Tj
+ET
+endstream
+endobj
+xref
+0 6
+0000000000 65535 f
+0000000015 00000 n
+0000000068 00000 n
+0000000161 00000 n
+0000000287 00000 n
+0000000365 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 6
+>>
+startxref
+823
+%%EOF
diff --git a/tests/auto/pdf/qpdfsearchmodel/tagged_mcr_multipage.pdf b/tests/auto/pdf/qpdfsearchmodel/tagged_mcr_multipage.pdf
new file mode 100644
index 000000000..fcc5fafda
--- /dev/null
+++ b/tests/auto/pdf/qpdfsearchmodel/tagged_mcr_multipage.pdf
@@ -0,0 +1,136 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+ /Type /Catalog
+ /MarkInfo <<
+ /Type /MarkInfo
+ /Marked true
+ >>
+ /Pages 2 0 R
+ /StructTreeRoot 8 0 R
+>>
+endobj
+2 0 obj <<
+ /Type /Pages
+ /CropBox [ 10.8197 8.459 605.705 801.639 ]
+ /MediaBox [ 0.0 0.0 616.721 809.902 ]
+ /Count 2
+ /Kids [
+ 4 0 R
+ 6 0 R
+ ]
+>>
+endobj
+3 0 obj <<
+ /Type /Font
+ /Subtype /Type1
+ /BaseFont /Times-Roman
+>>
+endobj
+4 0 obj <<
+ /Type /Page
+ /Tabs /S
+ /Parent 2 0 R
+ /StructParents 0
+ /Contents 5 0 R
+ /Resources <<
+ /ProcSet [/PDF /Text]
+ /Font <<
+ /F1 3 0 R
+ >>
+ >>
+>>
+endobj
+5 0 obj <<
+ /Length 83
+>>
+stream
+BT
+/Document <</MCID 0 >>BDC
+0 i
+/F1 1 Tf
+12 0 0 12 43.073 771.625 Tm
+(1)Tj
+EMC
+ET
+endstream
+endobj
+6 0 obj <<
+ /Type /Page
+ /Tabs /S
+ /Parent 2 0 R
+ /StructParents 1
+ /Contents 7 0 R
+ /Resources <<
+ /ProcSet [/PDF /Text]
+ /Font <<
+ /F1 3 0 R
+ >>
+ >>
+>>
+endobj
+7 0 obj <<
+ /Length 83
+>>
+stream
+BT
+/Document <</MCID 0 >>BDC
+0 i
+/F1 1 Tf
+12 0 0 12 43.073 771.625 Tm
+(2)Tj
+EMC
+ET
+endstream
+endobj
+8 0 obj <<
+ /Type /StructTreeRoot
+ /K 10 0 R
+ /ParentTree 9 0 R
+ /ParentTreeNextKey 2
+>>
+endobj
+9 0 obj <<
+ /Nums [
+ 0
+ [10 0 R]
+ 1
+ [10 0 R]
+ ]
+>>
+endobj
+10 0 obj <<
+ /T ()
+ /S /Document
+ /P 8 0 R
+ /Pg 4 0 R
+ /K [
+ 0
+ <<
+ /MCID 0
+ /Pg 6 0 R
+ /Type /MCR
+ >>
+ ]
+>>
+%endobj
+xref
+0 11
+0000000000 65535 f
+0000000015 00000 n
+0000000149 00000 n
+0000000315 00000 n
+0000000393 00000 n
+0000000575 00000 n
+0000000709 00000 n
+0000000891 00000 n
+0000001025 00000 n
+0000001125 00000 n
+0000001198 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 11
+>>
+startxref
+1345
+%%EOF
diff --git a/tests/auto/pdf/qpdfsearchmodel/tst_qpdfsearchmodel.cpp b/tests/auto/pdf/qpdfsearchmodel/tst_qpdfsearchmodel.cpp
index 0bdb9296b..cf71b148e 100644
--- a/tests/auto/pdf/qpdfsearchmodel/tst_qpdfsearchmodel.cpp
+++ b/tests/auto/pdf/qpdfsearchmodel/tst_qpdfsearchmodel.cpp
@@ -7,6 +7,8 @@
#include <QPdfDocument>
#include <QPdfSearchModel>
+Q_LOGGING_CATEGORY(lcTests, "qt.pdf.tests")
+
class tst_QPdfSearchModel: public QObject
{
Q_OBJECT
@@ -15,20 +17,51 @@ public:
tst_QPdfSearchModel() {}
private slots:
+ void findText_data();
void findText();
};
+void tst_QPdfSearchModel::findText_data()
+{
+ QTest::addColumn<QString>("pdfPath");
+ QTest::addColumn<QString>("searchString");
+ QTest::addColumn<int>("expectedMatchCount");
+ QTest::addColumn<int>("matchIndexToCheck");
+ QTest::addColumn<int>("expectedRectangleCount");
+ QTest::addColumn<int>("rectIndexToCheck");
+ QTest::addColumn<QRect>("expectedMatchBounds");
+
+ QTest::newRow("the search for ai") << QFINDTESTDATA("test.pdf")
+ << "ai" << 3 << 0 << 1 << 0 << QRect(321, 202, 9, 11);
+ QTest::newRow("rotated text") << QFINDTESTDATA("rotated_text.pdf")
+ << "world!" << 2 << 0 << 1 << 0 << QRect(76, 102, 26, 28);
+ QTest::newRow("displaced text") << QFINDTESTDATA("tagged_mcr_multipage.pdf")
+ << "1" << 1 << 0 << 1 << 0 << QRect(34, 22, 3, 8);
+}
+
void tst_QPdfSearchModel::findText()
{
+ QFETCH(QString, pdfPath);
+ QFETCH(QString, searchString);
+ QFETCH(int, expectedMatchCount);
+ QFETCH(int, matchIndexToCheck);
+ QFETCH(int, expectedRectangleCount);
+ QFETCH(int, rectIndexToCheck);
+ QFETCH(QRect, expectedMatchBounds);
+
QPdfDocument document;
- QCOMPARE(document.load(QFINDTESTDATA("test.pdf")), QPdfDocument::NoError);
+ QCOMPARE(document.load(pdfPath), QPdfDocument::Error::None);
QPdfSearchModel model;
model.setDocument(&document);
- QList<QRectF> matches = model.matches(1, "ai");
+ model.setSearchString(searchString);
- qDebug() << matches;
- QCOMPARE(matches.count(), 3);
+ QTRY_COMPARE(model.count(), expectedMatchCount); // wait for the timer
+ QPdfLink match = model.resultAtIndex(matchIndexToCheck);
+ qCDebug(lcTests) << match;
+ QList<QRectF> rects = match.rectangles();
+ QCOMPARE(rects.size(), expectedRectangleCount);
+ QCOMPARE(rects.at(rectIndexToCheck).toRect(), expectedMatchBounds);
}
QTEST_MAIN(tst_QPdfSearchModel)
diff --git a/tests/auto/pdfquick/CMakeLists.txt b/tests/auto/pdfquick/CMakeLists.txt
index 9f79459b2..e6a3a460c 100644
--- a/tests/auto/pdfquick/CMakeLists.txt
+++ b/tests/auto/pdfquick/CMakeLists.txt
@@ -1 +1,2 @@
add_subdirectory(multipageview)
+add_subdirectory(pdfpageimage)
diff --git a/tests/auto/pdfquick/multipageview/BLACKLIST b/tests/auto/pdfquick/multipageview/BLACKLIST
index b608bef1c..9012902f6 100644
--- a/tests/auto/pdfquick/multipageview/BLACKLIST
+++ b/tests/auto/pdfquick/multipageview/BLACKLIST
@@ -1,3 +1,7 @@
-# QTBUG-106072
-[password]
-windows
+# QTBUG-111306
+[navigation:click links and go back, twice]
+android
+
+# QTBUG-111306
+[navigation:click two links in series and then go back]
+android
diff --git a/tests/auto/pdfquick/multipageview/CMakeLists.txt b/tests/auto/pdfquick/multipageview/CMakeLists.txt
index 283bebc79..50f7d7d8f 100644
--- a/tests/auto/pdfquick/multipageview/CMakeLists.txt
+++ b/tests/auto/pdfquick/multipageview/CMakeLists.txt
@@ -20,11 +20,11 @@ qt_internal_add_test(tst_multipageview
qt_internal_extend_target(tst_multipageview CONDITION ANDROID OR IOS
DEFINES
- QT_QMLTEST_DATADIR=\\\":/data\\\"
+ QT_QMLTEST_DATADIR=":/data"
)
qt_internal_extend_target(tst_multipageview CONDITION NOT ANDROID AND NOT IOS
DEFINES
- QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\"
+ QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/data"
)
diff --git a/tests/auto/pdfquick/multipageview/data/jumpOnDocumentReady.qml b/tests/auto/pdfquick/multipageview/data/jumpOnDocumentReady.qml
new file mode 100644
index 000000000..ce74f5ed8
--- /dev/null
+++ b/tests/auto/pdfquick/multipageview/data/jumpOnDocumentReady.qml
@@ -0,0 +1,18 @@
+import QtQuick
+import QtQuick.Pdf
+
+PdfMultiPageView {
+ id: view
+ width: 480
+ height: 480
+
+ document: PdfDocument {
+ id: document
+ onStatusChanged: {
+ if(status === PdfDocument.Ready)
+ view.goToPage(2)
+ }
+ }
+
+ Component.onCompleted: document.source = "bookmarksAndLinks.pdf"
+}
diff --git a/tests/auto/pdfquick/multipageview/tst_multipageview.cpp b/tests/auto/pdfquick/multipageview/tst_multipageview.cpp
index 48cec928f..c5e0b30db 100644
--- a/tests/auto/pdfquick/multipageview/tst_multipageview.cpp
+++ b/tests/auto/pdfquick/multipageview/tst_multipageview.cpp
@@ -6,12 +6,15 @@
#include <QtCore/QLoggingCategory>
#include <QtGui/QClipboard>
#include <QtGui/QPointingDevice>
+#include <QtGui/QStyleHints>
#include <QtQuick/QQuickView>
#include <QtPdfQuick/private/qquickpdflinkmodel_p.h>
#include <QtPdfQuick/private/qquickpdfsearchmodel_p.h>
#include <QtPdfQuick/private/qquickpdfpageimage_p.h>
#include "../shared/util.h"
+using namespace Qt::StringLiterals;
+
Q_LOGGING_CATEGORY(lcTests, "qt.pdf.tests")
class tst_MultiPageView : public QQuickDataTest
@@ -26,6 +29,8 @@ private Q_SLOTS:
void password();
void selectionAndClipboard();
void search();
+ void pinchDragPinch();
+ void jumpOnDocumentReady();
public:
enum NavigationAction {
@@ -57,7 +62,7 @@ void tst_MultiPageView::internalLink_data()
QTest::addColumn<qreal>("expectedZoom");
QTest::addColumn<QPoint>("expectedScroll");
- QTest::newRow("first link") << 0 << 1 << qreal(1) << QPoint(134, 1276);
+ QTest::newRow("first link") << 0 << 1 << qreal(1) << QPoint(134, 1286);
// TODO fails because it zooms out, and the view leaves gaps between pages currently
// QTest::newRow("second link") << 1 << 2 << qreal(0.5) << QPoint(0, 717);
}
@@ -73,7 +78,7 @@ void tst_MultiPageView::internalLink()
QVERIFY(showView(window, testFileUrl("multiPageView.qml")));
QQuickItem *pdfView = window.rootObject();
QVERIFY(pdfView);
- pdfView->setProperty("source", "bookmarksAndLinks.pdf");
+ pdfView->setProperty("source", testFileUrl("bookmarksAndLinks.pdf"));
QTRY_COMPARE(pdfView->property("currentPageRenderingStatus").toInt(), QQuickPdfPageImage::Ready);
QQuickItem *table = static_cast<QQuickItem *>(findFirstChild(pdfView, "QQuickTableView"));
@@ -257,7 +262,7 @@ void tst_MultiPageView::password()
// actual QPdfDocument::pageCountChanged(int), for comparison with the illusory QQuickPdfDocument::pageCountChanged
QVERIFY(extPageCountChangedSpy.isValid());
- QVERIFY(pdfView->setProperty("source", u"pdf-sample.protected.pdf"_qs));
+ QVERIFY(pdfView->setProperty("source", testFileUrl(u"pdf-sample.protected.pdf"_s)));
QTRY_COMPARE(passwordRequiredSpy.size(), 1);
qCDebug(lcTests) << "error while awaiting password" << doc->error()
@@ -268,7 +273,7 @@ void tst_MultiPageView::password()
QCOMPARE(extPageCountChangedSpy.size(), 0);
QCOMPARE(statusChangedSpy.size(), 2); // Loading and then Error
statusChangedSpy.clear();
- QVERIFY(doc->setProperty("password", u"Qt"_qs));
+ QVERIFY(doc->setProperty("password", u"Qt"_s));
QCOMPARE(passwordChangedSpy.size(), 1);
QTRY_COMPARE(doc->property("status").toInt(), int(QPdfDocument::Status::Ready));
qCDebug(lcTests) << "after setPassword" << doc->error()
@@ -288,8 +293,8 @@ void tst_MultiPageView::selectionAndClipboard()
QVERIFY(pdfView);
QQuickPdfDocument *doc = pdfView->property("document").value<QQuickPdfDocument*>();
QVERIFY(doc);
- QVERIFY(doc->setProperty("password", u"Qt"_qs));
- QVERIFY(pdfView->setProperty("source", u"pdf-sample.protected.pdf"_qs));
+ QVERIFY(doc->setProperty("password", u"Qt"_s));
+ QVERIFY(pdfView->setProperty("source", testFileUrl((u"pdf-sample.protected.pdf"_s))));
QTRY_COMPARE(pdfView->property("currentPageRenderingStatus").toInt(), QQuickPdfPageImage::Ready);
QVERIFY(QMetaObject::invokeMethod(pdfView, "selectAll"));
@@ -316,8 +321,8 @@ void tst_MultiPageView::search()
QTRY_COMPARE(pdfView->width(), 200);
QQuickPdfDocument *doc = pdfView->property("document").value<QQuickPdfDocument*>();
QVERIFY(doc);
- QVERIFY(doc->setProperty("password", u"Qt"_qs));
- QVERIFY(pdfView->setProperty("source", u"pdf-sample.protected.pdf"_qs));
+ QVERIFY(doc->setProperty("password", u"Qt"_s));
+ QVERIFY(pdfView->setProperty("source", testFileUrl(u"pdf-sample.protected.pdf"_s)));
QTRY_COMPARE(pdfView->property("currentPageRenderingStatus").toInt(), QQuickPdfPageImage::Ready);
QPdfSearchModel *searchModel = pdfView->property("searchModel").value<QPdfSearchModel*>();
QVERIFY(searchModel);
@@ -328,7 +333,7 @@ void tst_MultiPageView::search()
QObject *multiline = findFirstChild(firstPage, "QQuickPathMultiline");
QVERIFY(multiline);
- pdfView->setProperty("searchString", u"PDF"_qs);
+ pdfView->setProperty("searchString", u"PDF"_s);
QTRY_COMPARE(searchModel->rowCount(QModelIndex()), 7); // occurrences of the word "PDF" in this file
const int count = searchModel->rowCount(QModelIndex());
QList<QList<QPointF>> resultOutlines = multiline->property("paths").value<QList<QList<QPointF>>>();
@@ -353,5 +358,89 @@ void tst_MultiPageView::search()
QVERIFY(movements > 4);
}
+void tst_MultiPageView::pinchDragPinch()
+{
+ qputenv("QML_NO_TOUCH_COMPRESSION", "1");
+ QQuickView window;
+ QVERIFY(showView(window, testFileUrl("multiPageView.qml")));
+ QQuickItem *pdfView = window.rootObject();
+ QVERIFY(pdfView);
+ pdfView->setProperty("source", testFileUrl("bookmarksAndLinks.pdf"));
+ QTRY_COMPARE(pdfView->property("currentPageRenderingStatus").toInt(), QQuickPdfPageImage::Ready);
+ QQuickItem *table = static_cast<QQuickItem *>(findFirstChild(pdfView, "QQuickTableView"));
+ QVERIFY(table);
+ QQuickItem *firstPage = tableViewItemAtCell(table, 0, 0);
+ QVERIFY(firstPage);
+ QQuickItem *paper = firstPage->childAt(10, 10);
+ QVERIFY(paper);
+ QQuickPdfPageImage *image = firstPage->findChild<QQuickPdfPageImage *>();
+ QVERIFY(image);
+
+ auto pinch = [&window, paper, this]() {
+ const int threshold = QGuiApplication::styleHints()->startDragDistance();
+ const int movement = 100;
+ QCOMPARE_GT(movement, threshold);
+ const qreal initialScale = paper->scale();
+ QPoint p0(100, 200);
+ QPoint p1(200, 200);
+ QTest::QTouchEventSequence seq = QTest::touchEvent(&window, touchscreen.get());
+ seq.press(0, p0, &window).commit();
+ seq.stationary(0).press(1, p1, &window).commit();
+ p1.setX(p1.x() + movement);
+ QSignalSpy frameSwappedSpy(&window, &QQuickWindow::frameSwapped);
+ seq.stationary(0).move(1, p1, &window).commit();
+ // after a frame is rendered, the PinchHandler ought to be active
+ // (but verifying it would require private API)
+ QTRY_VERIFY(frameSwappedSpy.size() > 0);
+ QTRY_COMPARE(paper->scale(), initialScale);
+
+ for (int i = 1; i <= 2; ++i) {
+ p1.setX(p1.x() + movement);
+ seq.stationary(0).move(1, p1, &window).commit();
+ QTRY_COMPARE(paper->scale(), initialScale + i * 0.5);
+ }
+ seq.release(0, p0, &window).release(1, p1, &window).commit();
+ };
+
+ auto drag = [&window, table, this]() {
+ const int movement = 100;
+ QPoint p0(200, 200);
+ QTest::QTouchEventSequence seq = QTest::touchEvent(&window, touchscreen.get());
+ seq.press(0, p0, &window).commit();
+ p0.setY(p0.y() + movement);
+ seq.move(0, p0, &window).commit();
+ p0.setY(p0.y() + movement);
+ seq.move(0, p0, &window).commit();
+ seq.release(0, p0, &window).commit();
+ QTRY_COMPARE(table->property("moving"), false);
+ };
+
+ pinch();
+ qCDebug(lcTests) << "new scale" << pdfView->property("renderScale").toReal();
+ QTRY_COMPARE(pdfView->property("renderScale").toReal(), 2);
+
+ drag();
+ QCOMPARE(pdfView->property("renderScale").toReal(), 2);
+
+ pinch();
+ qCDebug(lcTests) << "new scale" << pdfView->property("renderScale").toReal();
+ QTRY_COMPARE(pdfView->property("renderScale").toReal(), 4);
+
+ // wait for rendering to be done before we exit: if we delete the document
+ // prematurely, QPdfIOHandler might access a dangling pointer
+ QTRY_COMPARE(image->status(), QQuickPdfPageImage::Ready);
+}
+
+void tst_MultiPageView::jumpOnDocumentReady() // QTBUG-119416
+{
+ QQuickView window;
+ QVERIFY(showView(window, testFileUrl("jumpOnDocumentReady.qml")));
+ QQuickItem *pdfView = window.rootObject();
+ QVERIFY(pdfView);
+
+ // QML calls view.goToPage(2): verify that it eventually happens
+ QTRY_COMPARE(pdfView->property("currentPage").toInt(), 2);
+}
+
QTEST_MAIN(tst_MultiPageView)
#include "tst_multipageview.moc"
diff --git a/tests/auto/pdfquick/pdfpageimage/CMakeLists.txt b/tests/auto/pdfquick/pdfpageimage/CMakeLists.txt
new file mode 100644
index 000000000..da67d8721
--- /dev/null
+++ b/tests/auto/pdfquick/pdfpageimage/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Collect test data
+file(GLOB_RECURSE test_data_glob
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ data/*)
+list(APPEND test_data ${test_data_glob})
+
+qt_internal_add_test(tst_pdfpageimage
+ SOURCES
+ tst_pdfpageimage.cpp
+ ../shared/util.cpp ../shared/util.h
+ LIBRARIES
+ Qt::Gui
+ Qt::Quick
+ Qt::PdfQuickPrivate
+ TESTDATA ${test_data}
+)
+
+## Scopes:
+#####################################################################
+
+qt_internal_extend_target(tst_pdfpageimage CONDITION ANDROID OR IOS
+ DEFINES
+ QT_QMLTEST_DATADIR=":/data"
+)
+
+qt_internal_extend_target(tst_pdfpageimage CONDITION NOT ANDROID AND NOT IOS
+ DEFINES
+ QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/data"
+)
+
diff --git a/tests/auto/pdfquick/pdfpageimage/data/bookmarksAndLinks.pdf b/tests/auto/pdfquick/pdfpageimage/data/bookmarksAndLinks.pdf
new file mode 100644
index 000000000..aa0b99039
--- /dev/null
+++ b/tests/auto/pdfquick/pdfpageimage/data/bookmarksAndLinks.pdf
@@ -0,0 +1,317 @@
+%PDF-1.7
+
+1 0 obj
+<<
+ /Type /Catalog
+ /PageMode /FullScreen
+ /Outlines 6 0 R
+ /Pages 2 0 R
+ /Names 50 0 R
+ /PageLabels 23 0 R
+ /ViewerPreferences<</NonFullScreenPageMode (UseThumbs)>>
+>>
+endobj
+
+50 0 obj
+<<
+ /Dests <</Names [ (ToTest2) [4 0 R /XYZ 300 300 1] (ToTest3) [5 0 R /XYZ 290 10 0.5] (ToTest1) [3 0 R /XYZ 600 800 1] ]>>
+>>
+endobj
+
+23 0 obj
+<<
+ /Nums [0 <</S /D /P(test )>> 3 <</S /A >> 4<</S /R/St >> 5<</S /r/St >> ]
+ /Limits [0 5]
+>>
+endobj
+
+2 0 obj
+<<
+ /Type /Pages
+ /Kids [3 0 R 4 0 R 5 0 R]
+ /Count 3
+>>
+endobj
+
+3 0 obj
+<<
+ /Type /Page
+ /Parent 2 0 R
+ /MediaBox [0 10 600 800]
+ /Annots [24 0 R 25 0 R]
+ /Contents 16 0 R
+ /Resources <<
+ /Font <</F1 18 0 R>>
+ >>
+>>
+endobj
+
+24 0 obj
+<<
+ /Subtype /Link
+ /Border [0 0 0]
+ /Dest (ToTest2)
+ /A << /Type /Action
+ /S /GoTo
+ /D [5 0 R /FitR ¨C4 399 199 533]
+ >>
+ /Rect [10 690 150 720]
+
+>>
+endobj
+
+25 0 obj
+<<
+ /Subtype /Link
+ /Border [0 0 0]
+ /Dest (ToTest3)
+ /Rect [10 630 150 650]
+>>
+endobj
+
+
+16 0 obj
+<< /Length 0 >>
+ stream
+ BT
+ /F1 72 Tf
+ 200 200 TD
+ 0 0 1 RG
+ 5 Tr
+ (Test_1) Tj
+ 0 800 m
+ 600 0 l S
+ /F1 30 Tf
+ 0 1 0 RG
+ 1 Tr
+ -190 490 TD
+ (GO Test_2) Tj
+ 0 -50 TD
+ 5 w
+ 2 Tr
+ 1 0 0 RG
+ (GO Test_3) Tj
+ ET
+ endstream
+endobj
+
+
+endobj
+
+18 0 obj
+<<
+ /Type /Font
+ /Subtype /Type1
+ /Name /F1
+ /BaseFont /Helvetica
+>>
+endobj
+
+4 0 obj
+<<
+ /Type /Page
+ /Parent 2 0 R
+ /MediaBox [10 0 500 700]
+ /Annots [60 0 R]
+ /Contents 19 0 R
+ /Resources <<
+ /Font <</F2 20 0 R>>
+ >>
+>>
+endobj
+
+19 0 obj
+<< /Length 0 >>
+stream
+BT
+ 1 -0.7 0 1 30 100 cm
+ /F2 50 Tf
+ 10 50 TD
+ (TEST_2) Tj
+
+ 1 0.7 0 1 -30 -100 cm
+ /F2 25 Tf
+ 1 0 1 RG
+ 7 w
+ 100 60 TD
+
+ (GO Test_1) Tj
+ 100 100 140 40 re S f
+ET
+endstream
+endobj
+
+20 0 obj
+<<
+ /Type /Font
+ /Subtype /TrueType
+ /Name /F2
+ /BaseFont /NewYork , Bold
+ /FirstChar 0
+ /LastChar 255
+ /Widths 23 0 R
+ /FontDescriptor 7 0 R
+ /Encoding /MacRomanEncoding
+>>
+endobj
+
+60 0 obj
+<<
+ /Subtype /Link
+ /Border [0 0 0]
+ /Dest (ToTest1)
+ /Rect [110 110 230 150]
+>>
+endobj
+
+5 0 obj
+<<
+ /Type /Page
+ /Parent 2 0 R
+ /MediaBox [-10 -10 400 600]
+ /Annots [61 0 R]
+ /Contents 21 0 R
+ /Resources << /Font <</F3 22 0 R>> >>
+>>
+endobj
+
+21 0 obj
+<< /Length 0 >>
+stream
+BT
+ /F3 30 Tf
+ 290 10 TD
+ (TEST_3) Tj
+ -50 90 TD
+ (GO Test_2)Tj
+ET
+endstream
+endobj
+
+22 0 obj
+<<
+ /Type /Font
+ /Subtype /Type1
+ /Name /F3
+ /BaseFont /Courier-Bold
+>>
+endobj
+
+61 0 obj
+<<
+ /Subtype /Link
+ /Border [0 0 0]
+ /Dest (ToTest2)
+ /Rect [240 90 400 130]
+>>
+
+6 0 obj
+<<
+ /Type /Outlines
+ /First 7 0 R
+ /Last 11 0 R
+ /Count 4 0 R
+>>
+endobj
+
+7 0 obj
+<<
+ /Title (First)
+ /Parent 6 0 R
+ /Next 8 0 R
+ /C [1 0 0]
+ /Dest [ 3 0 R /XYZ 600 800 0.5 ]
+>>
+endobj
+
+8 0 obj
+<<
+ /Title (Second)
+ /Parent 6 0 R
+ /Prev 7 0 R
+ /Next 9 0 R
+ /C [0 1 0]
+ % /Dest [ 4 0 R /XYZ 500 700 null ]
+/Dest (ToTest2)
+>>
+endobj
+
+9 0 obj
+<<
+ /Title (Third)
+ /Parent 6 0 R
+ /Prev 8 0 R
+ /Next 10 0 R
+ /C [0 0 1]
+ /Dest [ 5 0 R /XYZ 400 600 0.8 ]
+>>
+endobj
+
+10 0 obj
+<<
+ /Title (Fourth)
+ /Parent 6 0 R
+ /Prev 9 0 R
+ /Next 11 0 R
+>>
+endobj
+
+11 0 obj
+<<
+ /Title (Fivth)
+ /Parent 6 0 R
+ /Prev 10 0 R
+ /First 12 0 R
+ /Last 15 0 R
+ /Count 4
+>>
+endobj
+
+12 0 obj
+<<
+ /Title (Fivth_1)
+ /Parent 11 0 R
+ /Next 13 0 R
+>>
+endobj
+
+13 0 obj
+<<
+ /Title (Fivth_2)
+ /Parent 11 0 R
+ /Prev 12 0 R
+ /Next 14 0 R
+>>
+endobj
+
+14 0 obj
+<<
+ /Title (Fivth_3)
+ /Parent 11 0 R
+ /Prev 13 0 R
+ /Next 15 0 R
+>>
+endobj
+
+15 0 obj
+<<
+ /Title (Fivth_4)
+ /Parent 11 0 R
+ /Prev 14 0 R
+>>
+endobj
+
+
+
+
+xref
+0000000000 65536 f
+
+trailer
+<<
+ /Size 0
+ /Root 1 0 R
+>>
+startxref
+0
+%%EOF
diff --git a/tests/auto/pdfquick/pdfpageimage/data/pdfPageImage.qml b/tests/auto/pdfquick/pdfpageimage/data/pdfPageImage.qml
new file mode 100644
index 000000000..a268bf14b
--- /dev/null
+++ b/tests/auto/pdfquick/pdfpageimage/data/pdfPageImage.qml
@@ -0,0 +1,16 @@
+import QtQuick
+import QtQuick.Pdf
+
+Item {
+ width: 320
+ height: 320
+
+ PdfDocument {
+ id: doc
+ source: "bookmarksAndLinks.pdf"
+ }
+
+ PdfPageImage {
+ anchors.centerIn: parent
+ }
+}
diff --git a/tests/auto/pdfquick/pdfpageimage/tst_pdfpageimage.cpp b/tests/auto/pdfquick/pdfpageimage/tst_pdfpageimage.cpp
new file mode 100644
index 000000000..d2c9c8709
--- /dev/null
+++ b/tests/auto/pdfquick/pdfpageimage/tst_pdfpageimage.cpp
@@ -0,0 +1,131 @@
+// Copyright (C) 2024 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 <QRegularExpression>
+#include <QSignalSpy>
+#include <QTest>
+#include <QtQuick/QQuickView>
+#include <QtPdfQuick/private/qquickpdfdocument_p.h>
+#include <QtPdfQuick/private/qquickpdfpageimage_p.h>
+#include "../shared/util.h"
+
+using namespace Qt::StringLiterals;
+
+Q_LOGGING_CATEGORY(lcTests, "qt.pdf.tests")
+
+// #define DEBUG_WRITE_OUTPUT
+
+class tst_PdfPageImage : public QQuickDataTest
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void settableProperties_data();
+ void settableProperties();
+
+public:
+ enum Property {
+ Source = 0x01,
+ Document = 0x02,
+ SourceSize = 0x04,
+ SourceClipRect = 0x08,
+ MipMap = 0x10,
+ AutoTransform = 0x20,
+ Asynchronous = 0x40,
+ NoCache = 0x80,
+ Mirror = 0x100,
+ MirrorVertically = 0x200,
+ ColorSpace = 0x400,
+ };
+ Q_DECLARE_FLAGS(Properties, Property)
+ Q_FLAG(Properties)
+
+private:
+#ifdef DEBUG_WRITE_OUTPUT
+ QTemporaryDir m_tmpDir;
+#endif
+};
+
+void tst_PdfPageImage::settableProperties_data()
+{
+ QTest::addColumn<tst_PdfPageImage::Properties>("toSet");
+ QTest::addColumn<QSize>("expectedSize");
+ QTest::addColumn<QRegularExpression>("expectedWarning");
+
+ const QRegularExpression NoWarning;
+ const qreal dpr = qGuiApp->devicePixelRatio();
+
+ QTest::newRow("source") << Properties(Source) << (QSizeF(600, 790) * dpr).toSize()
+ << QRegularExpression("document property not set: falling back to inefficient loading"); // QTBUG-104767
+ QTest::newRow("document") << Properties(Document) << QSize(600, 790) << NoWarning;
+ QTest::newRow("source and document") << Properties(Source | Document) << QSize(600, 790)
+ << QRegularExpression("document and source properties in conflict");
+ QTest::newRow("document and sourceSize") << Properties(Document | SourceSize) << QSize(100, 100) << NoWarning;
+ QTest::newRow("document and sourceClipRect") << Properties(Document | SourceClipRect) << QSize(100, 100) << NoWarning;
+ QTest::newRow("document and autoTransform") << Properties(Document | AutoTransform) << QSize(600, 790) << NoWarning;
+ QTest::newRow("document and async") << Properties(Document | Asynchronous) << QSize(600, 790) << NoWarning;
+ QTest::newRow("document and nocache") << Properties(Document | NoCache) << QSize(600, 790) << NoWarning;
+ QTest::newRow("document and mirror") << Properties(Document | Mirror) << QSize(600, 790) << NoWarning;
+ QTest::newRow("document and mirrorVertically") << Properties(Document | MirrorVertically) << QSize(600, 790) << NoWarning;
+ QTest::newRow("document and colorSpace") << Properties(Document | ColorSpace) << QSize(600, 790) << NoWarning;
+}
+
+void tst_PdfPageImage::settableProperties()
+{
+ QFETCH(tst_PdfPageImage::Properties, toSet);
+ QFETCH(QSize, expectedSize);
+ QFETCH(QRegularExpression, expectedWarning);
+
+ QQuickView window;
+ if (!expectedWarning.pattern().isEmpty())
+ QTest::ignoreMessage(QtWarningMsg, expectedWarning);
+ QVERIFY(showView(window, testFileUrl("pdfPageImage.qml")));
+ QQuickPdfPageImage *pdfImage = window.rootObject()->findChild<QQuickPdfPageImage *>();
+ QVERIFY(pdfImage);
+ QQuickPdfDocument *doc = window.rootObject()->findChild<QQuickPdfDocument *>();
+ QVERIFY(doc);
+ if (toSet.testFlag(Document))
+ pdfImage->setDocument(doc);
+ if (toSet.testFlag(Source))
+ pdfImage->setSource(doc->source());
+ if (toSet.testFlag(SourceSize))
+ pdfImage->setSourceSize({100, 100});
+ if (toSet.testFlag(SourceClipRect))
+ pdfImage->setSourceClipRect({100, 100, 100, 100});
+ if (toSet.testFlag(MipMap))
+ pdfImage->setMipmap(true);
+ if (toSet.testFlag(AutoTransform))
+ pdfImage->setAutoTransform(true);
+ if (toSet.testFlag(Asynchronous)) {
+ QCOMPARE(pdfImage->asynchronous(), false);
+ // test the opposite of the default
+ pdfImage->setAsynchronous(true);
+ }
+ if (toSet.testFlag(NoCache)) {
+ QCOMPARE(pdfImage->cache(), true);
+ // test the opposite of the default
+ pdfImage->setCache(false);
+ }
+ if (toSet.testFlag(Mirror)) {
+ QCOMPARE(pdfImage->mirror(), false);
+ pdfImage->setMirror(true);
+ }
+ if (toSet.testFlag(MirrorVertically)) {
+ QCOMPARE(pdfImage->mirrorVertically(), false);
+ pdfImage->setMirrorVertically(true);
+ }
+ if (toSet.testFlag(ColorSpace))
+ pdfImage->setColorSpace(QColorSpace::ProPhotoRgb);
+ QTRY_COMPARE(pdfImage->status(), QQuickPdfPageImage::Ready);
+ const QImage img = pdfImage->image();
+ QCOMPARE(img.size(), expectedSize);
+#ifdef DEBUG_WRITE_OUTPUT
+ m_tmpDir.setAutoRemove(false);
+ const auto path = m_tmpDir.filePath(QString::fromLocal8Bit(QTest::currentDataTag()) + ".png");
+ qCDebug(lcTests) << "saving to" << path;
+ img.save(path);
+#endif
+}
+
+QTEST_MAIN(tst_PdfPageImage)
+#include "tst_pdfpageimage.moc"
diff --git a/tests/auto/quick/inspectorserver/tst_inspectorserver.cpp b/tests/auto/quick/inspectorserver/tst_inspectorserver.cpp
index 58e8f7d11..a9638bee4 100644
--- a/tests/auto/quick/inspectorserver/tst_inspectorserver.cpp
+++ b/tests/auto/quick/inspectorserver/tst_inspectorserver.cpp
@@ -9,6 +9,7 @@
#include <QtTest/QtTest>
#include <QQuickWebEngineProfile>
#include <QtWebEngineQuick/private/qquickwebengineview_p.h>
+#include <QWebEnginePage>
#define INSPECTOR_SERVER_PORT "23654"
static const QUrl s_inspectorServerHttpBaseUrl("http://localhost:" INSPECTOR_SERVER_PORT);
@@ -22,6 +23,7 @@ private Q_SLOTS:
void init();
void cleanup();
+ void testDevToolsId();
void testPageList();
void testRemoteDebuggingMessage();
void openRemoteDebuggingSession();
@@ -36,6 +38,7 @@ private:
tst_InspectorServer::tst_InspectorServer()
{
+ qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--remote-allow-origins=*");
qputenv("QTWEBENGINE_REMOTE_DEBUGGING", INSPECTOR_SERVER_PORT);
QtWebEngineQuick::initialize();
QQuickWebEngineProfile::defaultProfile()->setOffTheRecord(true);
@@ -78,7 +81,7 @@ inline QQuickWebEngineView* tst_InspectorServer::webView() const
QJsonArray tst_InspectorServer::fetchPageList() const
{
QNetworkAccessManager qnam;
- QSignalSpy spy(&qnam, &QNetworkAccessManager::finished);;
+ QSignalSpy spy(&qnam, &QNetworkAccessManager::finished);
QNetworkRequest request(s_inspectorServerHttpBaseUrl.resolved(QUrl("json/list")));
QScopedPointer<QNetworkReply> reply(qnam.get(request));
spy.wait();
@@ -90,6 +93,21 @@ QJsonArray tst_InspectorServer::fetchPageList() const
return QJsonDocument::fromJson(reply->readAll()).array();
}
+void tst_InspectorServer::testDevToolsId()
+{
+ const QUrl testPageUrl = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
+ + QLatin1String("/html/basic_page.html"));
+ QSignalSpy loadSpy(webView(), SIGNAL(loadingChanged(QWebEngineLoadingInfo)));
+ webView()->setUrl(testPageUrl);
+ QTRY_VERIFY_WITH_TIMEOUT(loadSpy.size() && !webView()->isLoading(), 10000);
+
+ // Our page should be the only one in the list.
+ QJsonArray pageList = fetchPageList();
+ QCOMPARE(pageList.size(), 1);
+ QCOMPARE(testPageUrl.toString(), pageList.at(0).toObject().value("url").toString());
+ QCOMPARE(webView()->devToolsId(), pageList.at(0).toObject().value("id").toString());
+}
+
void tst_InspectorServer::testPageList()
{
const QUrl testPageUrl = QUrl::fromLocalFile(QDir(QT_TESTCASE_SOURCEDIR).canonicalPath()
diff --git a/tests/auto/quick/publicapi/tst_publicapi.cpp b/tests/auto/quick/publicapi/tst_publicapi.cpp
index 7e4acc97c..990b1de4f 100644
--- a/tests/auto/quick/publicapi/tst_publicapi.cpp
+++ b/tests/auto/quick/publicapi/tst_publicapi.cpp
@@ -10,6 +10,7 @@
#include <QtTest/QtTest>
#include <QtWebEngineQuick/QQuickWebEngineProfile>
#include <QtWebEngineCore/QWebEngineCertificateError>
+#include <QtWebEngineCore/QWebEngineDesktopMediaRequest>
#include <QtWebEngineCore/QWebEngineFileSystemAccessRequest>
#include <QtWebEngineCore/QWebEngineFindTextResult>
#include <QtWebEngineCore/QWebEngineFullScreenRequest>
@@ -23,6 +24,8 @@
#include <QtWebEngineCore/QWebEngineDownloadRequest>
#include <QtWebEngineCore/QWebEngineScript>
#include <QtWebEngineCore/QWebEngineLoadingInfo>
+#include <QtWebEngineCore/QWebEngineWebAuthUxRequest>
+#include <QtWebEngineCore/QWebEngineFrame>
#include <private/qquickwebengineview_p.h>
#include <private/qquickwebengineaction_p.h>
#include <private/qquickwebengineclientcertificateselection_p.h>
@@ -61,18 +64,25 @@ static const QList<const QMetaObject *> typesToCheck = QList<const QMetaObject *
<< &QQuickWebEngineTooltipRequest::staticMetaObject
<< &QWebEngineContextMenuRequest::staticMetaObject
<< &QWebEngineCertificateError::staticMetaObject
+ << &QWebEngineDesktopMediaRequest::staticMetaObject
<< &QWebEngineFileSystemAccessRequest::staticMetaObject
<< &QWebEngineFindTextResult::staticMetaObject
<< &QWebEngineLoadingInfo::staticMetaObject
+ << &QAbstractListModel::staticMetaObject
<< &QWebEngineNavigationRequest::staticMetaObject
<< &QWebEngineNewWindowRequest::staticMetaObject
<< &QWebEngineNotification::staticMetaObject
<< &QWebEngineQuotaRequest::staticMetaObject
<< &QWebEngineRegisterProtocolHandlerRequest::staticMetaObject
<< &QQuickWebEngineTouchSelectionMenuRequest::staticMetaObject
+ << &QWebEngineWebAuthUxRequest::staticMetaObject
+ << &QWebEngineWebAuthPinRequest::staticMetaObject
+ << &QWebEngineFrame::staticMetaObject
;
-static QList<QMetaEnum> knownEnumNames = QList<QMetaEnum>();
+static QList<QMetaEnum> knownEnumNames = QList<QMetaEnum>()
+ << QWebEngineDownloadRequest::staticMetaObject.enumerator(QWebEngineDownloadRequest::staticMetaObject.indexOfEnumerator("SavePageFormat"))
+ ;
static const QStringList hardcodedTypes = QStringList()
<< "QJSValue"
@@ -84,7 +94,8 @@ static const QStringList hardcodedTypes = QStringList()
<< "QWebEngineCookieStore*"
<< "Qt::LayoutDirection"
<< "QQuickWebEngineScriptCollection*"
- << "QQmlComponent*";
+ << "QQmlComponent*"
+ << "QMultiMap<QByteArray,QByteArray>";
static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineAction.text --> QString"
@@ -123,6 +134,7 @@ static const QStringList expectedAPI = QStringList()
<< "QWebEngineCertificateError.defer() --> void"
<< "QWebEngineCertificateError.description --> QString"
<< "QWebEngineCertificateError.type --> QWebEngineCertificateError::Type"
+ << "QWebEngineCertificateError.isMainFrame --> bool"
<< "QWebEngineCertificateError.acceptCertificate() --> void"
<< "QWebEngineCertificateError.overridable --> bool"
<< "QWebEngineCertificateError.rejectCertificate() --> void"
@@ -261,6 +273,11 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineTooltipRequest.text --> QString"
<< "QQuickWebEngineTooltipRequest.type --> QQuickWebEngineTooltipRequest::RequestType"
<< "QQuickWebEngineTooltipRequest.accepted --> bool"
+ << "QWebEngineDesktopMediaRequest.screensModel --> QAbstractListModel*"
+ << "QWebEngineDesktopMediaRequest.windowsModel --> QAbstractListModel*"
+ << "QWebEngineDesktopMediaRequest.selectScreen(QModelIndex) --> void"
+ << "QWebEngineDesktopMediaRequest.selectWindow(QModelIndex) --> void"
+ << "QWebEngineDesktopMediaRequest.cancel() --> void"
<< "QWebEngineFullScreenRequest.accept() --> void"
<< "QWebEngineFullScreenRequest.origin --> QUrl"
<< "QWebEngineFullScreenRequest.reject() --> void"
@@ -293,6 +310,7 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineJavaScriptDialogRequest.title --> QString"
<< "QQuickWebEngineJavaScriptDialogRequest.type --> QQuickWebEngineJavaScriptDialogRequest::DialogType"
<< "QWebEngineLoadingInfo.errorCode --> int"
+ << "QWebEngineLoadingInfo.responseHeaders --> QMultiMap<QByteArray,QByteArray>"
<< "QWebEngineLoadingInfo.errorDomain --> QWebEngineLoadingInfo::ErrorDomain"
<< "QWebEngineLoadingInfo.errorString --> QString"
<< "QWebEngineLoadingInfo.status --> QWebEngineLoadingInfo::LoadStatus"
@@ -313,6 +331,7 @@ static const QStringList expectedAPI = QStringList()
<< "QWebEngineNavigationRequest.action --> QWebEngineNavigationRequest::NavigationRequestAction"
<< "QWebEngineNavigationRequest.actionChanged() --> void"
<< "QWebEngineNavigationRequest.isMainFrame --> bool"
+ << "QWebEngineNavigationRequest.hasFormData --> bool"
<< "QWebEngineNavigationRequest.navigationType --> QWebEngineNavigationRequest::NavigationType"
<< "QWebEngineNavigationRequest.url --> QUrl"
<< "QWebEngineNavigationRequest.AcceptRequest --> NavigationRequestAction"
@@ -344,6 +363,7 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineProfile.cachePath --> QString"
<< "QQuickWebEngineProfile.cachePathChanged() --> void"
<< "QQuickWebEngineProfile.clearHttpCache() --> void"
+ << "QQuickWebEngineProfile.clearHttpCacheCompleted() --> void"
<< "QQuickWebEngineProfile.downloadFinished(QQuickWebEngineDownloadRequest*) --> void"
<< "QQuickWebEngineProfile.downloadRequested(QQuickWebEngineDownloadRequest*) --> void"
<< "QQuickWebEngineProfile.downloadPath --> QString"
@@ -363,8 +383,8 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineProfile.persistentCookiesPolicyChanged() --> void"
<< "QQuickWebEngineProfile.persistentStoragePath --> QString"
<< "QQuickWebEngineProfile.persistentStoragePathChanged() --> void"
- << "QQuickWebEngineProfile.pushServiceEndpoint --> QUrl"
- << "QQuickWebEngineProfile.pushServiceEndpointChanged() --> void"
+ << "QQuickWebEngineProfile.isPushServiceEnabled --> bool"
+ << "QQuickWebEngineProfile.pushServiceEnabledChanged() --> void"
<< "QQuickWebEngineProfile.spellCheckEnabled --> bool"
<< "QQuickWebEngineProfile.spellCheckEnabledChanged() --> void"
<< "QQuickWebEngineProfile.spellCheckLanguages --> QStringList"
@@ -393,6 +413,10 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineSettings.dnsPrefetchEnabledChanged() --> void"
<< "QQuickWebEngineSettings.errorPageEnabled --> bool"
<< "QQuickWebEngineSettings.errorPageEnabledChanged() --> void"
+ << "QQuickWebEngineSettings.forceDarkMode --> bool"
+ << "QQuickWebEngineSettings.forceDarkModeChanged() --> void"
+ << "QQuickWebEngineSettings.scrollAnimatorEnabled --> bool"
+ << "QQuickWebEngineSettings.scrollAnimatorEnabledChanged() --> void"
<< "QQuickWebEngineSettings.focusOnNavigationEnabled --> bool"
<< "QQuickWebEngineSettings.focusOnNavigationEnabledChanged() --> void"
<< "QQuickWebEngineSettings.fullScreenSupportEnabled --> bool"
@@ -439,6 +463,8 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineSettings.webGLEnabledChanged() --> void"
<< "QQuickWebEngineSettings.webRTCPublicInterfacesOnly --> bool"
<< "QQuickWebEngineSettings.webRTCPublicInterfacesOnlyChanged() --> void"
+ << "QQuickWebEngineSettings.readingFromCanvasEnabled --> bool"
+ << "QQuickWebEngineSettings.readingFromCanvasEnabledChanged() --> void"
<< "QQuickWebEngineSingleton.defaultProfile --> QQuickWebEngineProfile*"
<< "QQuickWebEngineSingleton.settings --> QQuickWebEngineSettings*"
<< "QQuickWebEngineSingleton.script() --> QWebEngineScript"
@@ -463,7 +489,6 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineView.action(WebAction) --> QQuickWebEngineAction*"
<< "QQuickWebEngineView.A0 --> PrintedPageSizeId"
<< "QQuickWebEngineView.A1 --> PrintedPageSizeId"
- << "QQuickWebEngineView.A10 --> PrintedPageSizeId"
<< "QQuickWebEngineView.A2 --> PrintedPageSizeId"
<< "QQuickWebEngineView.A3 --> PrintedPageSizeId"
<< "QQuickWebEngineView.A3Extra --> PrintedPageSizeId"
@@ -477,6 +502,7 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineView.A7 --> PrintedPageSizeId"
<< "QQuickWebEngineView.A8 --> PrintedPageSizeId"
<< "QQuickWebEngineView.A9 --> PrintedPageSizeId"
+ << "QQuickWebEngineView.A10 --> PrintedPageSizeId"
<< "QQuickWebEngineView.AbnormalTerminationStatus --> RenderProcessTerminationStatus"
<< "QQuickWebEngineView.AlignCenter --> WebAction"
<< "QQuickWebEngineView.AlignJustified --> WebAction"
@@ -494,7 +520,6 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineView.ArchE --> PrintedPageSizeId"
<< "QQuickWebEngineView.B0 --> PrintedPageSizeId"
<< "QQuickWebEngineView.B1 --> PrintedPageSizeId"
- << "QQuickWebEngineView.B10 --> PrintedPageSizeId"
<< "QQuickWebEngineView.B2 --> PrintedPageSizeId"
<< "QQuickWebEngineView.B3 --> PrintedPageSizeId"
<< "QQuickWebEngineView.B4 --> PrintedPageSizeId"
@@ -504,9 +529,13 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineView.B7 --> PrintedPageSizeId"
<< "QQuickWebEngineView.B8 --> PrintedPageSizeId"
<< "QQuickWebEngineView.B9 --> PrintedPageSizeId"
+ << "QQuickWebEngineView.B10 --> PrintedPageSizeId"
<< "QQuickWebEngineView.Back --> WebAction"
<< "QQuickWebEngineView.C5E --> PrintedPageSizeId"
<< "QQuickWebEngineView.CertificateErrorDomain --> ErrorDomain"
+ << "QQuickWebEngineView.ChangeTextDirectionLTR --> WebAction"
+ << "QQuickWebEngineView.ChangeTextDirectionRTL --> WebAction"
+ << "QQuickWebEngineView.ClipboardReadWrite --> Feature"
<< "QQuickWebEngineView.Comm10E --> PrintedPageSizeId"
<< "QQuickWebEngineView.ConnectionErrorDomain --> ErrorDomain"
<< "QQuickWebEngineView.Copy --> WebAction"
@@ -619,11 +648,10 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineView.LoadStartedStatus --> LoadStatus"
<< "QQuickWebEngineView.LoadStoppedStatus --> LoadStatus"
<< "QQuickWebEngineView.LoadSucceededStatus --> LoadStatus"
+ << "QQuickWebEngineView.LocalFontsAccess --> Feature"
<< "QQuickWebEngineView.MediaAudioCapture --> Feature"
<< "QQuickWebEngineView.MediaAudioVideoCapture --> Feature"
<< "QQuickWebEngineView.MediaVideoCapture --> Feature"
- << "QQuickWebEngineView.NPageSize --> PrintedPageSizeId"
- << "QQuickWebEngineView.NPaperSize --> PrintedPageSizeId"
<< "QQuickWebEngineView.NoErrorDomain --> ErrorDomain"
<< "QQuickWebEngineView.Notifications --> Feature"
<< "QQuickWebEngineView.NoWebAction --> WebAction"
@@ -663,6 +691,7 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineView.ToggleUnderline --> WebAction"
<< "QQuickWebEngineView.Undo --> WebAction"
<< "QQuickWebEngineView.Unselect --> WebAction"
+ << "QQuickWebEngineView.OpenLinkInNewBackgroundTab --> WebAction"
<< "QQuickWebEngineView.ViewSource --> WebAction"
<< "QQuickWebEngineView.WarningMessageLevel --> JavaScriptConsoleMessageLevel"
<< "QQuickWebEngineView.WebActionCount --> WebAction"
@@ -682,11 +711,14 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineView.contentsSize --> QSizeF"
<< "QQuickWebEngineView.contentsSizeChanged(QSizeF) --> void"
<< "QQuickWebEngineView.contextMenuRequested(QWebEngineContextMenuRequest*) --> void"
+ << "QQuickWebEngineView.desktopMediaRequested(QWebEngineDesktopMediaRequest) --> void"
+ << "QQuickWebEngineView.devToolsId --> QString"
<< "QQuickWebEngineView.devToolsView --> QQuickWebEngineView*"
<< "QQuickWebEngineView.devToolsViewChanged() --> void"
- << "QQuickWebEngineView.featurePermissionRequested(QUrl,Feature) --> void"
+ << "QQuickWebEngineView.featurePermissionRequested(QUrl,QQuickWebEngineView::Feature) --> void"
<< "QQuickWebEngineView.fileDialogRequested(QQuickWebEngineFileDialogRequest*) --> void"
<< "QQuickWebEngineView.fileSystemAccessRequested(QWebEngineFileSystemAccessRequest) --> void"
+ << "QQuickWebEngineView.findFrameByName(QString) --> QWebEngineFrame"
<< "QQuickWebEngineView.findText(QString) --> void"
<< "QQuickWebEngineView.findText(QString,FindFlags) --> void"
<< "QQuickWebEngineView.findText(QString,FindFlags,QJSValue) --> void"
@@ -697,7 +729,7 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineView.goBack() --> void"
<< "QQuickWebEngineView.goBackOrForward(int) --> void"
<< "QQuickWebEngineView.goForward() --> void"
- << "QQuickWebEngineView.grantFeaturePermission(QUrl,Feature,bool) --> void"
+ << "QQuickWebEngineView.grantFeaturePermission(QUrl,QQuickWebEngineView::Feature,bool) --> void"
<< "QQuickWebEngineView.history --> QWebEngineHistory*"
<< "QQuickWebEngineView.icon --> QUrl"
<< "QQuickWebEngineView.iconChanged() --> void"
@@ -705,10 +737,10 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineView.inspectedViewChanged() --> void"
<< "QQuickWebEngineView.isFullScreen --> bool"
<< "QQuickWebEngineView.isFullScreenChanged() --> void"
- << "QQuickWebEngineView.javaScriptConsoleMessage(JavaScriptConsoleMessageLevel,QString,int,QString) --> void"
+ << "QQuickWebEngineView.javaScriptConsoleMessage(QQuickWebEngineView::JavaScriptConsoleMessageLevel,QString,int,QString) --> void"
<< "QQuickWebEngineView.javaScriptDialogRequested(QQuickWebEngineJavaScriptDialogRequest*) --> void"
<< "QQuickWebEngineView.lifecycleState --> QQuickWebEngineView::LifecycleState"
- << "QQuickWebEngineView.lifecycleStateChanged(LifecycleState) --> void"
+ << "QQuickWebEngineView.lifecycleStateChanged(QQuickWebEngineView::LifecycleState) --> void"
<< "QQuickWebEngineView.linkHovered(QUrl) --> void"
<< "QQuickWebEngineView.loadHtml(QString) --> void"
<< "QQuickWebEngineView.loadHtml(QString,QUrl) --> void"
@@ -716,6 +748,7 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineView.loadProgressChanged() --> void"
<< "QQuickWebEngineView.loading --> bool"
<< "QQuickWebEngineView.loadingChanged(QWebEngineLoadingInfo) --> void"
+ << "QQuickWebEngineView.mainFrame --> QWebEngineFrame"
<< "QQuickWebEngineView.navigationRequested(QWebEngineNavigationRequest*) --> void"
<< "QQuickWebEngineView.newWindowRequested(QQuickWebEngineNewWindowRequest*) --> void"
<< "QQuickWebEngineView.AcceptRequest --> NavigationRequestAction"
@@ -747,11 +780,11 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineView.renderProcessPid --> qlonglong"
<< "QQuickWebEngineView.renderProcessPidChanged(qlonglong) --> void"
<< "QQuickWebEngineView.recommendedState --> QQuickWebEngineView::LifecycleState"
- << "QQuickWebEngineView.recommendedStateChanged(LifecycleState) --> void"
+ << "QQuickWebEngineView.recommendedStateChanged(QQuickWebEngineView::LifecycleState) --> void"
<< "QQuickWebEngineView.registerProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest) --> void"
<< "QQuickWebEngineView.reload() --> void"
<< "QQuickWebEngineView.reloadAndBypassCache() --> void"
- << "QQuickWebEngineView.renderProcessTerminated(RenderProcessTerminationStatus,int) --> void"
+ << "QQuickWebEngineView.renderProcessTerminated(QQuickWebEngineView::RenderProcessTerminationStatus,int) --> void"
<< "QQuickWebEngineView.replaceMisspelledWord(QString) --> void"
<< "QQuickWebEngineView.runJavaScript(QString) --> void"
<< "QQuickWebEngineView.runJavaScript(QString,QJSValue) --> void"
@@ -783,6 +816,8 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineView.zoomFactor --> double"
<< "QQuickWebEngineView.zoomFactorChanged(double) --> void"
<< "QQuickWebEngineView.acceptAsNewWindow(QWebEngineNewWindowRequest*) --> void"
+ << "QQuickWebEngineView.save(QString) --> void"
+ << "QQuickWebEngineView.save(QString,QWebEngineDownloadRequest::SavePageFormat) --> void"
<< "QWebEngineQuotaRequest.accept() --> void"
<< "QWebEngineQuotaRequest.origin --> QUrl"
<< "QWebEngineQuotaRequest.reject() --> void"
@@ -801,6 +836,64 @@ static const QStringList expectedAPI = QStringList()
<< "QWebEngineNotification.click() --> void"
<< "QWebEngineNotification.close() --> void"
<< "QWebEngineNotification.closed() --> void"
+ << "QQuickWebEngineView.webAuthUxRequested(QWebEngineWebAuthUxRequest*) --> void"
+ << "QWebEngineWebAuthUxRequest.WebAuthUxState.NotStarted --> WebAuthUxState"
+ << "QWebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount --> WebAuthUxState"
+ << "QWebEngineWebAuthUxRequest.WebAuthUxState.CollectPin --> WebAuthUxState"
+ << "QWebEngineWebAuthUxRequest.WebAuthUxState.FinishTokenCollection --> WebAuthUxState"
+ << "QWebEngineWebAuthUxRequest.WebAuthUxState.RequestFailed --> WebAuthUxState"
+ << "QWebEngineWebAuthUxRequest.WebAuthUxState.Cancelled --> WebAuthUxState"
+ << "QWebEngineWebAuthUxRequest.WebAuthUxState.Completed --> WebAuthUxState"
+ << "QWebEngineWebAuthUxRequest.PinEntryReason.Set --> PinEntryReason"
+ << "QWebEngineWebAuthUxRequest.PinEntryReason.Change --> PinEntryReason"
+ << "QWebEngineWebAuthUxRequest.PinEntryReason.Challenge --> PinEntryReason"
+ << "QWebEngineWebAuthUxRequest.PinEntryError.NoError --> PinEntryError"
+ << "QWebEngineWebAuthUxRequest.PinEntryError.InternalUvLocked --> PinEntryError"
+ << "QWebEngineWebAuthUxRequest.PinEntryError.WrongPin --> PinEntryError"
+ << "QWebEngineWebAuthUxRequest.PinEntryError.TooShort --> PinEntryError"
+ << "QWebEngineWebAuthUxRequest.PinEntryError.InvalidCharacters --> PinEntryError"
+ << "QWebEngineWebAuthUxRequest.PinEntryError.SameAsCurrentPin --> PinEntryError"
+ << "QWebEngineWebAuthUxRequest.RequestFailureReason.Timeout --> RequestFailureReason"
+ << "QWebEngineWebAuthUxRequest.RequestFailureReason.KeyNotRegistered --> RequestFailureReason"
+ << "QWebEngineWebAuthUxRequest.RequestFailureReason.KeyAlreadyRegistered --> RequestFailureReason"
+ << "QWebEngineWebAuthUxRequest.RequestFailureReason.SoftPinBlock --> RequestFailureReason"
+ << "QWebEngineWebAuthUxRequest.RequestFailureReason.HardPinBlock --> RequestFailureReason"
+ << "QWebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorRemovedDuringPinEntry --> RequestFailureReason"
+ << "QWebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingResidentKeys --> RequestFailureReason"
+ << "QWebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingUserVerification --> RequestFailureReason"
+ << "QWebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingLargeBlob --> RequestFailureReason"
+ << "QWebEngineWebAuthUxRequest.RequestFailureReason.NoCommonAlgorithms --> RequestFailureReason"
+ << "QWebEngineWebAuthUxRequest.RequestFailureReason.StorageFull --> RequestFailureReason"
+ << "QWebEngineWebAuthUxRequest.RequestFailureReason.UserConsentDenied --> RequestFailureReason"
+ << "QWebEngineWebAuthUxRequest.RequestFailureReason.WinUserCancelled --> RequestFailureReason"
+ << "QWebEngineWebAuthUxRequest.userNames --> QStringList"
+ << "QWebEngineWebAuthUxRequest.state --> QWebEngineWebAuthUxRequest::WebAuthUxState"
+ << "QWebEngineWebAuthUxRequest.relyingPartyId --> QString"
+ << "QWebEngineWebAuthUxRequest.pinRequest --> QWebEngineWebAuthPinRequest"
+ << "QWebEngineWebAuthUxRequest.requestFailureReason --> QWebEngineWebAuthUxRequest::RequestFailureReason"
+ << "QWebEngineWebAuthUxRequest.stateChanged(QWebEngineWebAuthUxRequest::WebAuthUxState) --> void"
+ << "QWebEngineWebAuthUxRequest.cancel() --> void"
+ << "QWebEngineWebAuthUxRequest.retry() --> void"
+ << "QWebEngineWebAuthUxRequest.setSelectedAccount(QString) --> void"
+ << "QWebEngineWebAuthUxRequest.setPin(QString) --> void"
+ << "QWebEngineWebAuthPinRequest.reason --> QWebEngineWebAuthUxRequest::PinEntryReason"
+ << "QWebEngineWebAuthPinRequest.error --> QWebEngineWebAuthUxRequest::PinEntryError"
+ << "QWebEngineWebAuthPinRequest.minPinLength --> int"
+ << "QWebEngineWebAuthPinRequest.remainingAttempts --> int"
+ << "QQuickWebEngineSettings.AllowImageAnimation --> ImageAnimationPolicy"
+ << "QQuickWebEngineSettings.AnimateImageOnce --> ImageAnimationPolicy"
+ << "QQuickWebEngineSettings.DisallowImageAnimation --> ImageAnimationPolicy"
+ << "QQuickWebEngineSettings.imageAnimationPolicy --> QQuickWebEngineSettings::ImageAnimationPolicy"
+ << "QQuickWebEngineSettings.imageAnimationPolicyChanged() --> void"
+ << "QWebEngineFrame.htmlName --> QString"
+ << "QWebEngineFrame.isValid --> bool"
+ << "QWebEngineFrame.name --> QString"
+ << "QWebEngineFrame.runJavaScript(QString) --> void"
+ << "QWebEngineFrame.runJavaScript(QString,uint) --> void"
+ << "QWebEngineFrame.runJavaScript(QString,QJSValue) --> void"
+ << "QWebEngineFrame.runJavaScript(QString,uint,QJSValue) --> void"
+ << "QWebEngineFrame.size --> QSizeF"
+ << "QWebEngineFrame.url --> QUrl"
;
static bool isCheckedEnum(QMetaType t)
diff --git a/tests/auto/quick/qmltests/BLACKLIST b/tests/auto/quick/qmltests/BLACKLIST
index 36c737693..fc8f9f0d8 100644
--- a/tests/auto/quick/qmltests/BLACKLIST
+++ b/tests/auto/quick/qmltests/BLACKLIST
@@ -10,8 +10,3 @@ macos
[CertificateError::test_error]
*
-[WebViewFindText::test_findTextInterruptedByLoad]
-b2qt arm 64bit
-
-[WebEngineViewLoadUrl::test_loadStartedAfterInPageNavigation]
-b2qt
diff --git a/tests/auto/quick/qmltests/CMakeLists.txt b/tests/auto/quick/qmltests/CMakeLists.txt
index 6f41b3e6d..daae6d60d 100644
--- a/tests/auto/quick/qmltests/CMakeLists.txt
+++ b/tests/auto/quick/qmltests/CMakeLists.txt
@@ -1,4 +1,4 @@
-# Copyright (C) 2022 The Qt Company Ltd.
+# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
include(../../httpserver/httpserver.cmake)
@@ -25,15 +25,17 @@ set(testList
tst_datalist.qml
tst_desktopBehaviorLoadHtml.qml
tst_download.qml
+ tst_dragHandlerUnderView.qml
tst_favicon.qml
tst_faviconDatabase.qml
tst_filePicker.qml
+ tst_filesystem.qml
tst_findText.qml
tst_focusOnNavigation.qml
tst_fullScreenRequest.qml
- tst_geopermission.qml
tst_getUserMedia.qml
tst_inputMethod.qml
+ tst_inputTextDirection.qml
tst_javaScriptDialogs.qml
tst_keyboardEvents.qml
tst_keyboardModifierMapping.qml
@@ -58,6 +60,7 @@ set(testList
tst_userScripts.qml
tst_userScriptCollection.qml
tst_viewSource.qml
+ tst_save.qml
)
if(QT_FEATURE_webengine_webchannel)
@@ -68,6 +71,10 @@ if(QT_FEATURE_ssl)
list(APPEND testList tst_certificateError.qml)
endif()
+if (NOT APPLE)
+ list(APPEND testList tst_geopermission.qml)
+endif()
+
set(content "")
foreach(test ${testList})
set(contents "${contents}${CMAKE_CURRENT_LIST_DIR}/data/${test}\n")
diff --git a/tests/auto/quick/qmltests/data/filesystemapi.html b/tests/auto/quick/qmltests/data/filesystemapi.html
new file mode 100644
index 000000000..ab1a33e4d
--- /dev/null
+++ b/tests/auto/quick/qmltests/data/filesystemapi.html
@@ -0,0 +1,66 @@
+<html>
+<head>
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title> Failed to Upload </title>
+</script>
+</head>
+
+<body>
+<button>Request File Picker</button>
+<script>
+ async function handleDirectoryEntry( dirHandle, out ) {
+ for await (const entry of dirHandle.values()) {
+ if (entry.kind === "file"){
+ const file = await entry.getFile();
+ out[ file.name ] = file;
+ }
+ if (entry.kind === "directory") {
+ const newHandle = await dirHandle.getDirectoryHandle( entry.name, { create: false } );
+ const newOut = out[ entry.name ] = {};
+ await handleDirectoryEntry( newHandle, newOut );
+ }
+ }
+ }
+ const button = document.querySelector('button');
+ button.addEventListener('click', async function() {
+ switch(window.dialogType) {
+ case "savePicker":
+ const saveFileHandle = await window.showSaveFilePicker();
+ const writable = await saveFileHandle.createWritable();
+ await writable.write(new Blob(['TEST_CONTENT']));
+ await writable.close();
+ console.log("TEST:DONE")
+ break;
+ case "filePicker":
+ let [openFileHandle] = await window.showOpenFilePicker();
+ const options = {};
+ options.mode = 'readwrite'
+ await openFileHandle.requestPermission(options)
+ const file = await openFileHandle.getFile();
+ const contents = await file.text();
+ console.log("TEST:" + contents)
+ console.log("TEST:DONE")
+ break;
+ case "directoryPicker":
+ console.log("start")
+ const dirHandle = await window.showDirectoryPicker();
+ for await (const entry of dirHandle.values()) {
+ if (entry.kind === "file"){
+ continue
+ }
+ if (entry.kind === "directory") {
+ console.log("TEST:" + entry.name)
+ }
+ }
+ console.log("TEST:DONE")
+ break;
+ default:
+ }
+ });
+ window.onload = function() {
+ window.dialogType = window.location.href.split('=')[1];
+ document.querySelector('button').focus()
+ }
+</script>
+</body>
+</html>
diff --git a/tests/auto/quick/qmltests/data/test4.html b/tests/auto/quick/qmltests/data/test4.html
index cf5708994..82830668a 100644
--- a/tests/auto/quick/qmltests/data/test4.html
+++ b/tests/auto/quick/qmltests/data/test4.html
@@ -9,7 +9,6 @@
font-size: 50px;
}
</style>
- <meta name="viewport" content="initial-scale=2.0"/>
</head>
<body>
<button onclick="scrollWin()" id="scroll">Click me to scroll!</button><br><br>
diff --git a/tests/auto/quick/qmltests/data/tst_action.qml b/tests/auto/quick/qmltests/data/tst_action.qml
index abe5f71b0..9e49c2dbf 100644
--- a/tests/auto/quick/qmltests/data/tst_action.qml
+++ b/tests/auto/quick/qmltests/data/tst_action.qml
@@ -65,7 +65,9 @@ TestWebEngineView {
{ webAction: WebEngineView.Indent, text: "&Indent", iconName: "", enabled: true },
{ webAction: WebEngineView.Outdent, text: "&Outdent", iconName: "", enabled: true },
{ webAction: WebEngineView.InsertOrderedList, text: "Insert &Ordered List", iconName: "", enabled: true },
- { webAction: WebEngineView.InsertUnorderedList, text: "Insert &Unordered List", iconName: "", enabled: true }
+ { webAction: WebEngineView.InsertUnorderedList, text: "Insert &Unordered List", iconName: "", enabled: true },
+ { webAction: WebEngineView.ChangeTextDirectionLTR, text: "Change text direction left to right", iconName: "", enabled: true },
+ { webAction: WebEngineView.ChangeTextDirectionRTL, text: "Change text direction right to left", iconName: "", enabled: true }
];
}
diff --git a/tests/auto/quick/qmltests/data/tst_dragHandlerUnderView.qml b/tests/auto/quick/qmltests/data/tst_dragHandlerUnderView.qml
new file mode 100644
index 000000000..c22bd44c2
--- /dev/null
+++ b/tests/auto/quick/qmltests/data/tst_dragHandlerUnderView.qml
@@ -0,0 +1,67 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import QtQuick
+import QtTest
+import QtWebEngine
+
+Item {
+ id: parentItem
+ width: 400
+ height: 300
+
+ Rectangle {
+ id: draggableDownUnder
+ color: "wheat"
+ width: 350
+ height: 250
+
+ DragHandler { id: dragHandler }
+ }
+
+ TestWebEngineView {
+ id: webEngineView
+ width: 300
+ height: 250
+
+ property var testUrl: Qt.resolvedUrl("test4.html")
+
+ SignalSpy {
+ id: scrollPositionSpy
+ target: webEngineView
+ signalName: "onScrollPositionChanged"
+ }
+
+ SignalSpy {
+ id: dragActiveSpy
+ target: dragHandler
+ signalName: "activeChanged"
+ }
+
+ TestCase {
+ id: testCase
+ name: "KeepMouseGrabDuringScrolling"
+ when: windowShown
+
+ function test_scroll() {
+ webEngineView.url = Qt.resolvedUrl("test4.html");
+ verify(webEngineView.waitForLoadSucceeded());
+
+ mousePress(webEngineView, 295, 20);
+ mouseMove(webEngineView, 295, 200);
+ mouseRelease(webEngineView, 295, 200);
+
+ // WebEngineView scrolled if the scrollbar was visible.
+ // But on macOS, the scrollbar is hidden, so text gets selected.
+ tryVerify(function() {
+ return (scrollPositionSpy.count === 1 && webEngineView.scrollPosition.y > 100)
+ || webEngineView.getTextSelection().length > 0;
+ });
+
+ // DragHandler didn't take over and drag
+ compare(dragActiveSpy.count, 0);
+ compare(draggableDownUnder.y, 0);
+ }
+ }
+ }
+}
diff --git a/tests/auto/quick/qmltests/data/tst_faviconDatabase.qml b/tests/auto/quick/qmltests/data/tst_faviconDatabase.qml
index 774708af0..284390619 100644
--- a/tests/auto/quick/qmltests/data/tst_faviconDatabase.qml
+++ b/tests/auto/quick/qmltests/data/tst_faviconDatabase.qml
@@ -85,6 +85,9 @@ TestWebEngineView {
function test_iconDatabase(row)
{
+ if (Screen.devicePixelRatio !== 1.0)
+ skip("This test is not supported on High DPI screens.");
+
webEngineView.profile = row.profile;
compare(iconChangedSpy.count, 0);
@@ -129,6 +132,9 @@ TestWebEngineView {
function test_iconDatabaseMultiView()
{
+ if (Screen.devicePixelRatio !== 1.0)
+ skip("This test is not supported on High DPI screens.");
+
var pixel;
var faviconImage = Qt.createQmlObject("
diff --git a/tests/auto/quick/qmltests/data/tst_filePicker.qml b/tests/auto/quick/qmltests/data/tst_filePicker.qml
index 2404efd2d..a7b59b2e9 100644
--- a/tests/auto/quick/qmltests/data/tst_filePicker.qml
+++ b/tests/auto/quick/qmltests/data/tst_filePicker.qml
@@ -11,7 +11,6 @@ TestWebEngineView {
id: webEngineView
width: 400
height: 300
- property var titleChanges: []
function driveLetter() {
if (Qt.platform.os !== "windows")
@@ -30,8 +29,6 @@ TestWebEngineView {
signalName: "renderProcessTerminated"
}
- onTitleChanged: { titleChanges.push(webEngineView.title) }
-
TestCase {
id: testCase
name: "WebEngineViewSingleFileUpload"
@@ -44,7 +41,6 @@ TestWebEngineView {
FilePickerParams.nameFilters = []
titleSpy.clear()
terminationSpy.clear()
- titleChanges = []
}
function cleanup() {
@@ -87,10 +83,10 @@ TestWebEngineView {
keyClick(Qt.Key_Enter); // Focus is on the button. Open FileDialog.
tryCompare(FilePickerParams, "filePickerOpened", true);
+ tryCompare(webEngineView, "title", row.expected);
webEngineView.url = Qt.resolvedUrl("about:blank");
verify(webEngineView.waitForLoadSucceeded());
tryCompare(webEngineView, "title", "about:blank");
- compare(titleChanges[titleChanges.length-2], row.expected);
// Custom dialog
@@ -98,7 +94,7 @@ TestWebEngineView {
function acceptedFileHandler(request) {
request.accepted = true;
- request.dialogAccept(row.input);
+ request.dialogAccept([row.input]);
finished = true;
}
@@ -108,10 +104,10 @@ TestWebEngineView {
keyClick(Qt.Key_Enter); // Focus is on the button. Open FileDialog.
tryVerify(function() { return finished; });
+ tryCompare(webEngineView, "title", row.expected);
webEngineView.url = Qt.resolvedUrl("about:blank");
verify(webEngineView.waitForLoadSucceeded());
tryCompare(webEngineView, "title", "about:blank");
- compare(titleChanges[titleChanges.length-2], row.expected);
webEngineView.fileDialogRequested.disconnect(acceptedFileHandler);
}
@@ -146,7 +142,7 @@ TestWebEngineView {
FilePickerParams.selectedFilesUrl.push(Qt.resolvedUrl("../data"))
keyClick(Qt.Key_Enter) // Focus is on the button. Open FileDialog.
- tryCompare(FilePickerParams, "filePickerOpened", true)
+ tryCompare(FilePickerParams, "directoryPickerOpened", true)
// Check that the title is a file list (eg. "test1.html,test2.html")
tryVerify(function() { return webEngineView.title.match("^([^,]+,)+[^,]+$"); })
@@ -242,10 +238,10 @@ TestWebEngineView {
keyClick(Qt.Key_Enter); // Focus is on the button. Open FileDialog.
tryCompare(FilePickerParams, "filePickerOpened", true);
+ tryCompare(webEngineView, "title", row.expected);
webEngineView.url = Qt.resolvedUrl("about:blank");
verify(webEngineView.waitForLoadSucceeded());
tryCompare(webEngineView, "title", "about:blank");
- compare(titleChanges[titleChanges.length-2], row.expected);
// Custom dialog
@@ -253,7 +249,7 @@ TestWebEngineView {
function acceptedFileHandler(request) {
request.accepted = true;
- request.dialogAccept(row.input);
+ request.dialogAccept([row.input]);
finished = true;
}
@@ -263,10 +259,10 @@ TestWebEngineView {
keyClick(Qt.Key_Enter); // Focus is on the button. Open FileDialog.
tryVerify(function() { return finished; });
+ tryCompare(webEngineView, "title", row.expected);
webEngineView.url = Qt.resolvedUrl("about:blank");
verify(webEngineView.waitForLoadSucceeded());
tryCompare(webEngineView, "title", "about:blank");
- compare(titleChanges[titleChanges.length-2], row.expected);
webEngineView.fileDialogRequested.disconnect(acceptedFileHandler);
}
diff --git a/tests/auto/quick/qmltests/data/tst_filesystem.qml b/tests/auto/quick/qmltests/data/tst_filesystem.qml
new file mode 100644
index 000000000..fa0da4457
--- /dev/null
+++ b/tests/auto/quick/qmltests/data/tst_filesystem.qml
@@ -0,0 +1,124 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import QtQuick
+import QtTest
+import QtWebEngine
+import Test.util
+import "../../qmltests/data"
+import "../mock-delegates/TestParams"
+
+
+TestWebEngineView {
+ id: webEngineView
+ width: 400
+ height: 300
+ property var logs: []
+ property bool accessRequested: false
+ property url file: tempDir.pathUrl('file.txt')
+
+ onJavaScriptConsoleMessage: function(level, message, lineNumber, source) {
+ var pair = message.split(':');
+ if (pair.length == 2 && pair[0] == "TEST")
+ logs.push(pair[1]);
+ }
+
+ TempDir { id: tempDir }
+
+ TestCase {
+ id: testCase
+ name: "FileSystemAPI"
+ when: windowShown
+
+ function init() {
+ clearLog()
+ FilePickerParams.filePickerOpened = false
+ FilePickerParams.selectFiles = false
+ FilePickerParams.selectedFilesUrl = []
+ FilePickerParams.nameFilters = []
+ accessRequested = false;
+ }
+
+ function cleanup() {
+ clearLog()
+ }
+
+ function clearLog() {
+ logs = []
+ }
+
+ function logContainsDoneMarker() {
+ if (logs.indexOf("DONE") > -1)
+ return true
+ else
+ return false
+ }
+
+ function result() {
+ return logs[0]
+ }
+
+ function fileAccessRequest(request) {
+ testCase.verify(!accessRequested)
+ accessRequested = true
+ testCase.verify(request.filePath == file)
+ testCase.verify(request.accessFlags == WebEngineFileSystemAccessRequest.Write | WebEngineFileSystemAccessRequest.Read)
+ request.accept()
+ }
+
+ function directoryAccessRequest(request) {
+ testCase.verify(!accessRequested)
+ accessRequested = true
+ testCase.verify(request.filePath == tempDir.pathUrl())
+ testCase.verify(request.accessFlags == WebEngineFileSystemAccessRequest.Read)
+ request.accept()
+ }
+
+ function test_saveFile() {
+ webEngineView.fileSystemAccessRequested.connect(fileAccessRequest);
+ webEngineView.url = Qt.resolvedUrl("filesystemapi.html?dialog=savePicker");
+ verify(webEngineView.waitForLoadSucceeded());
+ FilePickerParams.selectFiles = true;
+ FilePickerParams.selectedFilesUrl.push(file);
+ keyClick(Qt.Key_Enter); // Open SaveDialog.
+ tryCompare(FilePickerParams, "filePickerOpened", true);
+ tryVerify(logContainsDoneMarker,2000)
+ // write access for save dialogs is automatically granted
+ verify(!accessRequested)
+ webEngineView.fileSystemAccessRequested.disconnect(fileAccessRequest);
+ }
+
+ function test_openFile() {
+ // first save the file before open
+ test_saveFile()
+ init()
+ webEngineView.fileSystemAccessRequested.connect(fileAccessRequest);
+ webEngineView.url = Qt.resolvedUrl("filesystemapi.html?dialog=filePicker");
+ verify(webEngineView.waitForLoadSucceeded());
+ FilePickerParams.selectFiles = true;
+ FilePickerParams.selectedFilesUrl.push(file);
+ keyClick(Qt.Key_Enter); // Open FileDialog.
+ tryCompare(FilePickerParams, "filePickerOpened", true);
+ tryVerify(logContainsDoneMarker,2000)
+ verify(logs.indexOf("TEST_CONTENT") > -1)
+ verify(accessRequested)
+ webEngineView.fileSystemAccessRequested.disconnect(fileAccessRequest);
+ }
+
+ function test_selectDirectory() {
+ tempDir.createDirectory("TEST_DIR")
+ webEngineView.fileSystemAccessRequested.connect(directoryAccessRequest);
+ webEngineView.url = Qt.resolvedUrl("filesystemapi.html?dialog=directoryPicker");
+ verify(webEngineView.waitForLoadSucceeded())
+ FilePickerParams.selectFiles = true;
+ FilePickerParams.selectedFilesUrl.push(tempDir.pathUrl());
+ keyClick(Qt.Key_Enter); // Open showDirectoryDialog.
+ tryCompare(FilePickerParams, "directoryPickerOpened", true);
+ tryVerify(logContainsDoneMarker,2000)
+ verify(logs.indexOf("TEST_DIR") > -1)
+ verify(accessRequested)
+ webEngineView.fileSystemAccessRequested.disconnect(directoryAccessRequest);
+ }
+
+ }
+}
diff --git a/tests/auto/quick/qmltests/data/tst_findText.qml b/tests/auto/quick/qmltests/data/tst_findText.qml
index 392ce5dca..597cff73e 100644
--- a/tests/auto/quick/qmltests/data/tst_findText.qml
+++ b/tests/auto/quick/qmltests/data/tst_findText.qml
@@ -206,8 +206,7 @@ TestWebEngineView {
var listItemText = '';
for (var i = 0; i < 100000; ++i)
- listItemText += "bla ";
- listItemText = listItemText.trim();
+ listItemText += "bla";
webEngineView.loadHtml(
"<html><body>" +
diff --git a/tests/auto/quick/qmltests/data/tst_inputTextDirection.qml b/tests/auto/quick/qmltests/data/tst_inputTextDirection.qml
new file mode 100644
index 000000000..2141db4c8
--- /dev/null
+++ b/tests/auto/quick/qmltests/data/tst_inputTextDirection.qml
@@ -0,0 +1,43 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import QtQuick
+import QtTest
+import QtWebEngine
+
+TestWebEngineView {
+ id: webEngineView
+ width: 400
+ height: 400
+
+ TestCase {
+ id: testCase
+ name: "WebEngineInputTextDirection"
+ when: windowShown
+
+ function getInputTextDirection(element) {
+ var dir;
+ runJavaScript("document.getElementById('" + element + "').dir", function(result) {
+ dir = result;
+ });
+ tryVerify(function() { return dir != undefined; });
+ return dir;
+ }
+
+ function test_changeInputTextDirection() {
+ webEngineView.loadHtml("<html><body><input type='text' id='textfield' value='some text'></body></html>");
+ verify(webEngineView.waitForLoadSucceeded());
+ setFocusToElement("textfield");
+
+ var rtlAction = webEngineView.action(WebEngineView.ChangeTextDirectionRTL);
+ verify(rtlAction);
+ rtlAction.trigger();
+ compare(getInputTextDirection("textfield"), "rtl");
+
+ var ltrAction = webEngineView.action(WebEngineView.ChangeTextDirectionLTR);
+ verify(ltrAction);
+ ltrAction.trigger();
+ compare(getInputTextDirection("textfield"), "ltr");
+ }
+ }
+}
diff --git a/tests/auto/quick/qmltests/data/tst_newViewRequest.qml b/tests/auto/quick/qmltests/data/tst_newViewRequest.qml
index 6d4bdbb41..68350d107 100644
--- a/tests/auto/quick/qmltests/data/tst_newViewRequest.qml
+++ b/tests/auto/quick/qmltests/data/tst_newViewRequest.qml
@@ -101,8 +101,7 @@ TestWebEngineView {
compare(dialog.webEngineView.url, Qt.url("about:blank"));
dialog.destroy();
}
- // https://chromium-review.googlesource.com/c/chromium/src/+/1300395
- compare(newViewRequest.requestedUrl, 'about:blank#blocked');
+ compare(newViewRequest.requestedUrl, 'about:blank');
newViewRequestedSpy.clear();
// Open a page in a new dialog
diff --git a/tests/auto/quick/qmltests/data/tst_save.qml b/tests/auto/quick/qmltests/data/tst_save.qml
new file mode 100644
index 000000000..3289dbd8b
--- /dev/null
+++ b/tests/auto/quick/qmltests/data/tst_save.qml
@@ -0,0 +1,185 @@
+import QtQuick
+import QtTest
+import QtWebEngine
+import Test.util
+
+TestWebEngineView {
+ id: webEngineView
+ width: 200
+ height: 200
+ profile: testSaveProfile
+
+ property url downloadUrl: ""
+ property int totalBytes: 0
+ property int receivedBytes: 0
+ property string downloadDir: ""
+ property string downloadFileName: ""
+ property bool isSavePageDownload: false
+ property var downloadState: []
+ property int savePageFormat: WebEngineDownloadRequest.MimeHtmlSaveFormat;
+ property bool autoCancel: false
+
+ TempDir {
+ id: tempDir
+ }
+
+ SignalSpy {
+ id: downLoadRequestedSpy
+ target: testSaveProfile
+ signalName: "downloadRequested"
+ }
+
+ SignalSpy {
+ id: downloadFinishedSpy
+ target: testSaveProfile
+ signalName: "downloadFinished"
+ }
+
+ WebEngineProfile {
+ id: testSaveProfile
+
+ onDownloadRequested: function(download) {
+ downloadState.push(download.state)
+ downloadUrl = download.url
+ savePageFormat = download.savePageFormat
+ downloadDir = download.downloadDirectory;
+ downloadFileName = download.downloadFileName
+ isSavePageDownload = download.isSavePageDownload
+
+ if (autoCancel)
+ download.cancel()
+ }
+ onDownloadFinished: function(download) {
+ receivedBytes = download.receivedBytes
+ totalBytes = download.totalBytes
+ downloadState.push(download.state)
+ }
+ }
+
+ TestCase {
+ name: "WebEngineViewSave"
+
+ function verifyData() {
+ var isDataValid = false
+ webEngineView.runJavaScript("(function() {" +
+ "var title = document.title.toString();" +
+ "var body = document.body.innerText;" +
+ " return title === \"Test page 1\" && body.includes(\"Hello.\")" +
+ "})();", function(result) {
+ isDataValid = result;
+ });
+ tryVerify(function() { return isDataValid });
+ return isDataValid;
+ }
+
+ function init() {
+ downLoadRequestedSpy.clear()
+ downloadFinishedSpy.clear()
+ totalBytes = 0
+ receivedBytes = 0
+ downloadDir = ""
+ downloadFileName = ""
+ isSavePageDownload = false
+ downloadState = []
+ downloadUrl = ""
+ autoCancel = false
+ }
+
+ function test_savePage_data() {
+ return [
+ { tag: "SingleHtmlSaveFormat", savePageFormat: WebEngineDownloadRequest.SingleHtmlSaveFormat },
+ { tag: "CompleteHtmlSaveFormat", savePageFormat: WebEngineDownloadRequest.CompleteHtmlSaveFormat },
+ { tag: "MimeHtmlSaveFormat", savePageFormat: WebEngineDownloadRequest.MimeHtmlSaveFormat },
+ ];
+ }
+
+ function test_savePage(row) {
+ var saveFormat = row.savePageFormat
+
+ var fileDir = tempDir.path()
+ var fileName = "saved_page.html"
+ var filePath = fileDir + "/"+ fileName
+
+ // load data to view
+ webEngineView.url = Qt.resolvedUrl("test1.html")
+ verify(webEngineView.waitForLoadSucceeded())
+ verify(verifyData())
+
+ webEngineView.save(filePath, saveFormat)
+ downLoadRequestedSpy.wait()
+ compare(downLoadRequestedSpy.count, 1)
+ compare(downloadUrl, webEngineView.url)
+ compare(savePageFormat, saveFormat)
+ compare(downloadDir, fileDir)
+ compare(downloadFileName, fileName)
+ compare(isSavePageDownload, true)
+ compare(downloadState[0], WebEngineDownloadRequest.DownloadInProgress)
+ downloadFinishedSpy.wait()
+ compare(downloadFinishedSpy.count, 1)
+ compare(totalBytes, receivedBytes)
+ compare(downloadState[1], WebEngineDownloadRequest.DownloadCompleted)
+
+ // load some other data
+ webEngineView.url = Qt.resolvedUrl("about:blank")
+ verify(webEngineView.waitForLoadSucceeded())
+
+ // load save file to view
+ webEngineView.url = Qt.resolvedUrl(filePath)
+ verify(webEngineView.waitForLoadSucceeded())
+ verify(verifyData())
+ }
+
+ function test_saveImage_data() {
+ return [
+ { tag: "Auto accept", autoCancel: false },
+ { tag: "Cancel", autoCancel: true },
+ ];
+ }
+
+ function test_saveImage(row) {
+ autoCancel = row.autoCancel
+
+ var fileDir = tempDir.path()
+ var fileName = "favicon.png"
+ var filePath = fileDir + "/"+ fileName
+
+ // Load an image
+ webEngineView.url = Qt.resolvedUrl("icons/favicon.png")
+ verify(webEngineView.waitForLoadSucceeded())
+
+ webEngineView.save(filePath)
+ downLoadRequestedSpy.wait()
+ compare(downLoadRequestedSpy.count, 1)
+ compare(downloadUrl, webEngineView.url)
+ compare(downloadDir, fileDir)
+ compare(downloadFileName, fileName)
+ compare(isSavePageDownload, true)
+ compare(downloadState[0], WebEngineDownloadRequest.DownloadInProgress)
+ downloadFinishedSpy.wait()
+ compare(downloadFinishedSpy.count, 1)
+ if (autoCancel) {
+ compare(receivedBytes, 0)
+ compare(downloadState[1], WebEngineDownloadRequest.DownloadCancelled)
+ } else {
+ compare(totalBytes, receivedBytes)
+ compare(downloadState[1], WebEngineDownloadRequest.DownloadCompleted)
+ }
+ }
+
+ function test_saveWebAction() {
+ // Load an image
+ webEngineView.url = Qt.resolvedUrl("icons/favicon.png")
+ verify(webEngineView.waitForLoadSucceeded())
+
+ // Saving without specifying path shouldn't be auto accepted
+ webEngineView.triggerWebAction(WebEngineView.SavePage)
+ downLoadRequestedSpy.wait()
+ compare(downLoadRequestedSpy.count, 1)
+ compare(downloadUrl, webEngineView.url)
+ compare(isSavePageDownload, true)
+ // The initial download request starts from DownloadRequested state,
+ // which means it wasn't automatically accepted.
+ compare(downloadState[0], WebEngineDownloadRequest.DownloadRequested)
+ }
+ }
+}
diff --git a/tests/auto/quick/qmltests/data/tst_scrollPosition.qml b/tests/auto/quick/qmltests/data/tst_scrollPosition.qml
index e9c72ab7d..cc7d15e4c 100644
--- a/tests/auto/quick/qmltests/data/tst_scrollPosition.qml
+++ b/tests/auto/quick/qmltests/data/tst_scrollPosition.qml
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick
-import QtQuick.Window
import QtTest
import QtWebEngine
@@ -36,7 +35,7 @@ TestWebEngineView {
tryCompare(scrollPositionSpy, "count", 1);
compare(webEngineView.scrollPosition.x, 0);
- compare(webEngineView.scrollPosition.y, 600 * Screen.devicePixelRatio);
+ compare(webEngineView.scrollPosition.y, 600);
}
function test_scrollPositionAfterReload() {
@@ -49,13 +48,13 @@ TestWebEngineView {
// Wait for proper scroll position change otherwise we cannot expect
// the new y position after reload.
tryCompare(webEngineView.scrollPosition, "x", 0);
- tryCompare(webEngineView.scrollPosition, "y", 600 * Screen.devicePixelRatio);
+ tryCompare(webEngineView.scrollPosition, "y", 600);
webEngineView.reload();
verify(webEngineView.waitForLoadSucceeded());
tryCompare(webEngineView.scrollPosition, "x", 0);
- tryCompare(webEngineView.scrollPosition, "y", 600 * Screen.devicePixelRatio);
+ tryCompare(webEngineView.scrollPosition, "y", 600);
}
}
}
diff --git a/tests/auto/quick/qmltests/data/tst_settings.qml b/tests/auto/quick/qmltests/data/tst_settings.qml
index 11b2321e0..f47674aa7 100644
--- a/tests/auto/quick/qmltests/data/tst_settings.qml
+++ b/tests/auto/quick/qmltests/data/tst_settings.qml
@@ -78,6 +78,68 @@ TestWebEngineView {
webEngineView2.destroy();
}
+
+ function test_disableReadingFromCanvas_data() {
+ return [
+ { tag: 'disabled', disableReadingFromCanvas: false, result: true },
+ { tag: 'enabled', disableReadingFromCanvas: true, result: false },
+ ]
+ }
+
+ function test_disableReadingFromCanvas(data) {
+ webEngineView.settings.readingFromCanvasEnabled = !data.disableReadingFromCanvas;
+ webEngineView.loadHtml("<html><body>" +
+ "<canvas id='myCanvas' width='200' height='40' style='border:1px solid #000000;'></canvas>" +
+ "</body></html>");
+ verify(webEngineView.waitForLoadSucceeded());
+ verify(webEngineView.settings.readingFromCanvasEnabled === !data.disableReadingFromCanvas )
+
+ var jsCode = "(function(){" +
+ " var canvas = document.getElementById(\"myCanvas\");" +
+ " var ctx = canvas.getContext(\"2d\");" +
+ " ctx.fillStyle = \"rgb(255,0,255)\";" +
+ " ctx.fillRect(0, 0, 200, 40);" +
+ " try {" +
+ " src = canvas.toDataURL();" +
+ " }" +
+ " catch(err) {" +
+ " src = \"\";" +
+ " }" +
+ " return src.length ? true : false;" +
+ "})();";
+
+ var isDataRead = false;
+ runJavaScript(jsCode, function(result) {
+ isDataRead = result
+ });
+ tryVerify(function() { return isDataRead === data.result });
+ }
+
+ function test_forceDarkMode() {
+ // based on: https://developer.chrome.com/blog/auto-dark-theme/#detecting-auto-dark-theme
+ webEngineView.loadHtml("<html><body>" +
+ "<div id=\"detection\", style=\"display: none; background-color: canvas; color-scheme: light\"</div>" +
+ "</body></html>");
+ const script = "(() => {"
+ + " const detectionDiv = document.querySelector('#detection');"
+ + " return getComputedStyle(detectionDiv).backgroundColor != 'rgb(255, 255, 255)';"
+ + "})()";
+ verify(webEngineView.waitForLoadSucceeded());
+
+ var isAutoDark = true;
+ runJavaScript(script, result => isAutoDark = result);
+ tryVerify(() => {return !isAutoDark});
+
+ webEngineView.settings.forceDarkMode = true;
+ verify(webEngineView.settings.forceDarkMode == true)
+
+ isAutoDark = false;
+ // the page is not updated immediately
+ tryVerify(function() {
+ runJavaScript(script, result => isAutoDark = result);
+ return isAutoDark;
+ });
+ }
}
}
diff --git a/tests/auto/quick/qmltests/mock-delegates/QtWebEngine/ControlsDelegates/DirectoryPicker.qml b/tests/auto/quick/qmltests/mock-delegates/QtWebEngine/ControlsDelegates/DirectoryPicker.qml
new file mode 100644
index 000000000..71da28843
--- /dev/null
+++ b/tests/auto/quick/qmltests/mock-delegates/QtWebEngine/ControlsDelegates/DirectoryPicker.qml
@@ -0,0 +1,18 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import QtQuick
+import "../../TestParams"
+
+QtObject {
+ signal folderSelected(var folder)
+ signal rejected()
+
+ function open() {
+ FilePickerParams.directoryPickerOpened = true;
+ if (FilePickerParams.selectFiles)
+ folderSelected(FilePickerParams.selectedFilesUrl);
+ else
+ rejected();
+ }
+}
diff --git a/tests/auto/quick/qmltests/mock-delegates/TestParams/FilePickerParams.qml b/tests/auto/quick/qmltests/mock-delegates/TestParams/FilePickerParams.qml
index 4a1ffeb02..67d67dc40 100644
--- a/tests/auto/quick/qmltests/mock-delegates/TestParams/FilePickerParams.qml
+++ b/tests/auto/quick/qmltests/mock-delegates/TestParams/FilePickerParams.qml
@@ -8,5 +8,6 @@ QtObject {
property var selectedFilesUrl: [];
property bool selectFiles: false;
property bool filePickerOpened: false;
+ property bool directoryPickerOpened: false;
property var nameFilters: [];
}
diff --git a/tests/auto/quick/qmltests/tst_qmltests.cpp b/tests/auto/quick/qmltests/tst_qmltests.cpp
index 0d9e12cd6..9e928157e 100644
--- a/tests/auto/quick/qmltests/tst_qmltests.cpp
+++ b/tests/auto/quick/qmltests/tst_qmltests.cpp
@@ -105,6 +105,13 @@ public:
return tempDir.isValid() ? tempDir.path() : QString();
}
+ Q_INVOKABLE QUrl pathUrl(const QString &filename = QString())
+ {
+ Q_ASSERT(tempDir.isValid());
+ return filename.isEmpty() ? QUrl::fromLocalFile(tempDir.path())
+ : QUrl::fromLocalFile(tempDir.filePath(filename));
+ }
+
Q_INVOKABLE void removeRecursive(const QString dirname)
{
QDir dir(dirname);
@@ -118,6 +125,8 @@ public:
QDir().rmdir(dirname);
}
+ Q_INVOKABLE void createDirectory(const QString dirname) { QDir(tempDir.path()).mkdir(dirname); }
+
private:
QTemporaryDir tempDir;
};
diff --git a/tests/auto/quick/qquickwebengineview/tst_qquickwebengineview.cpp b/tests/auto/quick/qquickwebengineview/tst_qquickwebengineview.cpp
index 6e369b4c6..dbfa1cb33 100644
--- a/tests/auto/quick/qquickwebengineview/tst_qquickwebengineview.cpp
+++ b/tests/auto/quick/qquickwebengineview/tst_qquickwebengineview.cpp
@@ -1,8 +1,11 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "testwindow.h"
#include "quickutil.h"
+#include "util.h"
#include <QScopedPointer>
#include <QtCore/qelapsedtimer.h>
@@ -16,6 +19,7 @@
#include <QtGui/private/qinputmethod_p.h>
#include <QtWebEngineQuick/private/qquickwebenginescriptcollection_p.h>
#include <QtWebEngineQuick/private/qquickwebenginesettings_p.h>
+#include <QtWebEngineQuick/private/qquickwebenginedownloadrequest_p.h>
#include <QtWebEngineQuick/private/qquickwebengineview_p.h>
#include <QtWebEngineCore/private/qtwebenginecore-config_p.h>
#include <qpa/qplatforminputcontext.h>
@@ -69,9 +73,13 @@ private Q_SLOTS:
void javascriptClipboard_data();
void javascriptClipboard();
void setProfile();
- void focusChild();
+#if QT_CONFIG(accessibility)
void focusChild_data();
+ void focusChild();
+#endif
void htmlSelectPopup();
+ void savePage_data();
+ void savePage();
private:
inline QQuickWebEngineView *newWebEngineView();
@@ -687,8 +695,8 @@ void tst_QQuickWebEngineView::inputContextQueryInput()
QGuiApplication::sendEvent(qApp->focusObject(), &event);
}
QTRY_COMPARE(testContext.infos.size(), 1);
- QCOMPARE(testContext.infos[0].cursorPosition, 3);
- QCOMPARE(testContext.infos[0].anchorPosition, 3);
+ 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, "document.getElementById('input1').value").toString(), QStringLiteral("123QtWebEngine!"));
@@ -1144,9 +1152,9 @@ void tst_QQuickWebEngineView::javascriptClipboard()
"if (result.state == 'prompt') accessPrompt = true;"
"})"));
- QTRY_COMPARE(evaluateJavaScriptSync(view, "accessGranted").toBool(), copyResult);
- QTRY_COMPARE(evaluateJavaScriptSync(view, "accessDenied").toBool(), !javascriptCanAccessClipboard);
- QTRY_COMPARE(evaluateJavaScriptSync(view, "accessPrompt").toBool(), false);
+ QTRY_COMPARE(evaluateJavaScriptSync(view, "accessGranted").toBool(), javascriptCanAccessClipboard && javascriptCanPaste);
+ QTRY_COMPARE(evaluateJavaScriptSync(view, "accessDenied").toBool(), false);
+ QTRY_COMPARE(evaluateJavaScriptSync(view, "accessPrompt").toBool(), !javascriptCanAccessClipboard || !javascriptCanPaste);
evaluateJavaScriptSync(view,
QStringLiteral(
@@ -1160,9 +1168,9 @@ void tst_QQuickWebEngineView::javascriptClipboard()
"if (result.state == 'prompt') accessPrompt = true;"
"})"));
- QTRY_COMPARE(evaluateJavaScriptSync(view, "accessGranted").toBool(), pasteResult);
- QTRY_COMPARE(evaluateJavaScriptSync(view, "accessDenied").toBool(), !javascriptCanAccessClipboard || !javascriptCanPaste);
- QTRY_COMPARE(evaluateJavaScriptSync(view, "accessPrompt").toBool(), false);
+ QTRY_COMPARE(evaluateJavaScriptSync(view, "accessGranted").toBool(), javascriptCanAccessClipboard && javascriptCanPaste);
+ QTRY_COMPARE(evaluateJavaScriptSync(view, "accessDenied").toBool(), false);
+ QTRY_COMPARE(evaluateJavaScriptSync(view, "accessPrompt").toBool(), !javascriptCanAccessClipboard || !javascriptCanPaste);
}
void tst_QQuickWebEngineView::setProfile() {
@@ -1180,6 +1188,7 @@ void tst_QQuickWebEngineView::setProfile() {
QTRY_COMPARE(webEngineView()->url() ,urlFromTestPath("html/basic_page2.html"));
}
+#if QT_CONFIG(accessibility)
void tst_QQuickWebEngineView::focusChild_data()
{
QTest::addColumn<QString>("interfaceName");
@@ -1242,6 +1251,7 @@ void tst_QQuickWebEngineView::focusChild()
// <html> -> <body> -> <input>
QCOMPARE(traverseToWebDocumentAccessibleInterface(iface)->child(0)->child(0), iface->focusChild());
}
+#endif // QT_CONFIG(accessibility)
void tst_QQuickWebEngineView::htmlSelectPopup()
{
@@ -1270,8 +1280,92 @@ void tst_QQuickWebEngineView::htmlSelectPopup()
QCOMPARE(evaluateJavaScriptSync(&view, "document.getElementById('select').value").toString(), QStringLiteral("O2"));
}
+void tst_QQuickWebEngineView::savePage_data()
+{
+ QTest::addColumn<QWebEngineDownloadRequest::SavePageFormat>("savePageFormat");
+
+ QTest::newRow("SingleHtmlSaveFormat") << QWebEngineDownloadRequest::SingleHtmlSaveFormat;
+ QTest::newRow("CompleteHtmlSaveFormat") << QWebEngineDownloadRequest::CompleteHtmlSaveFormat;
+ QTest::newRow("MimeHtmlSaveFormat") << QWebEngineDownloadRequest::MimeHtmlSaveFormat;
+}
+
+void tst_QQuickWebEngineView::savePage()
+{
+ QFETCH(QWebEngineDownloadRequest::SavePageFormat, savePageFormat);
+
+ QTemporaryDir tempDir(QDir::tempPath() + "/tst_QQuickWebEngineView-XXXXXX");
+ QVERIFY(tempDir.isValid());
+ const QString filePath = tempDir.path() + "/saved_page.html";
+
+ QQuickWebEngineView *view = webEngineView();
+ int acceptedCount = 0;
+ int finishedCount = 0;
+ ScopedConnection sc1 = connect(
+ view->profile(), &QQuickWebEngineProfile::downloadRequested,
+ [&](QQuickWebEngineDownloadRequest *downloadRequest) {
+ QCOMPARE(downloadRequest->state(),
+ QQuickWebEngineDownloadRequest::DownloadInProgress);
+ QCOMPARE(downloadRequest->isSavePageDownload(), true);
+ QCOMPARE(downloadRequest->savePageFormat(), savePageFormat);
+ QCOMPARE(QDir(downloadRequest->downloadDirectory())
+ .filePath(downloadRequest->downloadFileName()),
+ filePath);
+ QCOMPARE(downloadRequest->url(), view->url());
+
+ connect(downloadRequest, &QQuickWebEngineDownloadRequest::isFinishedChanged,
+ [&, downloadRequest]() {
+ QCOMPARE(downloadRequest->state(),
+ QQuickWebEngineDownloadRequest::DownloadCompleted);
+ QCOMPARE(downloadRequest->isSavePageDownload(), true);
+ QCOMPARE(downloadRequest->isFinished(), true);
+ QCOMPARE(downloadRequest->savePageFormat(), savePageFormat);
+ QCOMPARE(downloadRequest->totalBytes(),
+ downloadRequest->receivedBytes());
+ QVERIFY(downloadRequest->receivedBytes() > 0);
+ QCOMPARE(QDir(downloadRequest->downloadDirectory())
+ .filePath(downloadRequest->downloadFileName()),
+ filePath);
+ QCOMPARE(downloadRequest->url(), view->url());
+ finishedCount++;
+ });
+ acceptedCount++;
+ });
+
+ const QString originalData = QStringLiteral("Basic page");
+ view->setUrl(urlFromTestPath("html/basic_page.html"));
+ QVERIFY(waitForLoadSucceeded(view));
+ QCOMPARE(evaluateJavaScriptSync(view, "document.getElementsByTagName('h1')[0].innerText")
+ .toString(),
+ originalData);
+
+ // Save the loaded page as HTML.
+ view->save(filePath, savePageFormat);
+ QTRY_COMPARE(acceptedCount, 1);
+ QTRY_COMPARE(finishedCount, 1);
+ QFile file(filePath);
+ QVERIFY(file.exists());
+
+ // Load something else.
+ view->setUrl(urlFromTestPath("html/basic_page2.html"));
+ QVERIFY(waitForLoadSucceeded(view));
+ QVERIFY(evaluateJavaScriptSync(view, "document.getElementsByTagName('h1')[0].innerText")
+ .toString()
+ != originalData);
+
+ // Load the saved page and compare the contents.
+ view->setUrl(QUrl::fromLocalFile(filePath));
+ QVERIFY(waitForLoadSucceeded(view));
+ QCOMPARE(evaluateJavaScriptSync(view, "document.getElementsByTagName('h1')[0].innerText")
+ .toString(),
+ originalData);
+}
+
+#if QT_CONFIG(accessibility)
static QByteArrayList params = QByteArrayList()
<< "--force-renderer-accessibility";
+#else
+static QByteArrayList params;
+#endif
W_QTEST_MAIN(tst_QQuickWebEngineView, params)
#include "tst_qquickwebengineview.moc"
diff --git a/tests/auto/quick/qquickwebengineviewgraphics/tst_qquickwebengineviewgraphics.cpp b/tests/auto/quick/qquickwebengineviewgraphics/tst_qquickwebengineviewgraphics.cpp
index 71c3a1cf2..3644ac481 100644
--- a/tests/auto/quick/qquickwebengineviewgraphics/tst_qquickwebengineviewgraphics.cpp
+++ b/tests/auto/quick/qquickwebengineviewgraphics/tst_qquickwebengineviewgraphics.cpp
@@ -124,6 +124,7 @@ void tst_QQuickWebEngineViewGraphics::reparentToOtherWindow()
m_view->rootObject()->setParentItem(window.contentItem());
window.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&window));
verifyGreenSquare(&window);
}
diff --git a/tests/auto/util/util.h b/tests/auto/util/util.h
index 2da339733..5533eed80 100644
--- a/tests/auto/util/util.h
+++ b/tests/auto/util/util.h
@@ -163,7 +163,7 @@ static inline QRect elementGeometry(QWebEnginePage *page, const QString &id)
QVariantList coords = evaluateJavaScriptSync(page, jsCode).toList();
if (coords.size() != 4) {
- qWarning("elementGeometry faield.");
+ qWarning("elementGeometry failed.");
return QRect();
}
diff --git a/tests/auto/widgets/CMakeLists.txt b/tests/auto/widgets/CMakeLists.txt
index 34d165c94..9246be68a 100644
--- a/tests/auto/widgets/CMakeLists.txt
+++ b/tests/auto/widgets/CMakeLists.txt
@@ -16,6 +16,7 @@ add_subdirectory(qwebenginehistory)
add_subdirectory(qwebenginescript)
if(LINUX)
add_subdirectory(offscreen)
+ add_subdirectory(qtbug_110287)
endif()
if(NOT MACOS)
add_subdirectory(touchinput)
diff --git a/tests/auto/widgets/accessibility/BLACKLIST b/tests/auto/widgets/accessibility/BLACKLIST
index 41d3635d2..6fdb0dd58 100644
--- a/tests/auto/widgets/accessibility/BLACKLIST
+++ b/tests/auto/widgets/accessibility/BLACKLIST
@@ -1,2 +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
index 4c0bb17ee..f6a08c9d3 100644
--- a/tests/auto/widgets/accessibility/CMakeLists.txt
+++ b/tests/auto/widgets/accessibility/CMakeLists.txt
@@ -8,5 +8,6 @@ qt_internal_add_test(tst_webengine_accessibility
tst_accessibility.cpp
LIBRARIES
Qt::WebEngineWidgets
+ Qt::WebEngineCorePrivate
Test::Util
)
diff --git a/tests/auto/widgets/accessibility/tst_accessibility.cpp b/tests/auto/widgets/accessibility/tst_accessibility.cpp
index a420d041c..1579b61e2 100644
--- a/tests/auto/widgets/accessibility/tst_accessibility.cpp
+++ b/tests/auto/widgets/accessibility/tst_accessibility.cpp
@@ -17,6 +17,7 @@
Boston, MA 02110-1301, USA.
*/
+#include <QtWebEngineCore/private/qtwebenginecore-config_p.h>
#include <qtest.h>
#include <widgetutil.h>
@@ -50,6 +51,7 @@ private Q_SLOTS:
void roles();
void objectName();
void crossTreeParent();
+ void tableCellInterface();
};
// This will be called before the first test function is executed.
@@ -475,7 +477,7 @@ 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;
@@ -605,9 +607,65 @@ void tst_Accessibility::crossTreeParent()
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/printing/tst_printing.cpp b/tests/auto/widgets/printing/tst_printing.cpp
index 1c1e0615e..605fb57b5 100644
--- a/tests/auto/widgets/printing/tst_printing.cpp
+++ b/tests/auto/widgets/printing/tst_printing.cpp
@@ -3,6 +3,7 @@
#include <QtWebEngineCore/private/qtwebenginecoreglobal_p.h>
#include <QtWebEngineCore/qtwebenginecore-config.h>
+#include <QWebEngineSettings>
#include <QWebEngineView>
#include <QTemporaryDir>
#include <QTest>
@@ -22,7 +23,9 @@ private slots:
void printRequest();
#if QT_CONFIG(webengine_system_poppler)
void printToPdfPoppler();
+ void printFromPdfViewer();
#endif
+ void interruptPrinting();
};
void tst_Printing::printToPdfBasic()
@@ -115,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/proxy/tst_proxy.cpp b/tests/auto/widgets/proxy/tst_proxy.cpp
index f378ae22f..3dc72618c 100644
--- a/tests/auto/widgets/proxy/tst_proxy.cpp
+++ b/tests/auto/widgets/proxy/tst_proxy.cpp
@@ -8,7 +8,7 @@
#include <QWebEnginePage>
#include <QWebEngineView>
#include <QWebEngineUrlRequestInterceptor>
-
+#include <QWebEngineLoadingInfo>
struct Interceptor : public QWebEngineUrlRequestInterceptor
{
@@ -28,6 +28,7 @@ public:
private slots:
void proxyAuthentication();
void forwardCookie();
+ void invalidHostName();
};
@@ -72,6 +73,19 @@ void tst_Proxy::forwardCookie()
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"
QTEST_MAIN(tst_Proxy)
diff --git a/tests/auto/widgets/proxypac/tst_proxypac.cpp b/tests/auto/widgets/proxypac/tst_proxypac.cpp
index afdccdea8..43ccbf028 100644
--- a/tests/auto/widgets/proxypac/tst_proxypac.cpp
+++ b/tests/auto/widgets/proxypac/tst_proxypac.cpp
@@ -50,7 +50,7 @@ void tst_ProxyPac::proxypac()
QSignalSpy spyFinished(&page, &QWebEnginePage::loadFinished);
page.load(QUrl("https://contribute.qt-project.org"));
- QTRY_VERIFY_WITH_TIMEOUT(!spyFinished.isEmpty(), 100000);
+ QTRY_VERIFY_WITH_TIMEOUT(!spyFinished.isEmpty(), 200000);
}
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/qwebenginedownloadrequest/tst_qwebenginedownloadrequest.cpp b/tests/auto/widgets/qwebenginedownloadrequest/tst_qwebenginedownloadrequest.cpp
index 1c2268b31..c81a27b3a 100644
--- a/tests/auto/widgets/qwebenginedownloadrequest/tst_qwebenginedownloadrequest.cpp
+++ b/tests/auto/widgets/qwebenginedownloadrequest/tst_qwebenginedownloadrequest.cpp
@@ -415,7 +415,7 @@ void tst_QWebEngineDownloadRequest::downloadLink()
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(), QWebEngineDownloadRequest::NoReason);
QCOMPARE(item->isSavePageDownload(), false);
@@ -526,7 +526,7 @@ void tst_QWebEngineDownloadRequest::downloadTwoLinks()
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(), QWebEngineDownloadRequest::NoReason);
QCOMPARE(item->savePageFormat(), QWebEngineDownloadRequest::UnknownSaveFormat);
diff --git a/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp b/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp
index 6ddc031d5..ad66e972c 100644
--- a/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp
+++ b/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp
@@ -487,9 +487,9 @@ void tst_QWebEngineHistory::clear()
QWebEnginePage page2(this);
QWebEngineHistory* hist2 = page2.history();
- QCOMPARE(hist2->count(), 1);
+ QCOMPARE(hist2->count(), 0);
hist2->clear();
- QCOMPARE(hist2->count(), 1); // Do not change anything.
+ QCOMPARE(hist2->count(), 0); // Do not change anything.
}
void tst_QWebEngineHistory::historyItemFromDeletedPage()
diff --git a/tests/auto/widgets/qwebenginepage/BLACKLIST b/tests/auto/widgets/qwebenginepage/BLACKLIST
index 7eb97b3bb..52def48d1 100644
--- a/tests/auto/widgets/qwebenginepage/BLACKLIST
+++ b/tests/auto/widgets/qwebenginepage/BLACKLIST
@@ -5,11 +5,11 @@ osx
windows
macos # Can't move cursor (QTBUG-76312)
-[acceptNavigationRequestNavigationType]
-b2qt arm
-
[comboBoxPopupPositionAfterMove]
macos
[comboBoxPopupPositionAfterChildMove]
macos
+
+[backgroundColor]
+macos
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..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
@@ -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,6 +67,7 @@
#include <qwebenginescript.h>
#include <qwebenginescriptcollection.h>
#include <qwebenginesettings.h>
+#include <qwebengineurlrequestinterceptor.h>
#include <qwebengineurlrequestjob.h>
#include <qwebengineurlscheme.h>
#include <qwebengineurlschemehandler.h>
@@ -113,10 +117,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,7 +172,8 @@ private Q_SLOTS:
void runJavaScriptDisabled();
void runJavaScriptFromSlot();
void fullScreenRequested();
- void quotaRequested();
+ void requestQuota_data();
+ void requestQuota();
// Tests from tst_QWebEngineFrame
@@ -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()
@@ -439,6 +469,7 @@ private:
bool m_allowGeolocation;
};
+#ifndef Q_OS_MACOS
void tst_QWebEnginePage::geolocationRequestJS_data()
{
QTest::addColumn<bool>("allowed");
@@ -478,6 +509,7 @@ void tst_QWebEnginePage::geolocationRequestJS()
QEXPECT_FAIL("", "No location service available.", Continue);
QCOMPARE(result, errorCode);
}
+#endif
void tst_QWebEnginePage::loadFinished()
{
@@ -605,6 +637,7 @@ public:
QWebEngineNavigationRequest::NavigationType type;
QUrl url;
bool isMainFrame;
+ bool hasFormData;
};
QList<Navigation> navigations;
@@ -622,6 +655,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 +673,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 +722,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 +1403,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 +1419,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 +1469,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 +1482,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 +1496,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;
@@ -2014,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);
@@ -2023,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);" \
- "});");
+ 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 +2125,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 +2445,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 +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);
@@ -2496,8 +2561,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 +2876,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 +2950,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 +3129,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 +3339,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 +3581,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 +3760,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);
@@ -3871,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);
@@ -3899,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");
@@ -5119,6 +5366,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);
+
+ 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/qwebengineprofile/tst_qwebengineprofile.cpp b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp
index 471385223..cebdaaa47 100644
--- a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp
+++ b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp
@@ -188,8 +188,10 @@ void tst_QWebEngineProfile::clearDataFromCache()
QVERIFY(server.start());
AutoDir cacheDir("./tst_QWebEngineProfile_clearDataFromCache");
+ QVERIFY(!cacheDir.exists("Cache"));
QWebEngineProfile profile(QStringLiteral("clearDataFromCache"));
+ QSignalSpy cacheSpy(&profile, &QWebEngineProfile::clearHttpCacheCompleted);
profile.setCachePath(cacheDir.path());
profile.setHttpCacheType(QWebEngineProfile::DiskHttpCache);
@@ -200,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();
}
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 ed12fdba0..9ba13589f 100644
--- a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp
+++ b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp
@@ -76,6 +76,7 @@ private Q_SLOTS:
void scriptsInNestedIframes();
void matchQrcUrl();
void injectionOrder();
+ void reloadWithSubframes();
};
void tst_QWebEngineScript::domEditing()
@@ -694,6 +695,38 @@ void tst_QWebEngineScript::injectionOrder()
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/qwebengineview/BLACKLIST b/tests/auto/widgets/qwebengineview/BLACKLIST
index 5d9cc038a..26f2da4bb 100644
--- a/tests/auto/widgets/qwebengineview/BLACKLIST
+++ b/tests/auto/widgets/qwebengineview/BLACKLIST
@@ -8,4 +8,5 @@ windows
windows
[horizontalScrollbarTest]
-b2qt # different scrollbar
+macos
+rhel # flaky
diff --git a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp
index a45799e70..f4ed06e14 100644
--- a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp
+++ b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp
@@ -18,6 +18,9 @@
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
+
+#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>
@@ -182,7 +185,10 @@ private Q_SLOTS:
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.
@@ -208,6 +214,95 @@ 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()
{
#if !defined(QWEBENGINEVIEW_RENDERHINTS)
@@ -683,19 +778,29 @@ void tst_QWebEngineView::unhandledKeyEventPropagation()
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.size(), 1);
+ QTRY_COMPARE(firstPaintSpy.size(), 1);
QVERIFY(view.page()->scrollPosition() == QPoint(0, 0));
QSignalSpy scrollSpy(view.page(), SIGNAL(scrollPositionChanged(QPointF)));
@@ -1353,18 +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.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);
@@ -1388,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()
@@ -2054,8 +2160,8 @@ void tst_QWebEngineView::inputContextQueryInput()
QApplication::sendEvent(view.focusProxy(), &event);
}
QTRY_COMPARE(testContext.infos.size(), 1);
- QCOMPARE(testContext.infos[0].cursorPosition, 3);
- QCOMPARE(testContext.infos[0].anchorPosition, 3);
+ 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!"));
@@ -2309,22 +2415,19 @@ void tst_QWebEngineView::textSelectionOutOfInputField()
// Select text by ctrl+a
QTest::keyClick(view.windowHandle(), Qt::Key_A, Qt::ControlModifier);
- QVERIFY(selectionChangedSpy.wait());
- QCOMPARE(selectionChangedSpy.size(), 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.size(), 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.size(), 3);
+ QTRY_COMPARE(selectionChangedSpy.size(), 3);
QVERIFY(view.hasSelection());
QCOMPARE(view.page()->selectedText(), QString("This is a text"));
@@ -2332,8 +2435,7 @@ void tst_QWebEngineView::textSelectionOutOfInputField()
view.hide();
view.page()->setLifecycleState(QWebEnginePage::LifecycleState::Discarded);
view.show();
- QVERIFY(loadFinishedSpy.wait());
- QCOMPARE(selectionChangedSpy.size(), 4);
+ QTRY_COMPARE(selectionChangedSpy.size(), 4);
QVERIFY(!view.hasSelection());
QVERIFY(view.page()->selectedText().isEmpty());
@@ -2356,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.size(), 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.size(), 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.size(), 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.size(), 4);
+ QTRY_COMPARE(selectionChangedSpy.size(), 4);
QVERIFY(!view.hasSelection());
QVERIFY(view.page()->selectedText().isEmpty());
}
@@ -2659,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"));
@@ -2959,6 +3057,7 @@ void tst_QWebEngineView::imeCompositionQueryEvent()
}
QInputMethodQueryEvent srrndTextQuery(Qt::ImSurroundingText);
+ QInputMethodQueryEvent absolutePosQuery(Qt::ImAbsolutePosition);
QInputMethodQueryEvent cursorPosQuery(Qt::ImCursorPosition);
QInputMethodQueryEvent anchorPosQuery(Qt::ImAnchorPosition);
@@ -2970,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
{
@@ -2993,13 +3094,64 @@ 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);
}
#if QT_CONFIG(clipboard)
@@ -3231,7 +3383,7 @@ void tst_QWebEngineView::webUIURLs_data()
QTest::newRow("process-internals") << QUrl("chrome://process-internals") << true;
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;
@@ -3636,6 +3788,28 @@ void tst_QWebEngineView::navigateOnDrop()
}
}
+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>"
@@ -3785,5 +3959,49 @@ void tst_QWebEngineView::datalist()
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)
#include "tst_qwebengineview.moc"
diff --git a/tests/auto/widgets/spellchecking/CMakeLists.txt b/tests/auto/widgets/spellchecking/CMakeLists.txt
index 8c538377d..d0c7656c1 100644
--- a/tests/auto/widgets/spellchecking/CMakeLists.txt
+++ b/tests/auto/widgets/spellchecking/CMakeLists.txt
@@ -2,6 +2,7 @@
# SPDX-License-Identifier: BSD-3-Clause
include(../../util/util.cmake)
+include(../../../../src/core/api/Qt6WebEngineCoreMacros.cmake)
qt_internal_add_test(tst_spellchecking
SOURCES
diff --git a/tests/auto/widgets/touchinput/tst_touchinput.cpp b/tests/auto/widgets/touchinput/tst_touchinput.cpp
index 562ccfcef..42178558c 100644
--- a/tests/auto/widgets/touchinput/tst_touchinput.cpp
+++ b/tests/auto/widgets/touchinput/tst_touchinput.cpp
@@ -130,7 +130,7 @@ void TouchInputTest::initTestCase()
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' width='150px' type='text' value='The Qt Company2' /></form>"
+ "<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>"
@@ -303,7 +303,7 @@ void TouchInputTest::pinchZoom()
for (int i = 0; i < 3; ++i) {
gesturePinch(/* zoomIn = */true, tapOneByOne);
- QTRY_VERIFY2(getScaleFactor(&scale) > 1.5, qPrintable(QString("i: %1, scale: %2").arg(i).arg(scale)));
+ 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);
}
diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt
index c6d3e6e50..d9f3e11ea 100644
--- a/tests/manual/CMakeLists.txt
+++ b/tests/manual/CMakeLists.txt
@@ -1,2 +1,7 @@
-add_subdirectory(quick)
-add_subdirectory(widgets)
+add_subdirectory(examples)
+if(TARGET Qt::WebEngineQuick)
+ add_subdirectory(quick)
+endif()
+if(TARGET Qt::WebEngineWidgets)
+ add_subdirectory(widgets)
+endif()
diff --git a/tests/manual/examples/CMakeLists.txt b/tests/manual/examples/CMakeLists.txt
new file mode 100644
index 000000000..c56302f20
--- /dev/null
+++ b/tests/manual/examples/CMakeLists.txt
@@ -0,0 +1,6 @@
+if(TARGET Qt::WebEngineWidgets)
+ add_subdirectory(widgets)
+endif()
+if(TARGET Qt::WebEngineQuick)
+ add_subdirectory(quick)
+endif()
diff --git a/tests/manual/examples/quick/CMakeLists.txt b/tests/manual/examples/quick/CMakeLists.txt
new file mode 100644
index 000000000..c461f2dbb
--- /dev/null
+++ b/tests/manual/examples/quick/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_subdirectory(minimal)
+add_subdirectory(customdialogs)
+add_subdirectory(customtouchhandle)
+add_subdirectory(webengineaction)
diff --git a/tests/manual/examples/quick/customdialogs/CMakeLists.txt b/tests/manual/examples/quick/customdialogs/CMakeLists.txt
new file mode 100644
index 000000000..bfbff79eb
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/CMakeLists.txt
@@ -0,0 +1,56 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(customdialogs LANGUAGES CXX)
+ find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_manual_test(customdialogs
+ SOURCES main.cpp server.cpp server.h
+)
+
+set_target_properties(customdialogs PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(customdialogs PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::WebEngineQuick
+)
+
+set(customdialogs_resource_files
+ "MessageRectangle.qml"
+ "SwitchButton.qml"
+ "WebView.qml"
+ "forms/Authentication.qml"
+ "forms/AuthenticationForm.ui.qml"
+ "forms/ColorCell.qml"
+ "forms/ColorPicker.qml"
+ "forms/ColorPickerForm.ui.qml"
+ "forms/CustomButton.qml"
+ "forms/FilePicker.qml"
+ "forms/FilePickerForm.ui.qml"
+ "forms/FileRow.qml"
+ "forms/JavaScript.qml"
+ "forms/JavaScriptForm.ui.qml"
+ "forms/Menu.qml"
+ "forms/MenuForm.ui.qml"
+ "forms/TouchSelectionMenu.qml"
+ "forms/TouchSelectionMenuForm.ui.qml"
+ "icon.svg"
+ "index.html"
+ "main.qml"
+ "style.css"
+)
+
+qt_add_resources(customdialogs "customdialogs"
+ PREFIX
+ "/"
+ FILES
+ ${customdialogs_resource_files}
+)
+
diff --git a/tests/manual/examples/quick/customdialogs/MessageRectangle.qml b/tests/manual/examples/quick/customdialogs/MessageRectangle.qml
new file mode 100644
index 000000000..09a202cf3
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/MessageRectangle.qml
@@ -0,0 +1,18 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Rectangle {
+ property alias text: messageText.text
+ width: parent.width
+ height: 30
+ visible: false
+ color: "#80c342"
+ Text {
+ id: messageText
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ font.pointSize: 12
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/SwitchButton.qml b/tests/manual/examples/quick/customdialogs/SwitchButton.qml
new file mode 100644
index 000000000..69fc1427e
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/SwitchButton.qml
@@ -0,0 +1,23 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+Item {
+ width: parent.width
+ height: 40
+ property alias checked: switcher.checked
+ RowLayout {
+ anchors.centerIn: parent
+ Text {
+ text: qsTr("Use default dialogs")
+ font.pointSize: 12
+ }
+ Switch {
+ id: switcher
+ checked: true
+ }
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/WebView.qml b/tests/manual/examples/quick/customdialogs/WebView.qml
new file mode 100644
index 000000000..5c99ee7e7
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/WebView.qml
@@ -0,0 +1,117 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtWebEngine
+
+WebEngineView {
+ id: view
+ url: "qrc:/index.html"
+ property bool useDefaultDialogs: true
+ signal openForm(var form)
+
+ Rectangle {
+ id: tooltip
+ width: 200
+ height: 30
+ z: 50
+ visible: false
+ color: "gray"
+ border.color: "black"
+ border.width: 2
+ radius: 3
+
+ property string text: ""
+
+ Text {
+ x: 0
+ y: 0
+ color: "#ffffff"
+ text: parent.text
+ font.pointSize: 12
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ font.bold: false
+ }
+
+ }
+
+ onContextMenuRequested: function(request) {
+ // we only show menu for links with #openMenu
+ if (!request.linkUrl.toString().endsWith("#openMenu")) {
+ request.accepted = true;
+ return;
+ }
+ // return early to show default menu
+ if (useDefaultDialogs)
+ return;
+
+ request.accepted = true;
+ openForm({item: Qt.resolvedUrl("forms/Menu.qml"),
+ properties: {"request": request}});
+ }
+
+ onTooltipRequested: function(request) {
+ if (useDefaultDialogs)
+ return;
+
+ if (request.type == TooltipRequest.Show) {
+ tooltip.visible = true;
+ tooltip.x = request.x;
+ tooltip.y = request.y;
+ tooltip.text = request.text;
+ } else {
+ tooltip.visible = false;
+ }
+
+ request.accepted = true;
+ }
+
+ onAuthenticationDialogRequested: function(request) {
+ if (useDefaultDialogs) {
+ // do not show proxy error page
+ view.url = "qrc:/index.html"
+ return;
+ }
+ request.accepted = true;
+ openForm({item: Qt.resolvedUrl("forms/Authentication.qml"),
+ properties: {"request": request}});
+ }
+
+ onJavaScriptDialogRequested: function(request) {
+ if (useDefaultDialogs)
+ return;
+
+ request.accepted = true;
+ openForm({item: Qt.resolvedUrl("forms/JavaScript.qml"),
+ properties: {"request": request}});
+ }
+
+ onColorDialogRequested: function(request) {
+ if (useDefaultDialogs)
+ return;
+
+ request.accepted = true;
+ openForm({item: Qt.resolvedUrl("forms/ColorPicker.qml"),
+ properties: {"request": request}});
+ }
+
+ onFileDialogRequested: function(request) {
+ if (useDefaultDialogs)
+ return;
+
+ request.accepted = true;
+ openForm({item: Qt.resolvedUrl("forms/FilePicker.qml"),
+ properties: {"request": request}});
+
+ }
+
+ onTouchSelectionMenuRequested: function(request) {
+ if (useDefaultDialogs)
+ return;
+
+ request.accepted = true;
+ openForm({item: Qt.resolvedUrl("forms/TouchSelectionMenu.qml"),
+ properties: {"request": request}});
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/customdialogs.pro b/tests/manual/examples/quick/customdialogs/customdialogs.pro
new file mode 100644
index 000000000..1b9a6778e
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/customdialogs.pro
@@ -0,0 +1,18 @@
+QT += webenginequick
+
+HEADERS += \
+ server.h
+
+SOURCES += \
+ main.cpp \
+ server.cpp
+
+RESOURCES += \
+ customdialogs.qrc
+
+target.path = $$[QT_INSTALL_EXAMPLES]/webenginequick/customdialogs
+INSTALLS += target
+
+qtHaveModule(widgets) {
+ QT += widgets # QApplication is required to get native styling with QtQuickControls
+}
diff --git a/tests/manual/examples/quick/customdialogs/customdialogs.qrc b/tests/manual/examples/quick/customdialogs/customdialogs.qrc
new file mode 100644
index 000000000..bb2677198
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/customdialogs.qrc
@@ -0,0 +1,24 @@
+<RCC>
+ <qresource prefix="/">
+ <file>forms/AuthenticationForm.ui.qml</file>
+ <file>forms/Authentication.qml</file>
+ <file>forms/ColorCell.qml</file>
+ <file>forms/ColorPickerForm.ui.qml</file>
+ <file>forms/ColorPicker.qml</file>
+ <file>forms/CustomButton.qml</file>
+ <file>forms/FilePickerForm.ui.qml</file>
+ <file>forms/FilePicker.qml</file>
+ <file>forms/FileRow.qml</file>
+ <file>forms/JavaScriptForm.ui.qml</file>
+ <file>forms/JavaScript.qml</file>
+ <file>forms/MenuForm.ui.qml</file>
+ <file>forms/Menu.qml</file>
+ <file>icon.svg</file>
+ <file>index.html</file>
+ <file>main.qml</file>
+ <file>MessageRectangle.qml</file>
+ <file>style.css</file>
+ <file>SwitchButton.qml</file>
+ <file>WebView.qml</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/examples/quick/customdialogs/forms/Authentication.qml b/tests/manual/examples/quick/customdialogs/forms/Authentication.qml
new file mode 100644
index 000000000..151a7c4aa
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/Authentication.qml
@@ -0,0 +1,31 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtWebEngine
+
+AuthenticationForm {
+ property QtObject request
+ signal closeForm()
+
+ cancelButton.onClicked: {
+ request.dialogReject();
+ closeForm();
+ }
+
+ loginButton.onClicked: {
+ request.dialogReject();
+ closeForm();
+ }
+
+ Component.onCompleted: {
+ switch (request.type) {
+ case AuthenticationDialogRequest.AuthenticationTypeHTTP:
+ console.log("HTTP Authentication Required. Host says: " + request.realm);
+ break;
+ case AuthenticationDialogRequest.AuthenticationTypeProxy:
+ console.log("Proxy Authentication Required for: " + request.proxyHost);
+ break;
+ }
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/forms/AuthenticationForm.ui.qml b/tests/manual/examples/quick/customdialogs/forms/AuthenticationForm.ui.qml
new file mode 100644
index 000000000..f14986b20
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/AuthenticationForm.ui.qml
@@ -0,0 +1,137 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+
+Item {
+ id: item1
+ property alias cancelButton: cancelButton
+ property alias loginButton: loginButton
+ property alias userName: userName
+ property alias password: password
+
+ ColumnLayout {
+ id: columnLayout
+ anchors.topMargin: 20
+ anchors.top: parent.top
+ anchors.bottomMargin: 20
+ anchors.bottom: parent.bottom
+ anchors.rightMargin: 20
+ anchors.right: parent.right
+ anchors.leftMargin: 20
+ anchors.left: parent.left
+
+ Image {
+ id: image
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+ source: "qrc:/icon.svg"
+ }
+
+ Rectangle {
+ id: rectangle
+ width: parent.width
+ height: 30
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+ gradient: Gradient {
+ GradientStop {
+ position: 0
+ color: "#25a6e2"
+ }
+ GradientStop {
+ color: "#188bd0"
+ }
+ }
+
+ Text {
+ id: textArea
+ x: 54
+ y: 5
+ color: "#ffffff"
+ text: qsTr("Restricted Area")
+ font.pointSize: 12
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ Item {
+ width: 40
+ height: 40
+ }
+
+ Text {
+ id: userNameText
+ text: qsTr("Username:")
+ font.pointSize: 12
+ }
+
+ TextField {
+ id: userName
+ width: 300
+ height: 22
+ Layout.fillWidth: true
+ font.pointSize: 12
+ color: "black"
+
+ background: Rectangle {
+ color: "white"
+ border.color: "black"
+ border.width: 1
+ }
+ }
+
+ Text {
+ id: passwordText
+ text: qsTr("Password:")
+ font.pointSize: 12
+ }
+
+ TextField {
+ id: password
+ width: 300
+ height: 26
+ Layout.fillWidth: true
+ font.pointSize: 12
+ color: "black"
+ echoMode: TextInput.Password
+
+ background: Rectangle {
+ color: "white"
+ border.color: "black"
+ border.width: 1
+ }
+ }
+
+ Item {
+ Layout.fillHeight: true
+ }
+
+ RowLayout {
+ id: rowLayout
+ width: 100
+ height: 100
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ CustomButton {
+ id: cancelButton
+ width: 90
+ height: 30
+ btnText: qsTr("Cancel")
+ btnBlue: false
+ }
+
+ CustomButton {
+ id: loginButton
+ width: 90
+ height: 30
+ btnText: qsTr("Login")
+ btnBlue: false
+ }
+ }
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/forms/ColorCell.qml b/tests/manual/examples/quick/customdialogs/forms/ColorCell.qml
new file mode 100644
index 000000000..57151780c
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/ColorCell.qml
@@ -0,0 +1,16 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Rectangle {
+ id: rectangle
+ width: 50
+ height: 50
+ signal clicked()
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ onClicked: rectangle.clicked()
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/forms/ColorPicker.qml b/tests/manual/examples/quick/customdialogs/forms/ColorPicker.qml
new file mode 100644
index 000000000..63269ddff
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/ColorPicker.qml
@@ -0,0 +1,31 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+ColorPickerForm {
+ property QtObject request
+ signal closeForm()
+
+ okButton.onClicked: {
+ request.dialogAccept(colorPicker.color);
+ closeForm();
+ }
+
+ cancelButton.onClicked: {
+ request.dialogReject();
+ closeForm();
+ }
+
+ function createCallback(color) {
+ return function() { colorPicker.color = color };
+ }
+
+ Component.onCompleted:{
+ for (var i = 0; i < grid.children.length; i++) {
+ var cell = grid.children[i];
+ cell.clicked.connect(createCallback(cell.color));
+ }
+ colorPicker.color = request.color;
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/forms/ColorPickerForm.ui.qml b/tests/manual/examples/quick/customdialogs/forms/ColorPickerForm.ui.qml
new file mode 100644
index 000000000..060aeef7d
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/ColorPickerForm.ui.qml
@@ -0,0 +1,186 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+
+Item {
+ property alias cancelButton: cancelButton
+ property alias okButton: okButton
+ property string message: "Message"
+ property string title: "Title"
+ property alias blue1: blue1
+ property alias grid: grid
+ property alias colorPicker: colorPicker
+
+ ColumnLayout {
+ id: columnLayout
+ anchors.topMargin: 20
+ anchors.top: parent.top
+ anchors.bottomMargin: 20
+ anchors.bottom: parent.bottom
+ anchors.rightMargin: 20
+ anchors.right: parent.right
+ anchors.leftMargin: 20
+ anchors.left: parent.left
+
+ Image {
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+ source: "qrc:/icon.svg"
+ }
+
+ Rectangle {
+ width: parent.width
+ height: 30
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+ gradient: Gradient {
+ GradientStop {
+ position: 0
+ color: "#25a6e2"
+ }
+
+ GradientStop {
+ color: "#188bd0"
+ }
+ }
+
+ Text {
+ id: title
+ x: 54
+ y: 5
+ color: "#ffffff"
+ text: qsTr("Select Color")
+ font.pointSize: 12
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ Item {
+ width: 40
+ height: 40
+ }
+
+ GridLayout {
+ id: grid
+ columns: 5
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+
+ ColorCell {
+ id: blue1
+ color: "#26d5f8"
+ }
+ ColorCell {
+ id: green1
+ color: "#25f93d"
+ }
+ ColorCell {
+ id: red1
+ color: "#f71111"
+ }
+ ColorCell {
+ id: yellow1
+ color: "#faf23c"
+ }
+ ColorCell {
+ id: orange1
+ color: "#ec8505"
+ }
+ ColorCell {
+ id: blue2
+ color: "#037eaa"
+ }
+ ColorCell {
+ id: green2
+ color: "#389a13"
+ }
+ ColorCell {
+ id: red2
+ color: "#b2001b"
+ }
+ ColorCell {
+ id: yellow2
+ color: "#caca03"
+ }
+ ColorCell {
+ id: orange2
+ color: "#bb4900"
+ }
+ ColorCell {
+ id: blue3
+ color: "#01506c"
+ }
+ ColorCell {
+ id: green3
+ color: "#37592b"
+ }
+ ColorCell {
+ id: red3
+ color: "#700113"
+ }
+ ColorCell {
+ id: yellow3
+ color: "#848404"
+ }
+
+ ColorCell {
+ id: orange3
+ color: "#563100"
+ }
+ }
+
+ Item {
+ width: 10
+ height: 10
+ }
+
+ Rectangle {
+ width: 90
+ height: 90
+ color: "#000000"
+ radius: 4
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+
+ Rectangle {
+ id: colorPicker
+ height: 80
+ color: "#ffffff"
+ anchors.rightMargin: 5
+ anchors.leftMargin: 5
+ anchors.bottomMargin: 5
+ anchors.topMargin: 5
+ anchors.fill: parent
+ }
+ }
+
+ Item {
+ Layout.fillHeight: true
+ }
+
+ RowLayout {
+ id: rowLayout
+ width: 100
+ height: 100
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ CustomButton {
+ id: cancelButton
+ width: 90
+ height: 30
+ btnText: qsTr("Cancel")
+ btnBlue: false
+ }
+
+ CustomButton {
+ id: okButton
+ width: 90
+ height: 30
+ btnText: qsTr("OK")
+ btnBlue: false
+ }
+ }
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/forms/CustomButton.qml b/tests/manual/examples/quick/customdialogs/forms/CustomButton.qml
new file mode 100644
index 000000000..00a06d558
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/CustomButton.qml
@@ -0,0 +1,61 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Rectangle {
+ id: root
+ width: 200
+ height: 30
+ radius: 5
+ property string btnText: "Name"
+ property bool btnEnable: true
+ property bool btnBlue: true
+ opacity: btnEnable ? 1.0 : 0.5
+ signal clicked()
+ gradient: btnBlue ? blueButton : greenButton
+ Text {
+ id: textArea
+ x: 54
+ y: 5
+ color: "#ffffff"
+ text: parent.btnText
+ font.pointSize: 12
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ font.bold: false
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ onClicked: {
+ if (btnEnable)
+ root.clicked();
+ }
+ }
+
+ Gradient {
+ id: blueButton
+ GradientStop {
+ position: 0
+ color: "#25a6e2"
+ }
+ GradientStop {
+ position: mouseArea.pressed && root.btnEnable ? 0.7 :1
+ color: "#188bd0"
+ }
+ }
+
+ Gradient {
+ id: greenButton
+ GradientStop {
+ position: 0
+ color: "#80c342"
+ }
+ GradientStop {
+ position: mouseArea.pressed && root.btnEnable ? 0.7 :1
+ color: "#5fac18"
+ }
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/forms/FilePicker.qml b/tests/manual/examples/quick/customdialogs/forms/FilePicker.qml
new file mode 100644
index 000000000..45ffefb3a
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/FilePicker.qml
@@ -0,0 +1,44 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+FilePickerForm {
+ property QtObject request
+ property string selectedFile
+ signal closeForm()
+
+ cancelButton.onClicked: {
+ request.dialogReject();
+ closeForm();
+ }
+
+ okButton.onClicked: {
+ request.dialogAccept('/' + selectedFile);
+ closeForm();
+ }
+
+ function createCallback(fileIndex) {
+ return function() {
+ for (var i = 0; i < files.children.length; i++) {
+ var file = files.children[i];
+ if (i === fileIndex) {
+ selectedFile = file.text;
+ file.selected = true;
+ } else {
+ file.selected = false;
+ }
+ }
+ }
+ }
+
+ Component.onCompleted: {
+ selectedFile = request.defaultFileName;
+ for (var i = 0; i < files.children.length; i++) {
+ var file = files.children[i];
+ file.clicked.connect(createCallback(i));
+ if (file.text === selectedFile)
+ file.selected = true;
+ }
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/forms/FilePickerForm.ui.qml b/tests/manual/examples/quick/customdialogs/forms/FilePickerForm.ui.qml
new file mode 100644
index 000000000..1e99b1a91
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/FilePickerForm.ui.qml
@@ -0,0 +1,128 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+
+Item {
+ property alias cancelButton: cancelButton
+ property alias okButton: okButton
+ property string message: "Message"
+ property string title: "Title"
+ property alias files: files
+
+ ColumnLayout {
+ id: columnLayout
+ anchors.topMargin: 20
+ anchors.top: parent.top
+ anchors.bottomMargin: 20
+ anchors.bottom: parent.bottom
+ anchors.rightMargin: 20
+ anchors.right: parent.right
+ anchors.leftMargin: 20
+ anchors.left: parent.left
+
+ Image {
+ id: image
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+ source: "qrc:/icon.svg"
+ }
+
+ Rectangle {
+ id: rectangle
+ width: parent.width
+ height: 30
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+ gradient: Gradient {
+ GradientStop {
+ position: 0
+ color: "#25a6e2"
+ }
+
+ GradientStop {
+ color: "#188bd0"
+ }
+ }
+
+ Text {
+ id: title
+ x: 54
+ y: 5
+ color: "#ffffff"
+ text: qsTr("Select File")
+ font.pointSize: 12
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ Item {
+ width: 40
+ height: 40
+ }
+
+ ColumnLayout {
+ id: files
+
+ FileRow {
+ id: filename1
+ text: "example.qdoc"
+ }
+
+ FileRow {
+ id: filename2
+ text: "factory.cpp"
+ }
+
+ FileRow {
+ id: filename3
+ text: "index.html"
+ }
+
+ FileRow {
+ id: filename4
+ text: "main.qml"
+ }
+
+ FileRow {
+ id: filename5
+ text: "qt-logo.png"
+ }
+
+ FileRow {
+ id: filename6
+ text: "window.h"
+ }
+ }
+
+ Item {
+ Layout.fillHeight: true
+ }
+
+ RowLayout {
+ id: rowLayout
+ width: 20
+ height: 100
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ CustomButton {
+ id: cancelButton
+ width: 90
+ height: 30
+ btnText: qsTr("Cancel")
+ btnBlue: false
+ }
+
+ CustomButton {
+ id: okButton
+ width: 90
+ height: 30
+ btnText: qsTr("OK")
+ btnBlue: false
+ }
+ }
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/forms/FileRow.qml b/tests/manual/examples/quick/customdialogs/forms/FileRow.qml
new file mode 100644
index 000000000..1a0cfc0a0
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/FileRow.qml
@@ -0,0 +1,44 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+
+Item {
+ id: root
+ height: 30
+ property string text: "Filename"
+ property bool selected: false
+ signal clicked()
+
+ RowLayout {
+ id: fileRow
+ width: 100
+
+ Item {
+ id: item5
+ width: 10
+ height: 10
+ }
+
+ Rectangle {
+ id: rectangle2
+ width: 10
+ height: 10
+ color: selected ? "#80c342" : "#25a6e2"
+ }
+
+ Text {
+ id: filename
+ text: root.text
+ font.pointSize: 12
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+ width: 200
+ height: 30
+ onClicked: root.clicked()
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/forms/JavaScript.qml b/tests/manual/examples/quick/customdialogs/forms/JavaScript.qml
new file mode 100644
index 000000000..132c95697
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/JavaScript.qml
@@ -0,0 +1,44 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtWebEngine
+
+JavaScriptForm {
+ property QtObject request
+ signal closeForm()
+
+ cancelButton.onClicked: {
+ request.dialogReject();
+ closeForm();
+ }
+
+ okButton.onClicked: {
+ request.dialogAccept(prompt.text);
+ closeForm();
+ }
+
+ Component.onCompleted: {
+ switch (request.type) {
+ case JavaScriptDialogRequest.DialogTypeAlert:
+ cancelButton.visible = false;
+ title = qsTr("Alert");
+ message = request.message;
+ prompt.text = "";
+ prompt.visible = false;
+ break;
+ case JavaScriptDialogRequest.DialogTypeConfirm:
+ title = qsTr("Confirm");
+ message = request.message;
+ prompt.text = "";
+ prompt.visible = false;
+ break;
+ case JavaScriptDialogRequest.DialogTypePrompt:
+ title = qsTr("Prompt");
+ message = request.message;
+ prompt.text = request.defaultText;
+ prompt.visible = true;
+ break;
+ }
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/forms/JavaScriptForm.ui.qml b/tests/manual/examples/quick/customdialogs/forms/JavaScriptForm.ui.qml
new file mode 100644
index 000000000..b535e7ef9
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/JavaScriptForm.ui.qml
@@ -0,0 +1,117 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+
+Item {
+ id: root
+ property alias cancelButton: cancelButton
+ property alias okButton: okButton
+ property string message: "Message"
+ property string title: "Title"
+ property alias prompt: prompt
+
+ ColumnLayout {
+ id: columnLayout
+ anchors.topMargin: 20
+ anchors.top: parent.top
+ anchors.bottomMargin: 20
+ anchors.bottom: parent.bottom
+ anchors.rightMargin: 20
+ anchors.right: parent.right
+ anchors.leftMargin: 20
+ anchors.left: parent.left
+
+ Image {
+ id: image
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+ source: "qrc:/icon.svg"
+ }
+
+ Rectangle {
+ id: rectangle
+ width: parent.width
+ height: 30
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+ gradient: Gradient {
+ GradientStop {
+ position: 0
+ color: "#25a6e2"
+ }
+
+ GradientStop {
+ color: "#188bd0"
+ }
+ }
+
+ Text {
+ id: title
+ x: 54
+ y: 5
+ color: "#ffffff"
+ text: qsTr("Title")
+ font.pointSize: 12
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ Item {
+ width: 40
+ height: 40
+ }
+
+ Text {
+ id: message
+ text: root.message
+ font.pointSize: 12
+ }
+
+ TextField {
+ id: prompt
+ width: 300
+ height: 22
+ Layout.fillWidth: true
+ font.pointSize: 12
+ color: "black"
+
+ background: Rectangle {
+ color: "white"
+ border.color: "black"
+ border.width: 1
+ }
+ }
+
+ Item {
+ Layout.fillHeight: true
+ }
+
+ RowLayout {
+ id: rowLayout
+ width: 100
+ height: 100
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ CustomButton {
+ id: cancelButton
+ width: 90
+ height: 30
+ btnText: qsTr("Cancel")
+ btnBlue: false
+ }
+
+ CustomButton {
+ id: okButton
+ width: 90
+ height: 30
+ btnText: qsTr("OK")
+ btnBlue: false
+ }
+ }
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/forms/Menu.qml b/tests/manual/examples/quick/customdialogs/forms/Menu.qml
new file mode 100644
index 000000000..b90802a0c
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/Menu.qml
@@ -0,0 +1,22 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+MenuForm {
+ property QtObject request
+ signal closeForm()
+
+ followLink.onClicked: closeForm()
+ back.onClicked: closeForm()
+ forward.onClicked: closeForm()
+ reload.onClicked: closeForm()
+ copyLinkUrl.onClicked: closeForm()
+ saveLink.onClicked: closeForm()
+ close.onClicked: closeForm()
+
+ Component.onCompleted: {
+ back.btnEnable = false;
+ forward.btnEnable = false;
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/forms/MenuForm.ui.qml b/tests/manual/examples/quick/customdialogs/forms/MenuForm.ui.qml
new file mode 100644
index 000000000..b4c06bb7d
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/MenuForm.ui.qml
@@ -0,0 +1,65 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+
+Item {
+ property alias followLink: followLink
+ property alias back: back
+ property alias forward: forward
+ property alias reload: reload
+ property alias copyLinkUrl: copyLinkUrl
+ property alias saveLink: saveLink
+ property alias close: close
+
+ ColumnLayout {
+ id: columnLayout
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Image {
+ id: image
+ width: 100
+ height: 100
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+ source: "qrc:/icon.svg"
+ }
+
+ CustomButton {
+ id: followLink
+ btnText: qsTr("Follow")
+ }
+
+ CustomButton {
+ id: back
+ btnText: qsTr("Back")
+ }
+
+ CustomButton {
+ id: forward
+ btnText: qsTr("Forward")
+ }
+
+ CustomButton {
+ id: reload
+ btnText: qsTr("Reload")
+ }
+
+ CustomButton {
+ id: copyLinkUrl
+ btnText: qsTr("Copy Link URL")
+ }
+
+ CustomButton {
+ id: saveLink
+ btnText: qsTr("Save Link")
+ }
+
+ CustomButton {
+ id: close
+ btnBlue: false
+ btnText: qsTr("Close")
+ }
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/forms/TouchSelectionMenu.qml b/tests/manual/examples/quick/customdialogs/forms/TouchSelectionMenu.qml
new file mode 100644
index 000000000..1b0c19789
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/TouchSelectionMenu.qml
@@ -0,0 +1,14 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+TouchSelectionMenuForm {
+ property QtObject request
+ signal closeForm()
+
+ cut.onClicked: closeForm()
+ copy.onClicked: closeForm()
+ paste.onClicked: closeForm()
+ contextMenu.onClicked: closeForm()
+}
diff --git a/tests/manual/examples/quick/customdialogs/forms/TouchSelectionMenuForm.ui.qml b/tests/manual/examples/quick/customdialogs/forms/TouchSelectionMenuForm.ui.qml
new file mode 100644
index 000000000..bed39566f
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/TouchSelectionMenuForm.ui.qml
@@ -0,0 +1,39 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+
+Item {
+ property alias cut: cut
+ property alias copy: copy
+ property alias paste: paste
+ property alias contextMenu: contextMenu
+
+ ColumnLayout {
+ id: columnLayout
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ CustomButton {
+ id: cut
+ btnText: qsTr("Cut")
+ }
+
+ CustomButton {
+ id: copy
+ btnText: qsTr("Copy")
+ }
+
+ CustomButton {
+ id: paste
+ btnText: qsTr("Paste")
+ }
+
+ CustomButton {
+ id: contextMenu
+ btnText: qsTr("...")
+ }
+
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/forms/forms.qmlproject b/tests/manual/examples/quick/customdialogs/forms/forms.qmlproject
new file mode 100644
index 000000000..b06afaaf1
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/forms/forms.qmlproject
@@ -0,0 +1,45 @@
+import QmlProject
+
+Project {
+ mainFile: "MenuForm.ui.qml"
+
+ /* Include .qml, .js, and image files from current directory and subdirectories */
+ QmlFiles {
+ directory: "."
+ }
+
+ JavaScriptFiles {
+ directory: "."
+ }
+
+ ImageFiles {
+ directory: "."
+ }
+
+ Files {
+ filter: "*.conf"
+ files: ["qtquickcontrols2.conf"]
+ }
+
+ Files {
+ filter: "qmldir"
+ directory: "."
+ }
+
+ Files {
+ filter: "*.ttf;*.otf"
+ }
+
+ Environment {
+ QT_QUICK_CONTROLS_CONF: "qtquickcontrols2.conf"
+ QT_AUTO_SCREEN_SCALE_FACTOR: "1"
+ }
+
+ qt6Project: true
+
+ /* List of plugin directories passed to QML runtime */
+ importPaths: [ ".", "imports" ]
+
+ /* Required for deployment */
+ targetDirectory: "/opt/forms"
+}
diff --git a/tests/manual/examples/quick/customdialogs/icon.svg b/tests/manual/examples/quick/customdialogs/icon.svg
new file mode 100644
index 000000000..48271180b
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/icon.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="94px" height="94px" viewBox="0 0 94 94" enable-background="new 0 0 94 94" xml:space="preserve">
+<g>
+ <circle fill="none" cx="47" cy="47" r="47"/>
+ <g>
+ <path fill="#46A2DA" d="M47,92.979c-11.779,0-23.559-4.484-32.526-13.451C-3.461,61.591-3.461,32.409,14.472,14.474
+ C32.41-3.463,61.592-3.461,79.526,14.473c17.935,17.936,17.935,47.119,0.002,65.054l-0.002,0.001
+ C70.559,88.495,58.779,92.979,47,92.979z"/>
+ </g>
+ <path fill="#80C342" d="M93,47C93,21.595,72.405,1,47,1C34.297,1,22.797,6.149,14.473,14.473l65.054,65.054
+ C87.851,71.203,93,59.703,93,47z"/>
+ <g>
+ <path fill="#46A2DA" d="M47,65c-4.808,0-9.328-1.873-12.728-5.272c-7.018-7.019-7.018-18.438,0-25.456
+ C37.672,30.873,42.192,29,47,29s9.328,1.873,12.728,5.272c7.018,7.019,7.018,18.438,0,25.456C56.328,63.127,51.808,65,47,65z"/>
+ <path fill="#FFFFFF" d="M62.248,59.919c6.671-7.858,6.312-19.644-1.105-27.061C57.237,28.953,52.118,27,47,27
+ c-5.118,0-10.237,1.953-14.142,5.858c-7.81,7.81-7.81,20.474,0,28.284C36.763,65.047,41.882,67,47,67
+ c4.379,0,8.752-1.441,12.372-4.3L77.88,81.209c0.989-0.895,1.935-1.837,2.843-2.814L62.248,59.919z M35.686,58.314
+ c-6.238-6.238-6.238-16.389,0-22.627C38.708,32.664,42.726,31,47,31c4.274,0,8.292,1.664,11.314,4.686
+ c6.238,6.238,6.238,16.389,0,22.627C55.292,61.336,51.274,63,47,63C42.726,63,38.708,61.336,35.686,58.314z"/>
+ </g>
+</g>
+</svg>
diff --git a/tests/manual/examples/quick/customdialogs/index.html b/tests/manual/examples/quick/customdialogs/index.html
new file mode 100644
index 000000000..d5de2827c
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/index.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Custom UI</title>
+ <link rel="stylesheet" type="text/css" href="style.css">
+ </head>
+ <body>
+ <table align="center">
+ <tr>
+ <td><div class="div"><a href="#openMenu" class="link">Right click on text to see link context menu</a></div></td>
+ </tr>
+ <tr>
+ <td><div class="div"><p title="I am a tooltip.">Hover this text to display a tooltip</a></div></td>
+ </tr>
+ <tr>
+ <td><div class="div"><p>Touch devices only: long press on this text to see the touch selection menu</p></div></td>
+ </tr>
+ <tr>
+ <td><button class="button" onclick="window.location = 'http://localhost.:5555/OPEN_AUTH'">
+ Open Authentication Dialog</button></td>
+ </tr>
+ <tr>
+ <td><button class="button" onclick="window.location = 'http://www.qt.io'">
+ Open Proxy Dialog</button></td>
+ </tr>
+ <tr>
+ <td><button class="button" onclick="alert('This is the Alert Dialog !')">
+ Open Alert Dialog</button></td>
+ </tr>
+ <tr>
+ <td><button class="button" onclick="confirm('This is the Confirm Dialog.')">
+ Open Confirm Dialog</button></td>
+ </tr>
+ <tr>
+ <td><button class="button" onclick="prompt('Is this the Prompt Dialog ?', 'Yes')">
+ Open Prompt Dialog</button></td>
+ </tr>
+ <tr>
+ <td><button class="button" onclick="document.getElementById('colorpicker').click()">
+ Open Color Dialog</button></td>
+ </tr>
+ <tr>
+ <td><button class="button" onclick="document.getElementById('filepicker').click()">
+ Open File Dialog</button></td>
+ </tr>
+ </table>
+ <input type="color" id="colorpicker" value="#ff0000" style="visibility:hidden"/>
+ <input type="file" id="filepicker" accept=".cpp, .html, .h, .png, .qdoc, .qml" style="visibility:hidden"/>
+ </body>
+</html>
diff --git a/tests/manual/examples/quick/customdialogs/main.cpp b/tests/manual/examples/quick/customdialogs/main.cpp
new file mode 100644
index 000000000..c114ea935
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/main.cpp
@@ -0,0 +1,32 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "server.h"
+#include <QtWebEngineQuick/qtwebenginequickglobal.h>
+#include <QNetworkProxy>
+#include <QQmlApplicationEngine>
+#include <QTimer>
+#include <QtGui/QGuiApplication>
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication::setOrganizationName("QtExamples");
+ QtWebEngineQuick::initialize();
+
+ QGuiApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+ Server *server = new Server(&engine);
+
+ engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+ QTimer::singleShot(0, server, &Server::run);
+
+ QNetworkProxy proxy;
+ proxy.setType(QNetworkProxy::HttpProxy);
+ proxy.setHostName("localhost");
+ proxy.setPort(5555);
+ QNetworkProxy::setApplicationProxy(proxy);
+
+ return app.exec();
+}
+
diff --git a/tests/manual/examples/quick/customdialogs/main.qml b/tests/manual/examples/quick/customdialogs/main.qml
new file mode 100644
index 000000000..d0cb6f324
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/main.qml
@@ -0,0 +1,56 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Window
+
+Window {
+ id: mainWindow
+ width: 800
+ height: 610
+ visible: true
+
+ StackView {
+ id: stackView
+ anchors.fill: parent
+ focus: true
+ initialItem: Item {
+ id: main
+ width: mainWindow.width
+ height: mainWindow.height
+ ColumnLayout {
+ anchors.fill: parent
+ SwitchButton {
+ id: switcher
+ Layout.fillWidth: true
+ }
+ WebView {
+ id: webView
+ useDefaultDialogs: switcher.checked
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+ }
+ }
+
+ function closeForm()
+ {
+ pop(main);
+ // reset url in case of proxy error
+ webView.url = "qrc:/index.html"
+ }
+
+ function openForm(form)
+ {
+ push(form.item, form.properties);
+ currentItem.closeForm.connect(closeForm);
+ }
+
+ }
+
+ Component.onCompleted: {
+ webView.openForm.connect(stackView.openForm);
+ }
+}
diff --git a/tests/manual/examples/quick/customdialogs/server.cpp b/tests/manual/examples/quick/customdialogs/server.cpp
new file mode 100644
index 000000000..efb870618
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/server.cpp
@@ -0,0 +1,47 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "server.h"
+#include <QDataStream>
+#include <QTcpSocket>
+
+Server::Server(QObject *parent) : QObject(parent)
+{
+ connect(&m_server, &QTcpServer::newConnection, this, &Server::handleNewConnection);
+}
+
+void Server::run()
+{
+ if (!m_server.listen(QHostAddress::LocalHost, 5555))
+ qWarning() << "Could not start the server -> http/proxy authentication dialog"
+ " will not work. Error:" << m_server.errorString();
+}
+
+void Server::handleNewConnection()
+{
+ QTcpSocket *socket = m_server.nextPendingConnection();
+ connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater);
+ connect(socket, &QAbstractSocket::readyRead, this, &Server::handleReadReady);
+}
+
+void Server::handleReadReady()
+{
+ QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
+ Q_ASSERT(socket);
+ m_data.append(socket->readAll());
+
+ // simply wait for whole request
+ if (!m_data.endsWith("\r\n\r\n"))
+ return;
+ if (m_data.contains(QByteArrayLiteral("OPEN_AUTH"))) {
+ socket->write("HTTP/1.1 401 Unauthorized\nWWW-Authenticate: "
+ "Basic realm=\"Very Restricted Area\"\r\n\r\n");
+ m_data.clear();
+ return;
+ }
+
+ socket->write("HTTP/1.1 407 Proxy Auth Required\nProxy-Authenticate: "
+ "Basic realm=\"Proxy requires authentication\"\r\n"
+ "content-length: 0\r\n\r\n");
+ m_data.clear();
+}
diff --git a/tests/manual/examples/quick/customdialogs/server.h b/tests/manual/examples/quick/customdialogs/server.h
new file mode 100644
index 000000000..563465013
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/server.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef SERVER_H
+#define SERVER_H
+
+#include <QObject>
+#include <QTcpServer>
+
+class Server : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit Server(QObject *parent = nullptr);
+
+public slots:
+ void run();
+
+private slots:
+ void handleNewConnection();
+ void handleReadReady();
+
+private:
+ QTcpServer m_server;
+ QByteArray m_data;
+};
+
+#endif // SERVER_H
diff --git a/tests/manual/examples/quick/customdialogs/style.css b/tests/manual/examples/quick/customdialogs/style.css
new file mode 100644
index 000000000..e4c25e7eb
--- /dev/null
+++ b/tests/manual/examples/quick/customdialogs/style.css
@@ -0,0 +1,37 @@
+.div {
+ padding:8px 4px;
+ border: 5px solid #188BD0;
+ width: 280px;
+ font-family: sans-serif;
+ font-size:10pt;
+}
+.link {
+ text-decoration: none;
+ color: #888888;
+}
+.button {
+ background: -webkit-linear-gradient(top,#25A6E2 0%,#188BD0 100%);
+ padding:8px 13px;
+ color:#fff;
+ font-family: sans-serif;
+ font-size:17px;
+ -webkit-border-radius:5px;
+ border:1px solid #1A87FF;
+ width: 300px;
+}
+.button:focus {
+ outline: none;
+}
+.button:active {
+ background: -webkit-linear-gradient(top,#25A6E2 0%,#188BD0 70%);
+}
+.input {
+ padding:8px 4px;
+ border: 5px solid #188BD0;
+ width: 280px;
+ font-family: sans-serif;
+ font-size:10pt;
+}
+.input:focus {
+ outline: none;
+}
diff --git a/tests/manual/examples/quick/customtouchhandle/CMakeLists.txt b/tests/manual/examples/quick/customtouchhandle/CMakeLists.txt
new file mode 100644
index 000000000..5574c28b8
--- /dev/null
+++ b/tests/manual/examples/quick/customtouchhandle/CMakeLists.txt
@@ -0,0 +1,36 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(customtouchhandle LANGUAGES CXX)
+ find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_manual_test(customtouchhandle
+ SOURCES main.cpp
+)
+
+set_target_properties(customtouchhandle PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(customtouchhandle PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::WebEngineQuick
+)
+
+# Resources:
+set(qml_resource_files
+ "main.qml"
+)
+
+qt6_add_resources(customtouchhandle "qml"
+ PREFIX
+ "/"
+ FILES
+ ${qml_resource_files}
+)
+
diff --git a/tests/manual/examples/quick/customtouchhandle/customtouchhandle.pro b/tests/manual/examples/quick/customtouchhandle/customtouchhandle.pro
new file mode 100644
index 000000000..a74ef3146
--- /dev/null
+++ b/tests/manual/examples/quick/customtouchhandle/customtouchhandle.pro
@@ -0,0 +1,10 @@
+TEMPLATE = app
+
+QT += webenginequick
+
+SOURCES += main.cpp
+
+RESOURCES += qml.qrc
+
+target.path = $$[QT_INSTALL_EXAMPLES]/webenginequick/customtouchhandle
+INSTALLS += target
diff --git a/tests/manual/examples/quick/customtouchhandle/main.cpp b/tests/manual/examples/quick/customtouchhandle/main.cpp
new file mode 100644
index 000000000..f1b70b024
--- /dev/null
+++ b/tests/manual/examples/quick/customtouchhandle/main.cpp
@@ -0,0 +1,19 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QtWebEngineQuick/qtwebenginequickglobal.h>
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication::setOrganizationName("QtExamples");
+ QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
+ QtWebEngineQuick::initialize();
+ QGuiApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+ engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+
+ return app.exec();
+}
diff --git a/tests/manual/examples/quick/customtouchhandle/main.qml b/tests/manual/examples/quick/customtouchhandle/main.qml
new file mode 100644
index 000000000..c40b4c73b
--- /dev/null
+++ b/tests/manual/examples/quick/customtouchhandle/main.qml
@@ -0,0 +1,96 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Window
+import QtWebEngine
+import QtQuick.Layouts
+import QtQuick.Controls
+
+ApplicationWindow {
+ width: 1024
+ height: 750
+ visible: true
+ header: ToolBar {
+ RowLayout {
+ anchors.fill: parent
+
+ ToolButton {
+ property int itemAction: WebEngineView.Back
+ text: webEngineView.action(itemAction).text
+ enabled: webEngineView.action(itemAction).enabled
+ onClicked: webEngineView.action(itemAction).trigger()
+ icon.name: webEngineView.action(itemAction).iconName
+ display: AbstractButton.TextUnderIcon
+ }
+
+ ToolButton {
+ property int itemAction: WebEngineView.Forward
+ text: webEngineView.action(itemAction).text
+ enabled: webEngineView.action(itemAction).enabled
+ onClicked: webEngineView.action(itemAction).trigger()
+ icon.name: webEngineView.action(itemAction).iconName
+ display: AbstractButton.TextUnderIcon
+ }
+
+ ToolButton {
+ property int itemAction: webEngineView.loading ? WebEngineView.Stop : WebEngineView.Reload
+ text: webEngineView.action(itemAction).text
+ enabled: webEngineView.action(itemAction).enabled
+ onClicked: webEngineView.action(itemAction).trigger()
+ icon.name: webEngineView.action(itemAction).iconName
+ display: AbstractButton.TextUnderIcon
+ }
+
+ TextField {
+ Layout.fillWidth: true
+ text: webEngineView.url
+ selectByMouse: true
+ onEditingFinished: webEngineView.url = text
+ }
+
+ Label { text: 'Handle: ' }
+ ComboBox {
+ model: [ 'Default', 'Circle', 'Square' ]
+
+ onCurrentValueChanged: {
+ if (currentValue == 'Circle')
+ webEngineView.touchHandleDelegate = circleTouchHandle
+ else if (currentValue == 'Square')
+ webEngineView.touchHandleDelegate = rectTouchHandle
+ else
+ webEngineView.touchHandleDelegate = null
+ }
+
+ Component.onCompleted: currentIndex = indexOfValue('Square')
+ }
+ }
+ }
+
+ Component {
+ id: circleTouchHandle
+ Rectangle {
+ color: "blue"
+ border.color: "black"
+ border.width: 2
+ radius: 50
+ }
+ }
+
+ Component {
+ id: rectTouchHandle
+ Rectangle {
+ border.color: "black"
+ border.width: 2
+ radius: 2
+ onVisibleChanged: if (visible) { color = 'yellow'; cAnim.restart(); }
+ ColorAnimation on color { id: cAnim; to: 'red'; duration: 1000 }
+ }
+ }
+
+ WebEngineView {
+ anchors.fill: parent
+ id: webEngineView
+ url: "https://www.qt.io"
+ }
+}
diff --git a/tests/manual/examples/quick/customtouchhandle/qml.qrc b/tests/manual/examples/quick/customtouchhandle/qml.qrc
new file mode 100644
index 000000000..5f6483ac3
--- /dev/null
+++ b/tests/manual/examples/quick/customtouchhandle/qml.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>main.qml</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/examples/quick/minimal/CMakeLists.txt b/tests/manual/examples/quick/minimal/CMakeLists.txt
new file mode 100644
index 000000000..42e2078f7
--- /dev/null
+++ b/tests/manual/examples/quick/minimal/CMakeLists.txt
@@ -0,0 +1,36 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(minimal LANGUAGES CXX)
+ find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_manual_test(webengine-minimal-qml
+ SOURCES
+ main.cpp
+)
+
+set_target_properties(webengine-minimal-qml PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(webengine-minimal-qml PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::WebEngineQuick
+)
+
+# Resources:
+set(qml_resource_files
+ "main.qml"
+)
+
+qt_add_resources(webengine-minimal-qml "qml"
+ PREFIX
+ "/"
+ FILES
+ ${qml_resource_files}
+)
diff --git a/tests/manual/examples/quick/minimal/main.cpp b/tests/manual/examples/quick/minimal/main.cpp
new file mode 100644
index 000000000..16466ae06
--- /dev/null
+++ b/tests/manual/examples/quick/minimal/main.cpp
@@ -0,0 +1,19 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QtWebEngineQuick/qtwebenginequickglobal.h>
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication::setOrganizationName("QtExamples");
+ QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
+ QtWebEngineQuick::initialize();
+ QGuiApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+ engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+
+ return app.exec();
+}
diff --git a/tests/manual/examples/quick/minimal/main.qml b/tests/manual/examples/quick/minimal/main.qml
new file mode 100644
index 000000000..6890b501b
--- /dev/null
+++ b/tests/manual/examples/quick/minimal/main.qml
@@ -0,0 +1,16 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Window
+import QtWebEngine
+
+Window {
+ width: 1024
+ height: 750
+ visible: true
+ WebEngineView {
+ anchors.fill: parent
+ url: "chrome://qt"
+ }
+}
diff --git a/tests/manual/examples/quick/minimal/minimal.pro b/tests/manual/examples/quick/minimal/minimal.pro
new file mode 100644
index 000000000..acca6477c
--- /dev/null
+++ b/tests/manual/examples/quick/minimal/minimal.pro
@@ -0,0 +1,10 @@
+TEMPLATE = app
+
+QT += webenginequick
+
+SOURCES += main.cpp
+
+RESOURCES += qml.qrc
+
+target.path = $$[QT_INSTALL_EXAMPLES]/webenginequick/minimal
+INSTALLS += target
diff --git a/tests/manual/examples/quick/minimal/qml.qrc b/tests/manual/examples/quick/minimal/qml.qrc
new file mode 100644
index 000000000..0ff3892d9
--- /dev/null
+++ b/tests/manual/examples/quick/minimal/qml.qrc
@@ -0,0 +1,6 @@
+<RCC>
+ <qresource prefix="/">
+ <file>main.qml</file>
+ </qresource>
+</RCC>
+
diff --git a/tests/manual/examples/quick/webengineaction/CMakeLists.txt b/tests/manual/examples/quick/webengineaction/CMakeLists.txt
new file mode 100644
index 000000000..e16772faf
--- /dev/null
+++ b/tests/manual/examples/quick/webengineaction/CMakeLists.txt
@@ -0,0 +1,37 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(webengineaction LANGUAGES CXX)
+ find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_manual_test(webengineaction
+ SOURCES
+ main.cpp
+ utils.h
+)
+
+set_target_properties(webengineaction PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(webengineaction PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::WebEngineQuick
+)
+
+# Resources:
+set(qml_resource_files
+ "main.qml"
+)
+
+qt_add_resources(webengineaction "qml"
+ PREFIX
+ "/"
+ FILES
+ ${qml_resource_files}
+)
diff --git a/tests/manual/examples/quick/webengineaction/main.cpp b/tests/manual/examples/quick/webengineaction/main.cpp
new file mode 100644
index 000000000..a7bfaaf36
--- /dev/null
+++ b/tests/manual/examples/quick/webengineaction/main.cpp
@@ -0,0 +1,25 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "utils.h"
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QQmlContext>
+#include <QtWebEngineQuick/qtwebenginequickglobal.h>
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication::setOrganizationName("QtExamples");
+ QtWebEngineQuick::initialize();
+
+ QGuiApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+ Utils utils;
+
+ engine.rootContext()->setContextProperty("utils", &utils);
+ engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+
+ return app.exec();
+}
diff --git a/tests/manual/examples/quick/webengineaction/main.qml b/tests/manual/examples/quick/webengineaction/main.qml
new file mode 100644
index 000000000..a1483b126
--- /dev/null
+++ b/tests/manual/examples/quick/webengineaction/main.qml
@@ -0,0 +1,119 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Window
+import QtWebEngine
+import QtQuick.Controls
+import QtQuick.Layouts
+
+ApplicationWindow {
+ id: window
+ visible: true
+ width: 800
+ height: 600
+ title: qsTr("WebEngineAction Example")
+
+ header: ToolBar {
+ RowLayout {
+ anchors.fill: parent
+
+ ToolButton {
+ property int itemAction: WebEngineView.Back
+ text: webEngineView.action(itemAction).text
+ enabled: webEngineView.action(itemAction).enabled
+ onClicked: webEngineView.action(itemAction).trigger()
+ icon.name: webEngineView.action(itemAction).iconName
+ display: AbstractButton.TextUnderIcon
+ }
+
+ ToolButton {
+ property int itemAction: WebEngineView.Forward
+ text: webEngineView.action(itemAction).text
+ enabled: webEngineView.action(itemAction).enabled
+ onClicked: webEngineView.action(itemAction).trigger()
+ icon.name: webEngineView.action(itemAction).iconName
+ display: AbstractButton.TextUnderIcon
+ }
+
+ ToolButton {
+ property int itemAction: webEngineView.loading ? WebEngineView.Stop : WebEngineView.Reload
+ text: webEngineView.action(itemAction).text
+ enabled: webEngineView.action(itemAction).enabled
+ onClicked: webEngineView.action(itemAction).trigger()
+ icon.name: webEngineView.action(itemAction).iconName
+ display: AbstractButton.TextUnderIcon
+ }
+
+ TextField {
+ Layout.fillWidth: true
+
+ text: webEngineView.url
+ selectByMouse: true
+ onEditingFinished: webEngineView.url = utils.fromUserInput(text)
+ }
+
+ ToolButton {
+ id: settingsButton
+ text: "Settings"
+ icon.name: "settings-configure"
+ display: AbstractButton.TextUnderIcon
+ onClicked: settingsMenu.open()
+ checked: settingsMenu.visible
+
+ Menu {
+ id: settingsMenu
+ y: settingsButton.height
+
+ MenuItem {
+ id: customContextMenuOption
+ checkable: true
+ checked: true
+
+ text: "Custom context menu"
+ }
+ }
+ }
+ }
+ }
+
+ WebEngineView {
+ id: webEngineView
+ url: "https://qt.io"
+ anchors.fill: parent
+
+ Component.onCompleted: {
+ profile.downloadRequested.connect(function(download){
+ download.accept();
+ })
+ }
+
+ property Menu contextMenu: Menu {
+ Repeater {
+ model: [
+ WebEngineView.Back,
+ WebEngineView.Forward,
+ WebEngineView.Reload,
+ WebEngineView.SavePage,
+ WebEngineView.Copy,
+ WebEngineView.Paste,
+ WebEngineView.Cut
+ ]
+ MenuItem {
+ text: webEngineView.action(modelData).text
+ enabled: webEngineView.action(modelData).enabled
+ onClicked: webEngineView.action(modelData).trigger()
+ icon.name: webEngineView.action(modelData).iconName
+ display: MenuItem.TextBesideIcon
+ }
+ }
+ }
+
+ onContextMenuRequested: function(request) {
+ if (customContextMenuOption.checked) {
+ request.accepted = true;
+ contextMenu.popup();
+ }
+ }
+ }
+}
diff --git a/tests/manual/examples/quick/webengineaction/qml.qrc b/tests/manual/examples/quick/webengineaction/qml.qrc
new file mode 100644
index 000000000..5f6483ac3
--- /dev/null
+++ b/tests/manual/examples/quick/webengineaction/qml.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>main.qml</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/quick/touchbrowser/utils.h b/tests/manual/examples/quick/webengineaction/utils.h
index 605ebf23d..d9a803907 100644
--- a/tests/manual/quick/touchbrowser/utils.h
+++ b/tests/manual/examples/quick/webengineaction/utils.h
@@ -1,5 +1,5 @@
-// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef UTILS_H
#define UTILS_H
@@ -7,13 +7,14 @@
#include <QtCore/QFileInfo>
#include <QtCore/QUrl>
-class Utils : public QObject {
+class Utils : public QObject
+{
Q_OBJECT
public:
- Q_INVOKABLE static QUrl fromUserInput(const QString& userInput);
+ Q_INVOKABLE static QUrl fromUserInput(const QString &userInput);
};
-inline QUrl Utils::fromUserInput(const QString& userInput)
+inline QUrl Utils::fromUserInput(const QString &userInput)
{
QFileInfo fileInfo(userInput);
if (fileInfo.exists())
diff --git a/tests/manual/examples/quick/webengineaction/webengineaction.pro b/tests/manual/examples/quick/webengineaction/webengineaction.pro
new file mode 100644
index 000000000..9286604a1
--- /dev/null
+++ b/tests/manual/examples/quick/webengineaction/webengineaction.pro
@@ -0,0 +1,8 @@
+TEMPLATE = app
+
+QT += webenginequick
+
+HEADERS += utils.h
+SOURCES += main.cpp
+
+RESOURCES += qml.qrc
diff --git a/tests/manual/examples/widgets/CMakeLists.txt b/tests/manual/examples/widgets/CMakeLists.txt
new file mode 100644
index 000000000..d853ba634
--- /dev/null
+++ b/tests/manual/examples/widgets/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(minimal)
diff --git a/tests/manual/examples/widgets/minimal/CMakeLists.txt b/tests/manual/examples/widgets/minimal/CMakeLists.txt
new file mode 100644
index 000000000..3b39683ba
--- /dev/null
+++ b/tests/manual/examples/widgets/minimal/CMakeLists.txt
@@ -0,0 +1,24 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(minimal LANGUAGES CXX)
+ find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
+ endif()
+
+qt_internal_add_manual_test(minimal-widgets
+ SOURCES
+ main.cpp
+)
+
+set_target_properties(minimal-widgets PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(minimal-widgets PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::WebEngineWidgets
+)
diff --git a/tests/manual/examples/widgets/minimal/main.cpp b/tests/manual/examples/widgets/minimal/main.cpp
new file mode 100644
index 000000000..425973116
--- /dev/null
+++ b/tests/manual/examples/widgets/minimal/main.cpp
@@ -0,0 +1,28 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QApplication>
+#include <QWebEngineView>
+
+QUrl commandLineUrlArgument()
+{
+ const QStringList args = QCoreApplication::arguments();
+ for (const QString &arg : args.mid(1)) {
+ if (!arg.startsWith(QLatin1Char('-')))
+ return QUrl::fromUserInput(arg);
+ }
+ return QUrl(QStringLiteral("chrome://qt"));
+}
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication::setOrganizationName("QtExamples");
+ QApplication app(argc, argv);
+
+ QWebEngineView view;
+ view.setUrl(commandLineUrlArgument());
+ view.resize(1024, 750);
+ view.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/examples/widgets/minimal/minimal.pro b/tests/manual/examples/widgets/minimal/minimal.pro
new file mode 100644
index 000000000..849f4b9b6
--- /dev/null
+++ b/tests/manual/examples/widgets/minimal/minimal.pro
@@ -0,0 +1,8 @@
+TEMPLATE = app
+
+QT += webenginewidgets
+
+SOURCES += main.cpp
+
+target.path = $$[QT_INSTALL_EXAMPLES]/webenginewidgets/minimal
+INSTALLS += target
diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro
deleted file mode 100644
index edf95846c..000000000
--- a/tests/manual/manual.pro
+++ /dev/null
@@ -1,7 +0,0 @@
-TEMPLATE = subdirs
-
-SUBDIRS += \
- widgets \
- quick
-
-!qtHaveModule(webenginewidgets): SUBDIRS -= widgets
diff --git a/tests/manual/quick/CMakeLists.txt b/tests/manual/quick/CMakeLists.txt
index 0562237b4..d6c4b88a9 100644
--- a/tests/manual/quick/CMakeLists.txt
+++ b/tests/manual/quick/CMakeLists.txt
@@ -1 +1,2 @@
add_subdirectory(touchbrowser)
+add_subdirectory(geopermission)
diff --git a/tests/manual/quick/geopermission/CMakeLists.txt b/tests/manual/quick/geopermission/CMakeLists.txt
new file mode 100644
index 000000000..088f248e1
--- /dev/null
+++ b/tests/manual/quick/geopermission/CMakeLists.txt
@@ -0,0 +1,63 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_geopermission LANGUAGES CXX)
+ find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_manual_test(tst_geopermission
+ SOURCES
+ main.cpp
+ LIBRARIES
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ Qt::WebEngineQuick
+)
+
+if(WIN32)
+ set_property(
+ TARGET tst_geopermission
+ APPEND PROPERTY
+ SOURCES tst_geopermission.exe.manifest)
+endif()
+
+set_target_properties(tst_geopermission PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+ MACOSX_BUNDLE_GUI_IDENTIFIER "io.qt.dev.webenginequick.tst_geopermission"
+)
+
+# Resources:
+set(resources_resource_files
+ "tst_geopermission.qml"
+ "geolocation.html"
+)
+
+qt_add_resources(tst_geopermission "resources"
+ PREFIX
+ "/"
+ FILES
+ ${resources_resource_files}
+)
+
+foreach(permission_plugin IN LISTS QT_ALL_PLUGINS_FOUND_BY_FIND_PACKAGE_permissions)
+ set(permission_plugin "${QT_CMAKE_EXPORT_NAMESPACE}::${permission_plugin}")
+ qt6_import_plugins(tst_geopermission INCLUDE ${permission_plugin})
+endforeach()
+
+if (APPLE)
+ set_target_properties(tst_geopermission PROPERTIES
+ MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist"
+ )
+
+ if (NOT CMAKE_GENERATOR STREQUAL "Xcode")
+ # Need to sign application for location permissions to work
+ add_custom_command(TARGET tst_geopermission
+ POST_BUILD COMMAND codesign -s - tst_geopermission.app)
+ endif()
+endif()
+
diff --git a/tests/manual/quick/geopermission/Info.plist b/tests/manual/quick/geopermission/Info.plist
new file mode 100644
index 000000000..9853e1900
--- /dev/null
+++ b/tests/manual/quick/geopermission/Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleName</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
+ <key>CFBundleExecutable</key>
+ <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
+ <key>CFBundleVersion</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
+ <key>CFBundleIconFile</key>
+ <string>${MACOSX_BUNDLE_ICON_FILE}</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+ <key>NSLocationUsageDescription</key>
+ <string>Geolocation test would like to give web sites access to your location for demo purposes.</string>
+</dict>
+</plist>
diff --git a/tests/manual/quick/geopermission/geolocation.html b/tests/manual/quick/geopermission/geolocation.html
new file mode 100644
index 000000000..e8c54bc58
--- /dev/null
+++ b/tests/manual/quick/geopermission/geolocation.html
@@ -0,0 +1,32 @@
+<html>
+<head>
+<title>Geolocation Permission API Test</title>
+<script>
+
+var errorMessage;
+var handled = false;
+
+function successHandler(location) {
+ var message = document.getElementById("message");
+ message.innerHTML = "Latitude: " + location.coords.latitude +
+ "<br>Longitude: " + location.coords.longitude;
+
+ errorMessage = "";
+ handled = true;
+}
+
+function errorHandler(error) {
+ errorMessage = error.message;
+ handled = true;
+}
+
+<!-- One shot example -->
+navigator.geolocation.getCurrentPosition(successHandler, errorHandler);
+
+</script>
+</head>
+<body>
+<div id="message">Location unknown</div>
+</body>
+</html>
+
diff --git a/tests/manual/quick/geopermission/main.cpp b/tests/manual/quick/geopermission/main.cpp
new file mode 100644
index 000000000..e0ff6f3e7
--- /dev/null
+++ b/tests/manual/quick/geopermission/main.cpp
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QtWebEngineQuick/qtwebenginequickglobal.h>
+
+#include <QtQml/QQmlApplicationEngine>
+#include <QtQml/QQmlContext>
+#include <QQuickView>
+
+#include <QtGui/QGuiApplication>
+
+#include <QtCore/QCommandLineParser>
+#include <QtCore/QCommandLineOption>
+#include <QtCore/QLoggingCategory>
+
+int main(int argc, char **argv)
+{
+ QCoreApplication::setApplicationName("Geopermission test");
+ QCoreApplication::setOrganizationName("QtProject");
+
+ QtWebEngineQuick::initialize();
+
+ QGuiApplication app(argc, argv);
+
+ QQuickView view;
+
+ view.setTitle("Touch Browser");
+ view.setFlags(Qt::Window | Qt::WindowTitleHint);
+ view.setResizeMode(QQuickView::SizeRootObjectToView);
+ view.setSource(QUrl("qrc:/tst_geopermission.qml"));
+
+ QObject::connect(view.engine(), SIGNAL(quit()), &app, SLOT(quit()));
+
+ view.show();
+ if (view.size().isEmpty())
+ view.setGeometry(0, 0, 800, 600);
+
+ return app.exec();
+}
diff --git a/tests/manual/quick/geopermission/tst_geopermission.qml b/tests/manual/quick/geopermission/tst_geopermission.qml
new file mode 100644
index 000000000..36317c176
--- /dev/null
+++ b/tests/manual/quick/geopermission/tst_geopermission.qml
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import QtQuick
+import QtTest
+import QtWebEngine
+
+WebEngineView {
+ id: webEngineView
+ width: 200
+ height: 200
+ url: Qt.resolvedUrl("qrc:/geolocation.html")
+ property bool deniedGeolocation: false
+ property bool geoPermissionRequested: false
+
+ onFeaturePermissionRequested: function(securityOrigin, feature) {
+ if (feature === WebEngineView.Geolocation) {
+ geoPermissionRequested = true
+ if (deniedGeolocation) {
+ webEngineView.grantFeaturePermission(securityOrigin, feature, false)
+ }
+ else {
+ webEngineView.grantFeaturePermission(securityOrigin, feature, true)
+ }
+ }
+ }
+
+}
diff --git a/tests/manual/quick/pdf/multipleDocuments.qml b/tests/manual/quick/pdf/multipleDocuments.qml
index 9d08178f0..055808ab6 100644
--- a/tests/manual/quick/pdf/multipleDocuments.qml
+++ b/tests/manual/quick/pdf/multipleDocuments.qml
@@ -44,7 +44,7 @@ ApplicationWindow {
action: Action {
shortcut: StandardKey.ZoomIn
enabled: pageView.sourceSize.width < 10000
- icon.source: "../../../../examples/pdf/pdfviewer/resources/zoom-in.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/zoom-in.svg"
onTriggered: pageView.renderScale *= root.scaleStep
}
}
@@ -52,20 +52,20 @@ ApplicationWindow {
action: Action {
shortcut: StandardKey.ZoomOut
enabled: pageView.sourceSize.width > 50
- icon.source: "../../../../examples/pdf/pdfviewer/resources/zoom-out.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/zoom-out.svg"
onTriggered: pageView.renderScale /= root.scaleStep
}
}
ToolButton {
action: Action {
shortcut: "Ctrl+0"
- icon.source: "../../../../examples/pdf/pdfviewer/resources/zoom-original.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/zoom-original.svg"
onTriggered: pageView.resetScale()
}
}
ToolButton {
action: Action {
- icon.source: "../../../../examples/pdf/pdfviewer/resources/go-previous-view-page.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/go-previous-view-page.svg"
enabled: pageView.backEnabled
onTriggered: pageView.back()
}
@@ -84,7 +84,7 @@ ApplicationWindow {
}
ToolButton {
action: Action {
- icon.source: "../../../../examples/pdf/pdfviewer/resources/go-next-view-page.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/go-next-view-page.svg"
enabled: pageView.forwardEnabled
onTriggered: pageView.forward()
}
diff --git a/tests/manual/quick/pdf/pdfPageView.qml b/tests/manual/quick/pdf/pdfPageView.qml
index 475e1d608..22c0d5ac2 100644
--- a/tests/manual/quick/pdf/pdfPageView.qml
+++ b/tests/manual/quick/pdf/pdfPageView.qml
@@ -24,7 +24,7 @@ ApplicationWindow {
ToolButton {
action: Action {
shortcut: StandardKey.Open
- icon.source: "../../../../examples/pdf/pdfviewer/resources/document-open.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/document-open.svg"
onTriggered: fileDialog.open()
}
}
@@ -32,7 +32,7 @@ ApplicationWindow {
action: Action {
shortcut: StandardKey.ZoomIn
enabled: pageView.sourceSize.width < 10000
- icon.source: "../../../../examples/pdf/pdfviewer/resources/zoom-in.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/zoom-in.svg"
onTriggered: pageView.renderScale *= root.scaleStep
}
}
@@ -40,46 +40,46 @@ ApplicationWindow {
action: Action {
shortcut: StandardKey.ZoomOut
enabled: pageView.sourceSize.width > 50
- icon.source: "../../../../examples/pdf/pdfviewer/resources/zoom-out.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/zoom-out.svg"
onTriggered: pageView.renderScale /= root.scaleStep
}
}
ToolButton {
action: Action {
- icon.source: "../../../../examples/pdf/pdfviewer/resources/zoom-fit-width.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/zoom-fit-width.svg"
onTriggered: pageView.scaleToWidth(root.contentItem.width, root.contentItem.height)
}
}
ToolButton {
action: Action {
- icon.source: "../../../../examples/pdf/pdfviewer/resources/zoom-fit-best.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/zoom-fit-best.svg"
onTriggered: pageView.scaleToPage(root.contentItem.width, root.contentItem.height)
}
}
ToolButton {
action: Action {
shortcut: "Ctrl+0"
- icon.source: "../../../../examples/pdf/pdfviewer/resources/zoom-original.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/zoom-original.svg"
onTriggered: pageView.resetScale()
}
}
ToolButton {
action: Action {
shortcut: "Ctrl+L"
- icon.source: "../../../../examples/pdf/pdfviewer/resources/rotate-left.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/rotate-left.svg"
onTriggered: pageView.rotation -= 90
}
}
ToolButton {
action: Action {
shortcut: "Ctrl+R"
- icon.source: "../../../../examples/pdf/pdfviewer/resources/rotate-right.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/rotate-right.svg"
onTriggered: pageView.rotation += 90
}
}
ToolButton {
action: Action {
- icon.source: "../../../../examples/pdf/pdfviewer/resources/go-previous-view-page.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/go-previous-view-page.svg"
enabled: pageView.backEnabled
onTriggered: pageView.back()
}
@@ -115,7 +115,7 @@ ApplicationWindow {
}
ToolButton {
action: Action {
- icon.source: "../../../../examples/pdf/pdfviewer/resources/go-next-view-page.svg"
+ icon.source: "../../../../examples/pdf//resources/go-next-view-page.svg"
enabled: pageView.forwardEnabled
onTriggered: pageView.forward()
}
@@ -126,7 +126,7 @@ ApplicationWindow {
ToolButton {
action: Action {
shortcut: StandardKey.Copy
- icon.source: "../../../../examples/pdf/pdfviewer/resources/edit-copy.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/edit-copy.svg"
enabled: pageView.selectedText !== ""
onTriggered: pageView.copySelectionToClipboard()
}
@@ -246,7 +246,7 @@ ApplicationWindow {
RowLayout {
ToolButton {
action: Action {
- icon.source: "../../../../examples/pdf/pdfviewer/resources/go-up-search.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/go-up-search.svg"
shortcut: StandardKey.FindPrevious
onTriggered: pageView.searchBack()
}
@@ -261,7 +261,7 @@ ApplicationWindow {
Layout.fillWidth: true
Image {
visible: searchField.text !== ""
- source: "../../../../examples/pdf/pdfviewer/resources/edit-clear.svg"
+ source: "../../../../examples/pdf/singlepage/resources/edit-clear.svg"
anchors {
right: parent.right
top: parent.top
@@ -276,7 +276,7 @@ ApplicationWindow {
}
ToolButton {
action: Action {
- icon.source: "../../../../examples/pdf/pdfviewer/resources/go-down-search.svg"
+ icon.source: "../../../../examples/pdf/singlepage/resources/go-down-search.svg"
shortcut: StandardKey.FindNext
onTriggered: pageView.searchForward()
}
diff --git a/tests/manual/quick/pdf/withdoc.qml b/tests/manual/quick/pdf/withdoc.qml
index d4bf12e29..0a2a86630 100644
--- a/tests/manual/quick/pdf/withdoc.qml
+++ b/tests/manual/quick/pdf/withdoc.qml
@@ -19,6 +19,11 @@ Window {
onPasswordRequired: function() { passwordDialog.open() }
}
+ Component.onCompleted: {
+ if (Application.arguments.length > 2)
+ doc.source = Application.arguments[Application.arguments.length - 1]
+ }
+
FileDialog {
id: fileDialog
title: "Open a PDF file"
@@ -85,6 +90,7 @@ Window {
PdfPageImage {
id: image
document: doc
+ retainWhileLoading: true
property real zoomFactor: Math.sqrt(2)
diff --git a/tests/manual/quick/quick.pro b/tests/manual/quick/quick.pro
deleted file mode 100644
index 8522763f8..000000000
--- a/tests/manual/quick/quick.pro
+++ /dev/null
@@ -1,5 +0,0 @@
-TEMPLATE = subdirs
-
-SUBDIRS += \
- faviconbrowser \
- touchbrowser
diff --git a/tests/manual/quick/touchbrowser/AddressBar.qml b/tests/manual/quick/touchbrowser/AddressBar.qml
index 6ccea3441..42188c94e 100644
--- a/tests/manual/quick/touchbrowser/AddressBar.qml
+++ b/tests/manual/quick/touchbrowser/AddressBar.qml
@@ -1,5 +1,5 @@
-// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+// Copyright (C) 2022 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
import QtQuick
import QtQuick.Controls
diff --git a/tests/manual/quick/touchbrowser/CMakeLists.txt b/tests/manual/quick/touchbrowser/CMakeLists.txt
index d4064c05e..0d3275e58 100644
--- a/tests/manual/quick/touchbrowser/CMakeLists.txt
+++ b/tests/manual/quick/touchbrowser/CMakeLists.txt
@@ -2,40 +2,30 @@
# SPDX-License-Identifier: BSD-3-Clause
if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
- cmake_minimum_required(VERSION 3.16)
+ cmake_minimum_required(VERSION 3.19)
project(touchbrowser LANGUAGES CXX)
find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
endif()
-if(NOT CMAKE_CROSSCOMPILING)
- add_definitions(-DDESKTOP_BUILD)
- set(SOURCES "touchmockingapplication.cpp")
-endif()
+set(CMAKE_AUTORCC ON)
+set(TOUCHMOCKING_DIR "../../touchmocking")
+
+include_directories(${TOUCHMOCKING_DIR})
+add_definitions(-DQUICK_TOUCHBROWSER)
-qt_internal_add_manual_test(touchbrowser
+qt_internal_add_manual_test(touchbrowser-quick
GUI
SOURCES
main.cpp
- utils.h
- ${SOURCES}
+ resources.qrc
+ ${TOUCHMOCKING_DIR}/touchmockingapplication.cpp
+ ${TOUCHMOCKING_DIR}/touchmockingapplication.h
+ ${TOUCHMOCKING_DIR}/utils.h
LIBRARIES
- Qt::GuiPrivate
+ Qt::Core
Qt::Quick
Qt::WebEngineQuick
ENABLE_AUTOGEN_TOOLS
moc
)
-set(touchbrowser_resource_files
- "AddressBar.qml"
- "main.qml"
- "MockTouchPoint.qml"
- "touchpoint.png"
-)
-
-qt_add_resources(touchbrowser "touchbrowser"
- PREFIX
- "/"
- FILES
- ${touchbrowser_resource_files}
-)
diff --git a/tests/manual/quick/touchbrowser/MockTouchPoint.qml b/tests/manual/quick/touchbrowser/MockTouchPoint.qml
index bdce0555c..895e12e70 100644
--- a/tests/manual/quick/touchbrowser/MockTouchPoint.qml
+++ b/tests/manual/quick/touchbrowser/MockTouchPoint.qml
@@ -1,6 +1,5 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-
+// Copyright (C) 2022 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
import QtQuick
Item {
diff --git a/tests/manual/quick/touchbrowser/main.cpp b/tests/manual/quick/touchbrowser/main.cpp
index 7b222038e..1f4d7d869 100644
--- a/tests/manual/quick/touchbrowser/main.cpp
+++ b/tests/manual/quick/touchbrowser/main.cpp
@@ -1,16 +1,14 @@
-// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+// Copyright (C) 2022 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
-#if defined(DESKTOP_BUILD)
#include "touchmockingapplication.h"
-#endif
#include "utils.h"
-#include <QtGui/QGuiApplication>
-#include <QtQml/QQmlApplicationEngine>
-#include <QtQml/QQmlContext>
-#include <QtQuick/QQuickView>
-#include <QtWebEngineQuick/qtwebenginequickglobal.h>
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QQmlContext>
+#include <QQuickView>
+#include <QtWebEngineQuick>
static QUrl startupUrl()
{
@@ -29,31 +27,10 @@ static QUrl startupUrl()
int main(int argc, char **argv)
{
- QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
- QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
-
- // We use touch mocking on desktop and apply all the mobile switches.
- QByteArrayList args = QByteArrayList()
- << QByteArrayLiteral("--enable-embedded-switches")
- << QByteArrayLiteral("--log-level=0");
- const int count = args.size() + argc;
- QList<char*> qargv(count);
-
- qargv[0] = argv[0];
- for (int i = 0; i < args.size(); ++i)
- qargv[i + 1] = args[i].data();
- for (int i = args.size() + 1; i < count; ++i)
- qargv[i] = argv[i - args.size()];
-
- int qAppArgCount = qargv.size();
-
QtWebEngineQuick::initialize();
-#if defined(DESKTOP_BUILD)
- TouchMockingApplication app(qAppArgCount, qargv.data());
-#else
- QGuiApplication app(qAppArgCount, qargv.data());
-#endif
+ TouchMockingApplication app(argc, argv);
+ app.setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents, true);
QQuickView view;
Utils utils;
diff --git a/tests/manual/quick/touchbrowser/qml.qrc b/tests/manual/quick/touchbrowser/resources.qrc
index 5c384502e..87d655a27 100644
--- a/tests/manual/quick/touchbrowser/qml.qrc
+++ b/tests/manual/quick/touchbrowser/resources.qrc
@@ -2,7 +2,6 @@
<qresource prefix="/">
<file>main.qml</file>
<file>AddressBar.qml</file>
- <file>MockTouchPoint.qml</file>
- <file>touchpoint.png</file>
+ <file alias="touchpoint.png">../../touchmocking/touchpoint.png</file>
</qresource>
</RCC>
diff --git a/tests/manual/quick/touchbrowser/touchbrowser.pro b/tests/manual/quick/touchbrowser/touchbrowser.pro
index 92e4e6703..710584df8 100644
--- a/tests/manual/quick/touchbrowser/touchbrowser.pro
+++ b/tests/manual/quick/touchbrowser/touchbrowser.pro
@@ -1,19 +1,15 @@
TEMPLATE = app
-QT += quick webenginequick
-CONFIG += c++11
+DEFINES += QUICK_TOUCHBROWSER
+QT += core gui quick webenginequick
-SOURCES += \
- main.cpp
+INCLUDEPATH += ../../touchmocking
+SOURCES += \
+ main.cpp \
+ ../../touchmocking/touchmockingapplication.cpp
HEADERS += \
- utils.h
-
-RESOURCES += qml.qrc
+ ../../touchmocking/touchmockingapplication.h \
+ ../../touchmocking/utils.h
-!cross_compile {
- DEFINES += DESKTOP_BUILD
- SOURCES += touchmockingapplication.cpp
- HEADERS += touchmockingapplication.h
- QT += gui-private
-}
+RESOURCES += resources.qrc
diff --git a/tests/manual/quick/touchbrowser/touchmockingapplication.cpp b/tests/manual/quick/touchbrowser/touchmockingapplication.cpp
deleted file mode 100644
index 4fad86d33..000000000
--- a/tests/manual/quick/touchbrowser/touchmockingapplication.cpp
+++ /dev/null
@@ -1,261 +0,0 @@
-// 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 "touchmockingapplication.h"
-
-#include <qpa/qwindowsysteminterface.h>
-#include <QtQuick/QQuickItem>
-#include <QtQuick/QQuickView>
-#include <QInputDevice>
-
-static inline bool isTouchEvent(const QEvent* event)
-{
- switch (event->type()) {
- case QEvent::TouchBegin:
- case QEvent::TouchUpdate:
- case QEvent::TouchEnd:
- return true;
- default:
- return false;
- }
-}
-
-static inline bool isMouseEvent(const QEvent* event)
-{
- switch (event->type()) {
- case QEvent::MouseButtonPress:
- case QEvent::MouseMove:
- case QEvent::MouseButtonRelease:
- case QEvent::MouseButtonDblClick:
- return true;
- default:
- return false;
- }
-}
-
-TouchMockingApplication::TouchMockingApplication(int& argc, char** argv)
- : QGuiApplication(argc, argv)
- , m_realTouchEventReceived(false)
- , m_pendingFakeTouchEventCount(0)
- , m_holdingControl(false)
-{
-}
-
-bool TouchMockingApplication::notify(QObject* target, QEvent* event)
-{
- // We try to be smart, if we received real touch event, we are probably on a device
- // with touch screen, and we should not have touch mocking.
-
- if (!event->spontaneous() || m_realTouchEventReceived)
- return QGuiApplication::notify(target, event);
-
- if (isTouchEvent(event)) {
- if (m_pendingFakeTouchEventCount)
- --m_pendingFakeTouchEventCount;
- else
- m_realTouchEventReceived = true;
- return QGuiApplication::notify(target, event);
- }
-
- QQuickView* window = qobject_cast<QQuickView*>(target);
- if (!window)
- return QGuiApplication::notify(target, event);
-
- m_holdingControl = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier);
-
- if (event->type() == QEvent::KeyRelease && static_cast<QKeyEvent*>(event)->key() == Qt::Key_Control) {
- foreach (int id, m_heldTouchPoints)
- if (m_touchPoints.contains(id) && !QGuiApplication::mouseButtons().testFlag(Qt::MouseButton(id))) {
- QMutableEventPoint::setState(m_touchPoints[id], QEventPoint::Released);
- m_heldTouchPoints.remove(id);
- } else
- QMutableEventPoint::setState(m_touchPoints[id], QEventPoint::Stationary);
-
- sendTouchEvent(window, m_heldTouchPoints.isEmpty() ? QEvent::TouchEnd : QEvent::TouchUpdate, static_cast<QKeyEvent*>(event)->timestamp());
- }
-
- if (isMouseEvent(event)) {
- const QMouseEvent* const mouseEvent = static_cast<QMouseEvent*>(event);
-
- QEventPoint touchPoint;
- QMutableEventPoint::setPressure(touchPoint, 1);
-
- QEvent::Type touchType = QEvent::None;
-
- switch (mouseEvent->type()) {
- case QEvent::MouseButtonPress:
- QMutableEventPoint::setId(touchPoint, mouseEvent->button());
- if (m_touchPoints.contains(touchPoint.id())) {
- QMutableEventPoint::setState(touchPoint, QEventPoint::Updated);
- touchType = QEvent::TouchUpdate;
- } else {
- QMutableEventPoint::setState(touchPoint, QEventPoint::Pressed);
- // Check if more buttons are held down than just the event triggering one.
- if (mouseEvent->buttons() > mouseEvent->button())
- touchType = QEvent::TouchUpdate;
- else
- touchType = QEvent::TouchBegin;
- }
- break;
- case QEvent::MouseMove:
- if (!mouseEvent->buttons()) {
- // We have to swallow the event instead of propagating it,
- // since we avoid sending the mouse release events and if the
- // Flickable is the mouse grabber it would receive the event
- // and would move the content.
- event->accept();
- return true;
- }
- touchType = QEvent::TouchUpdate;
- QMutableEventPoint::setId(touchPoint, mouseEvent->buttons());
- QMutableEventPoint::setState(touchPoint, QEventPoint::Updated);
- break;
- case QEvent::MouseButtonRelease:
- // Check if any buttons are still held down after this event.
- if (mouseEvent->buttons())
- touchType = QEvent::TouchUpdate;
- else
- touchType = QEvent::TouchEnd;
- QMutableEventPoint::setId(touchPoint, mouseEvent->button());
- QMutableEventPoint::setState(touchPoint, QEventPoint::Released);
- break;
- case QEvent::MouseButtonDblClick:
- // Eat double-clicks, their accompanying press event is all we need.
- event->accept();
- return true;
- default:
- Q_ASSERT_X(false, "multi-touch mocking", "unhandled event type");
- }
-
- // A move can have resulted in multiple buttons, so we need check them individually.
- if (touchPoint.id() & Qt::LeftButton)
- updateTouchPoint(mouseEvent, touchPoint, Qt::LeftButton);
- if (touchPoint.id() & Qt::MiddleButton)
- updateTouchPoint(mouseEvent, touchPoint, Qt::MiddleButton);
- if (touchPoint.id() & Qt::RightButton)
- updateTouchPoint(mouseEvent, touchPoint, Qt::RightButton);
-
- if (m_holdingControl && touchPoint.state() == QEventPoint::Released) {
- // We avoid sending the release event because the Flickable is
- // listening to mouse events and would start a bounce-back
- // animation if it received a mouse release.
- event->accept();
- return true;
- }
-
- // Update states for all other touch-points
- for (QHash<int, QEventPoint>::iterator it = m_touchPoints.begin(), end = m_touchPoints.end(); it != end; ++it) {
- if (!(it.value().id() & touchPoint.id()))
- QMutableEventPoint::setState(it.value(), QEventPoint::Stationary);
- }
-
- Q_ASSERT(touchType != QEvent::None);
-
- if (!sendTouchEvent(window, touchType, mouseEvent->timestamp()))
- return QGuiApplication::notify(target, event);
-
- event->accept();
- return true;
- }
-
- return QGuiApplication::notify(target, event);
-}
-
-void TouchMockingApplication::updateTouchPoint(const QMouseEvent* mouseEvent, QEventPoint touchPoint, Qt::MouseButton mouseButton)
-{
- // Ignore inserting additional touch points if Ctrl isn't held because it produces
- // inconsistent touch events and results in assers in the gesture recognizers.
- if (!m_holdingControl && m_touchPoints.size() && !m_touchPoints.contains(mouseButton))
- return;
-
- if (m_holdingControl && touchPoint.state() == QEventPoint::Released) {
- m_heldTouchPoints.insert(mouseButton);
- return;
- }
-
- // Gesture recognition uses the screen position for the initial threshold
- // but since the canvas translates touch events we actually need to pass
- // the screen position as the scene position to deliver the appropriate
- // coordinates to the target.
- QMutableEventPoint::setPosition(touchPoint, mouseEvent->position());
- QMutableEventPoint::setScenePosition(touchPoint, mouseEvent->globalPosition());
-
- if (touchPoint.state() == QEventPoint::Pressed)
- QMutableEventPoint::setScenePosition(touchPoint, mouseEvent->scenePosition());
- else {
- const QEventPoint& oldTouchPoint = m_touchPoints[mouseButton];
- QMutableEventPoint::setGlobalLastPosition(touchPoint, oldTouchPoint.globalPosition());
- }
-
- // Update current touch-point.
- QMutableEventPoint::setId(touchPoint, mouseButton);
- m_touchPoints.insert(mouseButton, touchPoint);
-}
-
-bool TouchMockingApplication::sendTouchEvent(QQuickView* window, QEvent::Type type, ulong timestamp)
-{
- static QPointingDevice* device = 0;
- if (!device) {
- device = new QPointingDevice(QStringLiteral("MockTouchDevice"), 1,
- QPointingDevice::DeviceType::TouchScreen,
- QPointingDevice::PointerType::AllPointerTypes,
- QInputDevice::Capability::All, 3, 3,
- QString(), QPointingDeviceUniqueId(), window->rootObject());
-
- QWindowSystemInterface::registerInputDevice(device);
- }
-
- m_pendingFakeTouchEventCount++;
-
- const QList<QEventPoint>& currentTouchPoints = m_touchPoints.values();
- QEventPoint::States touchPointStates = QEventPoint::States();
- foreach (const QEventPoint& touchPoint, currentTouchPoints)
- touchPointStates |= touchPoint.state();
-
- QTouchEvent event(type, device, Qt::NoModifier, currentTouchPoints);
- event.setTimestamp(timestamp);
- event.setAccepted(false);
-
- QGuiApplication::notify(window, &event);
-
- updateVisualMockTouchPoints(window, m_holdingControl ? currentTouchPoints : QList<QEventPoint>());
-
- // Get rid of touch-points that are no longer valid
- foreach (const QEventPoint& touchPoint, currentTouchPoints) {
- if (touchPoint.state() == QEventPoint::Released)
- m_touchPoints.remove(touchPoint.id());
- }
-
- return event.isAccepted();
-}
-
-void TouchMockingApplication::updateVisualMockTouchPoints(QQuickView* window,const QList<QEventPoint>& touchPoints)
-{
- if (touchPoints.isEmpty()) {
- // Hide all touch indicator items.
- foreach (QQuickItem* item, m_activeMockComponents.values())
- item->setProperty("pressed", false);
-
- return;
- }
-
- foreach (const QEventPoint& touchPoint, touchPoints) {
- QQuickItem* mockTouchPointItem = m_activeMockComponents.value(touchPoint.id());
-
- if (!mockTouchPointItem) {
- QQmlComponent touchMockPointComponent(window->engine(), QUrl("qrc:///MockTouchPoint.qml"));
- mockTouchPointItem = qobject_cast<QQuickItem*>(touchMockPointComponent.create());
- Q_ASSERT(mockTouchPointItem);
- m_activeMockComponents.insert(touchPoint.id(), mockTouchPointItem);
- mockTouchPointItem->setProperty("pointId", QVariant(touchPoint.id()));
- mockTouchPointItem->setParent(window->rootObject());
- mockTouchPointItem->setParentItem(window->rootObject());
- }
-
- mockTouchPointItem->setX(touchPoint.position().x());
- mockTouchPointItem->setY(touchPoint.position().y());
- mockTouchPointItem->setWidth(touchPoint.ellipseDiameters().width());
- mockTouchPointItem->setHeight(touchPoint.ellipseDiameters().height());
- mockTouchPointItem->setProperty("pressed", QVariant(touchPoint.state() != QEventPoint::Released));
- }
-}
diff --git a/tests/manual/quick/touchbrowser/touchmockingapplication.h b/tests/manual/quick/touchbrowser/touchmockingapplication.h
deleted file mode 100644
index 3264d5a0c..000000000
--- a/tests/manual/quick/touchbrowser/touchmockingapplication.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// 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 TOUCHMOCKINGAPPLICATION_H
-#define TOUCHMOCKINGAPPLICATION_H
-
-#include <QtGui/QGuiApplication>
-#include <QtGui/private/qeventpoint_p.h>
-#include <QtGui/QEventPoint>
-
-#include <private/qevent_p.h>
-
-QT_BEGIN_NAMESPACE
-class QQuickView;
-class QQuickItem;
-QT_END_NAMESPACE
-
-class TouchMockingApplication : public QGuiApplication
-{
- Q_OBJECT
-
-public:
- TouchMockingApplication(int &argc, char **argv);
-
- virtual bool notify(QObject *, QEvent *) override;
-
-private:
- void updateTouchPoint(const QMouseEvent *, QEventPoint, Qt::MouseButton);
- bool sendTouchEvent(QQuickView *, QEvent::Type, ulong timestamp);
- void updateVisualMockTouchPoints(QQuickView *,const QList<QEventPoint> &touchPoints);
-
-private:
- bool m_realTouchEventReceived;
- int m_pendingFakeTouchEventCount;
-
- QHash<int, QEventPoint> m_touchPoints;
- QSet<int> m_heldTouchPoints;
- QHash<int, QQuickItem*> m_activeMockComponents;
-
- bool m_holdingControl;
-};
-
-#endif // TOUCHMOCKINGAPPLICATION_H
diff --git a/tests/manual/touchmocking/touchmockingapplication.cpp b/tests/manual/touchmocking/touchmockingapplication.cpp
new file mode 100644
index 000000000..feedae5cd
--- /dev/null
+++ b/tests/manual/touchmocking/touchmockingapplication.cpp
@@ -0,0 +1,78 @@
+// Copyright (C) 2022 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 "touchmockingapplication.h"
+
+#include <QCursor>
+#include <QEvent>
+#include <QPixmap>
+
+#if defined(QUICK_TOUCHBROWSER)
+# include <QQuickView>
+#endif
+
+#if defined(WIDGET_TOUCHBROWSER)
+# include <QMainWindow>
+#endif
+
+static inline bool isMouseEvent(QEvent *event)
+{
+ switch (event->type()) {
+ case QEvent::MouseMove:
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonRelease:
+ return true;
+ default:
+ return false;
+ }
+}
+
+TouchMockingApplication::TouchMockingApplication(int &argc, char **argv)
+ : Application(argc, argv), m_touchPoint(new QCursor(QPixmap(":touchpoint.png")))
+{
+}
+
+TouchMockingApplication::~TouchMockingApplication()
+{
+ delete m_touchPoint;
+}
+
+bool TouchMockingApplication::notify(QObject *target, QEvent *event)
+{
+ switch (event->type()) {
+ case QEvent::TouchBegin:
+ setOverrideCursor(*m_touchPoint);
+ break;
+ case QEvent::TouchEnd:
+ restoreCursor();
+ break;
+ default:
+ break;
+ }
+
+// All mouse events that are not accepted by the application will be translated to touch events
+// instead (see Qt::AA_SynthesizeTouchForUnhandledMouseEvents).
+#if defined(QUICK_TOUCHBROWSER)
+ if (isMouseEvent(event) && qobject_cast<QQuickView *>(target)) {
+ event->ignore();
+ return false;
+ }
+#elif defined(WIDGET_TOUCHBROWSER)
+ // Popups ignore touch evenets so we send MouseEvents directly.
+ if (isMouseEvent(event)) {
+ if (activePopupWidget()) {
+ restoreCursor();
+ } else {
+ event->ignore();
+ return false;
+ }
+ }
+#endif
+ return Application::notify(target, event);
+}
+
+void TouchMockingApplication::restoreCursor()
+{
+ while (overrideCursor())
+ restoreOverrideCursor();
+}
diff --git a/tests/manual/touchmocking/touchmockingapplication.h b/tests/manual/touchmocking/touchmockingapplication.h
new file mode 100644
index 000000000..4f6e744fc
--- /dev/null
+++ b/tests/manual/touchmocking/touchmockingapplication.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2022 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
+
+#ifndef TOUCHMOCKINGAPPLICATION_H
+#define TOUCHMOCKINGAPPLICATION_H
+
+#if defined(QUICK_TOUCHBROWSER)
+# include <QGuiApplication>
+using Application = QGuiApplication;
+#elif defined(WIDGET_TOUCHBROWSER)
+# include <QApplication>
+using Application = QApplication;
+#endif
+
+QT_BEGIN_NAMESPACE
+class QCursor;
+QT_END_NAMESPACE
+
+class TouchMockingApplication : public Application
+{
+ Q_OBJECT
+
+public:
+ TouchMockingApplication(int &argc, char **argv);
+ ~TouchMockingApplication();
+
+ virtual bool notify(QObject *, QEvent *) override;
+
+private:
+ void restoreCursor();
+
+ QCursor *m_touchPoint;
+};
+
+#endif // TOUCHMOCKINGAPPLICATION_H
diff --git a/tests/manual/quick/touchbrowser/touchpoint.png b/tests/manual/touchmocking/touchpoint.png
index 7649ee991..7649ee991 100644
--- a/tests/manual/quick/touchbrowser/touchpoint.png
+++ b/tests/manual/touchmocking/touchpoint.png
Binary files differ
diff --git a/tests/manual/touchmocking/utils.h b/tests/manual/touchmocking/utils.h
new file mode 100644
index 000000000..12d493d3f
--- /dev/null
+++ b/tests/manual/touchmocking/utils.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2022 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
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <QFileInfo>
+#include <QUrl>
+
+class Utils : public QObject
+{
+ Q_OBJECT
+public:
+ Q_INVOKABLE static QUrl fromUserInput(const QString &userInput);
+};
+
+inline QUrl Utils::fromUserInput(const QString &userInput)
+{
+ QFileInfo fileInfo(userInput);
+ if (fileInfo.exists())
+ return QUrl::fromLocalFile(fileInfo.absoluteFilePath());
+ return QUrl::fromUserInput(userInput);
+}
+
+#endif // UTILS_H
diff --git a/tests/manual/widgets/CMakeLists.txt b/tests/manual/widgets/CMakeLists.txt
index 4e619f066..7c19f9e43 100644
--- a/tests/manual/widgets/CMakeLists.txt
+++ b/tests/manual/widgets/CMakeLists.txt
@@ -1,2 +1,9 @@
add_subdirectory(inputmethods)
-add_subdirectory(webgl)
+add_subdirectory(geolocation)
+add_subdirectory(touchbrowser)
+if(QT_FEATURE_opengl)
+ add_subdirectory(webgl)
+endif()
+if(TARGET Qt6::HttpServer)
+ add_subdirectory(webrtc)
+endif()
diff --git a/tests/manual/widgets/geolocation/CMakeLists.txt b/tests/manual/widgets/geolocation/CMakeLists.txt
new file mode 100644
index 000000000..2ca8c2f52
--- /dev/null
+++ b/tests/manual/widgets/geolocation/CMakeLists.txt
@@ -0,0 +1,55 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(geolocation LANGUAGES CXX)
+ find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_manual_test(geolocation
+ GUI
+ SOURCES
+ main.cpp
+ LIBRARIES
+ Qt::Core
+ Qt::Gui
+ Qt::Test
+ Qt::WebEngineWidgets
+ ENABLE_AUTOGEN_TOOLS
+ moc
+)
+
+set_target_properties(geolocation PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+ MACOSX_BUNDLE_GUI_IDENTIFIER "io.qt.dev.webenginewidgets.geolocation"
+)
+
+set(geolocation_resource_files
+ "geolocation.html"
+)
+
+qt_add_resources(geolocation "geolocation"
+ PREFIX
+ "/"
+ FILES
+ ${geolocation_resource_files}
+)
+
+ foreach(permission_plugin IN LISTS QT_ALL_PLUGINS_FOUND_BY_FIND_PACKAGE_permissions)
+ set(permission_plugin "${QT_CMAKE_EXPORT_NAMESPACE}::${permission_plugin}")
+ qt6_import_plugins(geolocation INCLUDE ${permission_plugin})
+ endforeach()
+
+if (APPLE)
+ set_target_properties(geolocation PROPERTIES
+ MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist"
+ )
+
+ if (NOT CMAKE_GENERATOR STREQUAL "Xcode")
+ # Need to sign application for location permissions to work
+ add_custom_command(TARGET geolocation
+ POST_BUILD COMMAND codesign -s - geolocation.app)
+ endif()
+endif()
diff --git a/tests/manual/widgets/geolocation/Info.plist b/tests/manual/widgets/geolocation/Info.plist
new file mode 100644
index 000000000..9853e1900
--- /dev/null
+++ b/tests/manual/widgets/geolocation/Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleName</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
+ <key>CFBundleExecutable</key>
+ <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
+ <key>CFBundleVersion</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
+ <key>CFBundleIconFile</key>
+ <string>${MACOSX_BUNDLE_ICON_FILE}</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+ <key>NSLocationUsageDescription</key>
+ <string>Geolocation test would like to give web sites access to your location for demo purposes.</string>
+</dict>
+</plist>
diff --git a/tests/manual/widgets/geolocation/geolocation.html b/tests/manual/widgets/geolocation/geolocation.html
new file mode 100644
index 000000000..e8c54bc58
--- /dev/null
+++ b/tests/manual/widgets/geolocation/geolocation.html
@@ -0,0 +1,32 @@
+<html>
+<head>
+<title>Geolocation Permission API Test</title>
+<script>
+
+var errorMessage;
+var handled = false;
+
+function successHandler(location) {
+ var message = document.getElementById("message");
+ message.innerHTML = "Latitude: " + location.coords.latitude +
+ "<br>Longitude: " + location.coords.longitude;
+
+ errorMessage = "";
+ handled = true;
+}
+
+function errorHandler(error) {
+ errorMessage = error.message;
+ handled = true;
+}
+
+<!-- One shot example -->
+navigator.geolocation.getCurrentPosition(successHandler, errorHandler);
+
+</script>
+</head>
+<body>
+<div id="message">Location unknown</div>
+</body>
+</html>
+
diff --git a/tests/manual/widgets/geolocation/main.cpp b/tests/manual/widgets/geolocation/main.cpp
new file mode 100644
index 000000000..f33cf5798
--- /dev/null
+++ b/tests/manual/widgets/geolocation/main.cpp
@@ -0,0 +1,51 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QApplication>
+#include <QFormLayout>
+#include <QGroupBox>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QMainWindow>
+#include <QMessageBox>
+#include <QVBoxLayout>
+#include <QWebEnginePage>
+#include <QWebEngineView>
+
+class GeoPermissionWebView : public QWebEngineView {
+ Q_OBJECT
+
+public slots:
+ void handleFeaturePermissionRequested(const QUrl &securityOrigin,
+ QWebEnginePage::Feature feature)
+ {
+ qWarning("Feature Permission");
+ QString title = tr("Permission Request");
+ QString question = QLatin1String("Allow access to geolocation?");
+ if (!question.isEmpty() && QMessageBox::question(window(), title, question) == QMessageBox::Yes)
+ page()->setFeaturePermission(securityOrigin, feature,
+ QWebEnginePage::PermissionGrantedByUser);
+ else
+ page()->setFeaturePermission(securityOrigin, feature,
+ QWebEnginePage::PermissionDeniedByUser);
+ }
+
+};
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+ QMainWindow w;
+ GeoPermissionWebView webview;
+ QWebEnginePage page;
+ QObject::connect(&page, &QWebEnginePage::featurePermissionRequested, &webview,
+ &GeoPermissionWebView::handleFeaturePermissionRequested);
+ webview.setPage(&page);
+ page.load(QUrl("qrc:/geolocation.html"));
+ w.setCentralWidget(&webview);
+ w.show();
+
+ return a.exec();
+}
+
+#include "main.moc"
diff --git a/tests/manual/widgets/inputmethods/colorpicker.h b/tests/manual/widgets/inputmethods/colorpicker.h
index 171c0186b..0b6b3257a 100644
--- a/tests/manual/widgets/inputmethods/colorpicker.h
+++ b/tests/manual/widgets/inputmethods/colorpicker.h
@@ -7,8 +7,10 @@
#include <QColor>
#include <QWidget>
+QT_BEGIN_NAMESPACE
class QLineEdit;
class QPushButton;
+QT_END_NAMESPACE
class ColorPicker : public QWidget
{
diff --git a/tests/manual/widgets/inputmethods/controlview.h b/tests/manual/widgets/inputmethods/controlview.h
index f6b3e7fe6..caa08593f 100644
--- a/tests/manual/widgets/inputmethods/controlview.h
+++ b/tests/manual/widgets/inputmethods/controlview.h
@@ -8,12 +8,15 @@
#include <QTextCharFormat>
#include <QWidget>
-class ColorPicker;
+QT_BEGIN_NAMESPACE
class QComboBox;
class QLabel;
class QLineEdit;
class QPushButton;
class QSpinBox;
+QT_END_NAMESPACE
+
+class ColorPicker;
class ControlView : public QWidget
{
diff --git a/tests/manual/widgets/inputmethods/testview.h b/tests/manual/widgets/inputmethods/testview.h
index feb512cde..b99e60d75 100644
--- a/tests/manual/widgets/inputmethods/testview.h
+++ b/tests/manual/widgets/inputmethods/testview.h
@@ -7,8 +7,10 @@
#include <QTextCharFormat>
#include <QWidget>
+QT_BEGIN_NAMESPACE
class QPushButton;
class QTableView;
+QT_END_NAMESPACE
class TestView : public QWidget
{
diff --git a/tests/manual/widgets/touchbrowser/CMakeLists.txt b/tests/manual/widgets/touchbrowser/CMakeLists.txt
new file mode 100644
index 000000000..de60ad2b4
--- /dev/null
+++ b/tests/manual/widgets/touchbrowser/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.19)
+ project(touchbrowser LANGUAGES CXX)
+ find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
+endif()
+
+set(CMAKE_AUTORCC ON)
+set(TOUCHMOCKING_DIR "../../touchmocking")
+
+include_directories(${TOUCHMOCKING_DIR})
+add_definitions(-DWIDGET_TOUCHBROWSER)
+
+qt_internal_add_manual_test(touchbrowser-widget
+ GUI
+ SOURCES
+ main.cpp
+ resources.qrc
+ ${TOUCHMOCKING_DIR}/touchmockingapplication.cpp
+ ${TOUCHMOCKING_DIR}/touchmockingapplication.h
+ ${TOUCHMOCKING_DIR}/utils.h
+ LIBRARIES
+ Qt::Core
+ Qt::Gui
+ Qt::WebEngineWidgets
+ ENABLE_AUTOGEN_TOOLS
+ moc
+)
diff --git a/tests/manual/widgets/touchbrowser/main.cpp b/tests/manual/widgets/touchbrowser/main.cpp
new file mode 100644
index 000000000..18baf79e8
--- /dev/null
+++ b/tests/manual/widgets/touchbrowser/main.cpp
@@ -0,0 +1,62 @@
+// Copyright (C) 2022 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 "touchmockingapplication.h"
+#include "utils.h"
+
+#include <QApplication>
+#include <QLineEdit>
+#include <QMainWindow>
+#include <QToolBar>
+#include <QWebEngineView>
+
+
+static QUrl startupUrl()
+{
+ QUrl ret;
+ QStringList args(qApp->arguments());
+ args.takeFirst();
+ for (const QString &arg : std::as_const(args)) {
+ if (arg.startsWith(QLatin1Char('-')))
+ continue;
+ ret = Utils::fromUserInput(arg);
+ if (ret.isValid())
+ return ret;
+ }
+ return QUrl(QStringLiteral("https://www.qt.io/"));
+}
+
+int main(int argc, char **argv)
+{
+ TouchMockingApplication app(argc, argv);
+ app.setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents, true);
+
+ QMainWindow window;
+ QWebEngineView view(&window);
+ QToolBar addressBar("AddressBar", &window);
+ QLineEdit lineEdit(&addressBar);
+
+ view.setAttribute(Qt::WA_AcceptTouchEvents, true);
+ view.setUrl(startupUrl());
+ window.resize(1024, 750);
+ window.setCentralWidget(&view);
+
+ addressBar.setAttribute(Qt::WA_AcceptTouchEvents, true);
+ addressBar.setMovable(false);
+ addressBar.toggleViewAction()->setEnabled(false);
+
+ lineEdit.setAttribute(Qt::WA_AcceptTouchEvents, true);
+ lineEdit.setClearButtonEnabled(true);
+
+ addressBar.addWidget(&lineEdit);
+ QObject::connect(&lineEdit, &QLineEdit::returnPressed, [&]() {
+ QUrl url = Utils::fromUserInput(lineEdit.text());
+ lineEdit.setText(url.toDisplayString());
+ view.setUrl(url);
+ });
+
+ window.addToolBar(&addressBar);
+ window.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/widgets/touchbrowser/resources.qrc b/tests/manual/widgets/touchbrowser/resources.qrc
new file mode 100644
index 000000000..b621823ea
--- /dev/null
+++ b/tests/manual/widgets/touchbrowser/resources.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file alias="touchpoint.png">../../touchmocking/touchpoint.png</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/widgets/touchbrowser/touchbrowser.pro b/tests/manual/widgets/touchbrowser/touchbrowser.pro
new file mode 100644
index 000000000..1587f390a
--- /dev/null
+++ b/tests/manual/widgets/touchbrowser/touchbrowser.pro
@@ -0,0 +1,15 @@
+TEMPLATE = app
+
+DEFINES += WIDGET_TOUCHBROWSER
+QT += core gui webenginewidgets
+
+INCLUDEPATH += ../../touchmocking
+
+SOURCES += \
+ main.cpp \
+ ../../touchmocking/touchmockingapplication.cpp
+HEADERS += \
+ ../../touchmocking/touchmockingapplication.h \
+ ../../touchmocking/utils.h
+
+RESOURCES += resources.qrc
diff --git a/tests/manual/widgets/webrtc/CMakeLists.txt b/tests/manual/widgets/webrtc/CMakeLists.txt
new file mode 100644
index 000000000..3f98f1fd6
--- /dev/null
+++ b/tests/manual/widgets/webrtc/CMakeLists.txt
@@ -0,0 +1,24 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.19)
+ project(webrtc LANGUAGES CXX)
+ find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
+endif()
+
+set(CMAKE_AUTOUIC ON)
+set(CMAKE_AUTORCC ON)
+
+qt_internal_add_manual_test(webrtc
+ GUI
+ SOURCES
+ main.cpp
+ mediaPicker.ui
+ qrc.qrc
+ LIBRARIES
+ Qt::Core
+ Qt::HttpServer
+ Qt::Gui
+ Qt::WebEngineWidgets
+)
diff --git a/tests/manual/widgets/webrtc/index.html b/tests/manual/widgets/webrtc/index.html
new file mode 100644
index 000000000..433d643c3
--- /dev/null
+++ b/tests/manual/widgets/webrtc/index.html
@@ -0,0 +1,86 @@
+<!doctype html>
+<html>
+ <head>
+ <style>
+ body {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-wrap: wrap;
+ flex-flow: column;
+ }
+ buttons {
+ justify-content: space-around;
+ }
+ </style>
+ </head>
+ <body>
+ <div id="buttons" >
+ <input value ="getDisplayMedia" onclick="getDisplayMedia(true, true);" type="button">
+ <input value = "chooseDesktopMedia" onclick="chooseDesktopMedia();" type="button">
+ <input value ="Stop" onclick="stop();" type="button">
+ </div>
+ <div id="content"></div>
+ </body>
+ <script>
+ const EXTENSION_ID = "nkeimhogjdpnpccoofpliimaahmaaome"; // hangout services extension
+ const content = document.getElementById("content");
+ const video = document.createElement("video");
+ video.setAttribute("width", 640);
+ video.setAttribute("height", 640);
+ video.setAttribute("style", "background-color: black;");
+ content.appendChild(video);
+
+ async function getDisplayMedia(v = true, a = true) {
+ stop();
+ navigator.mediaDevices.getDisplayMedia({ video: v, audio: a })
+ .then(stream => {
+ start(stream);
+ }, error => {
+ console.error(error);
+ });
+ }
+
+ function chooseDesktopMedia() {
+ stop();
+ // Connect to the 'chooseDesktopMedia' listener within the hangout services extension.
+ let port = chrome.runtime.connect(EXTENSION_ID, {name: "chooseDesktopMedia"})
+
+ // The 'chooseDesktopMedia' api returns a streamId that
+ // identifies a media source in the constraints of 'getUserMedia'
+ // (see chromeMediaSourceId)
+ port.onMessage.addListener(result => {
+ navigator.mediaDevices.getUserMedia({
+ video: {
+ mandatory: {
+ chromeMediaSource: "desktop",
+ chromeMediaSourceId: result.value.streamId
+ },
+ }
+ }).then(stream => {
+ start(stream);
+ }, error => {
+ console.error(error);
+ })
+ })
+
+ // Trigger the listener on the other side,
+ // we should see the picker dialog after this call.
+ port.postMessage({method: "chooseDesktopMedia"})
+ }
+
+ function stop() {
+ if (video.srcObject)
+ for (const track of video.srcObject.getTracks())
+ track.stop()
+ video.srcObject = null;
+ video.setAttribute("style", "background-color: black;");
+ }
+
+ function start(stream) {
+ video.srcObject = stream;
+ video.play();
+ }
+
+ </script>
+</html>
diff --git a/tests/manual/widgets/webrtc/main.cpp b/tests/manual/widgets/webrtc/main.cpp
new file mode 100644
index 000000000..328e4ae36
--- /dev/null
+++ b/tests/manual/widgets/webrtc/main.cpp
@@ -0,0 +1,112 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QApplication>
+#include <QByteArray>
+#include <QDialog>
+#include <QFile>
+#include <QHttpServer>
+#include <QListView>
+#include <QMessageBox>
+#include <QWebEnginePage>
+#include <QWebEngineProfile>
+#include <QWebEngineSettings>
+#include <QWebEngineView>
+
+#include "ui_mediaPicker.h"
+#include <QWebEngineDesktopMediaRequest>
+
+// Test the screen/window selection and capturing APIs using QWebEngineDesktopMediaRequest,
+// getDisplayMedia (js) and chooseDesktopMedia (hangouts)
+
+// Note: Wayland compositors require Pipewire support in QWE
+
+class Page : public QWebEnginePage
+{
+ Q_OBJECT
+
+public:
+ Page(QWebEngineProfile *profile, QObject *parent = nullptr);
+private slots:
+ void handlePermissionRequest(const QUrl &origin, Feature feature);
+ void handleDesktopMediaRequest(const QWebEngineDesktopMediaRequest &request);
+};
+
+Page::Page(QWebEngineProfile *profile, QObject *parent) : QWebEnginePage(profile, parent)
+{
+ settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true);
+ connect(this, &QWebEnginePage::featurePermissionRequested, this,
+ &Page::handlePermissionRequest);
+ connect(this, &QWebEnginePage::desktopMediaRequested, this, &Page::handleDesktopMediaRequest);
+}
+
+void Page::handlePermissionRequest(const QUrl &origin, Feature feature)
+{
+ if (QMessageBox::question(QApplication::activeWindow(), tr("Permission request"),
+ tr("allow access?"))
+ == QMessageBox::Yes)
+ setFeaturePermission(origin, feature, PermissionGrantedByUser);
+ else
+ setFeaturePermission(origin, feature, PermissionDeniedByUser);
+}
+
+void Page::handleDesktopMediaRequest(const QWebEngineDesktopMediaRequest &request)
+{
+ Ui::MediaPickerDialog mediaPickerDialog;
+ QDialog dialog;
+ dialog.setModal(true);
+ mediaPickerDialog.setupUi(&dialog);
+
+ auto *screensView = mediaPickerDialog.screensView;
+ auto *windowsView = mediaPickerDialog.windowsView;
+ auto *screensModel = request.screensModel();
+ auto *windowsModel = request.windowsModel();
+
+ screensView->setModel(screensModel);
+ windowsView->setModel(windowsModel);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ if (mediaPickerDialog.tabWidget->currentIndex() == 0)
+ request.selectWindow(windowsView->selectionModel()->selectedIndexes().first());
+ else
+ request.selectScreen(screensView->selectionModel()->selectedIndexes().first());
+ } else {
+ request.cancel();
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+
+ QHttpServer server;
+
+ QFile file(":index.html");
+
+ if (!file.open(QIODeviceBase::ReadOnly)) {
+ qWarning("failed to open file!");
+ return 0;
+ }
+
+ QByteArray data = file.readAll();
+ if (data.isEmpty()) {
+ qWarning("failed to read file!");
+ return 0;
+ }
+
+ server.route("/index.html", [data]() {
+ return data;
+ });
+
+ server.listen(QHostAddress::Any, 3000);
+
+ QWebEngineView view;
+ Page *page = new Page(QWebEngineProfile::defaultProfile(), &view);
+ view.setPage(page);
+ view.resize(1024, 750);
+ view.setUrl(QUrl("http://localhost:3000/index.html"));
+ view.show();
+ return app.exec();
+}
+
+#include "main.moc"
diff --git a/tests/manual/widgets/webrtc/mediaPicker.ui b/tests/manual/widgets/webrtc/mediaPicker.ui
new file mode 100644
index 000000000..8bfab3f9b
--- /dev/null
+++ b/tests/manual/widgets/webrtc/mediaPicker.ui
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MediaPickerDialog</class>
+ <widget class="QDialog" name="MediaPickerDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>500</width>
+ <height>400</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Choose what to share</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="windows">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="contextMenuPolicy">
+ <enum>Qt::NoContextMenu</enum>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <attribute name="title">
+ <string>Windows</string>
+ </attribute>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QListView" name="windowsView"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="screens">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="contextMenuPolicy">
+ <enum>Qt::NoContextMenu</enum>
+ </property>
+ <attribute name="title">
+ <string>Screens</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QListView" name="screensView"/>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>MediaPickerDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>MediaPickerDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/tests/manual/widgets/webrtc/qrc.qrc b/tests/manual/widgets/webrtc/qrc.qrc
new file mode 100644
index 000000000..c3322b454
--- /dev/null
+++ b/tests/manual/widgets/webrtc/qrc.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>index.html</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/widgets/widgets.pro b/tests/manual/widgets/widgets.pro
deleted file mode 100644
index 34e88f0e3..000000000
--- a/tests/manual/widgets/widgets.pro
+++ /dev/null
@@ -1,5 +0,0 @@
-TEMPLATE= subdirs
-
-SUBDIRS += \
- inputmethods \
- webgl
diff --git a/tests/quicktestbrowser/ApplicationRoot.qml b/tests/quicktestbrowser/ApplicationRoot.qml
deleted file mode 100644
index 74e913ba5..000000000
--- a/tests/quicktestbrowser/ApplicationRoot.qml
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-import QtQuick 2.1
-import QtWebEngine 1.1
-
-QtObject {
- id: root
-
- property bool thirdPartyCookiesEnabled: true
-
- property QtObject testProfile: WebEngineProfile {
- storageName: "Test"
- }
-
- property QtObject otrProfile: WebEngineProfile {
- offTheRecord: true
- }
-
- property Component browserWindowComponent: BrowserWindow {
- applicationRoot: root
- onClosing: destroy()
- }
- property Component browserDialogComponent: BrowserDialog {
- onClosing: destroy()
- }
- function createWindow(profile) {
- var newWindow = browserWindowComponent.createObject(root)
- newWindow.currentWebView.profile = profile
- profile.downloadRequested.connect(newWindow.onDownloadRequested)
- profile.presentNotification.connect(newWindow.onPresentNotification)
- return newWindow
- }
- function createDialog(profile) {
- var newDialog = browserDialogComponent.createObject(root)
- newDialog.currentWebView.profile = profile
- return newDialog
- }
- function load(url) {
- var browserWindow = createWindow(testProfile)
- browserWindow.currentWebView.url = url
- }
-}
diff --git a/tests/quicktestbrowser/BrowserDialog.qml b/tests/quicktestbrowser/BrowserDialog.qml
deleted file mode 100644
index a896223dc..000000000
--- a/tests/quicktestbrowser/BrowserDialog.qml
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-import QtQuick 2.1
-import QtQuick.Window 2.2
-import QtWebEngine 1.1
-
-Window {
- property alias currentWebView: webView
- flags: Qt.Dialog
- width: 800
- height: 600
- visible: true
- onClosing: destroy()
- WebEngineView {
- id: webView
- anchors.fill: parent
- }
-}
diff --git a/tests/quicktestbrowser/BrowserWindow.qml b/tests/quicktestbrowser/BrowserWindow.qml
deleted file mode 100644
index 699e877a9..000000000
--- a/tests/quicktestbrowser/BrowserWindow.qml
+++ /dev/null
@@ -1,507 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-import QtQuick 2.1
-import QtWebEngine 1.2
-
-import QtQuick.Controls 1.0
-import QtQuick.Controls.Styles 1.0
-import QtQuick.Layouts 1.0
-import QtQuick.Window 2.1
-import QtQuick.Controls.Private 1.0
-import Qt.labs.settings 1.0
-import QtQuick.Dialogs 1.2
-
-ApplicationWindow {
- id: browserWindow
- property QtObject applicationRoot
- property Item currentWebView: tabs.currentIndex < tabs.count ? tabs.getTab(tabs.currentIndex).item.webView : null
- property int previousVisibility: Window.Windowed
-
- property bool isFullScreen: visibility == Window.FullScreen
- onIsFullScreenChanged: {
- // This is for the case where the system forces us to leave fullscreen.
- if (currentWebView && !isFullScreen) {
- currentWebView.state = ""
- if (currentWebView.isFullScreen) {
- currentWebView.fullScreenCancelled()
- fullScreenNotification.hide()
- }
- }
- }
-
- height: 600
- width: 800
- visible: true
- title: currentWebView && currentWebView.title
-
- Settings {
- id : appSettings
- property alias autoLoadImages: loadImages.checked;
- property alias javaScriptEnabled: javaScriptEnabled.checked;
- property alias errorPageEnabled: errorPageEnabled.checked;
- property alias pluginsEnabled: pluginsEnabled.checked;
- property alias thirdPartyCookiesEnabled: thirdPartyCookiesEnabled.checked;
- }
-
- // Make sure the Qt.WindowFullscreenButtonHint is set on OS X.
- Component.onCompleted: flags = flags | Qt.WindowFullscreenButtonHint
-
- // Create a styleItem to determine the platform.
- // When using style "mac", ToolButtons are not supposed to accept focus.
- StyleItem { id: styleItem }
- property bool platformIsMac: styleItem.style == "mac"
-
- Action {
- shortcut: "Ctrl+D"
- onTriggered: {
- downloadView.visible = !downloadView.visible
- }
- }
-
- Action {
- id: focus
- shortcut: "Ctrl+L"
- onTriggered: {
- addressBar.forceActiveFocus();
- addressBar.selectAll();
- }
- }
- Action {
- shortcut: "Ctrl+R"
- onTriggered: {
- if (currentWebView)
- currentWebView.reload()
- }
- }
- Action {
- shortcut: "Ctrl+T"
- onTriggered: {
- tabs.createEmptyTab(currentWebView.profile)
- tabs.currentIndex = tabs.count - 1
- addressBar.forceActiveFocus();
- addressBar.selectAll();
- }
- }
- Action {
- shortcut: "Ctrl+W"
- onTriggered: {
- if (tabs.count == 1)
- browserWindow.close()
- else
- tabs.removeTab(tabs.currentIndex)
- }
- }
-
- Action {
- shortcut: "Escape"
- onTriggered: {
- if (browserWindow.isFullScreen)
- browserWindow.visibility = browserWindow.previousVisibility
- }
- }
- Action {
- shortcut: "Ctrl+0"
- onTriggered: zoomController.reset()
- }
- Action {
- shortcut: "Ctrl+-"
- onTriggered: zoomController.zoomOut()
- }
- Action {
- shortcut: "Ctrl+="
- onTriggered: zoomController.zoomIn()
- }
-
- Menu {
- id: backHistoryMenu
-
- Instantiator {
- model: currentWebView && currentWebView.history.backItems
- MenuItem {
- text: model.title
- onTriggered: currentWebView.goBackOrForward(model.offset)
- }
-
- onObjectAdded: backHistoryMenu.insertItem(index, object)
- onObjectRemoved: backHistoryMenu.removeItem(object)
- }
- }
-
- Menu {
- id: forwardHistoryMenu
-
- Instantiator {
- model: currentWebView && currentWebView.history.forwardItems
- MenuItem {
- text: model.title
- onTriggered: currentWebView.goBackOrForward(model.offset)
- }
-
- onObjectAdded: forwardHistoryMenu.insertItem(index, object)
- onObjectRemoved: forwardHistoryMenu.removeItem(object)
- }
- }
-
- toolBar: ToolBar {
- id: navigationBar
- RowLayout {
- anchors.fill: parent;
- ButtonWithMenu {
- id: backButton
- iconSource: "icons/go-previous.png"
- enabled: currentWebView && currentWebView.canGoBack
- activeFocusOnTab: !browserWindow.platformIsMac
- onClicked: currentWebView.goBack()
- longPressMenu: backHistoryMenu
- }
- ButtonWithMenu {
- id: forwardButton
- iconSource: "icons/go-next.png"
- enabled: currentWebView && currentWebView.canGoForward
- activeFocusOnTab: !browserWindow.platformIsMac
- onClicked: currentWebView.goForward()
- longPressMenu: forwardHistoryMenu
- }
- ToolButton {
- id: reloadButton
- iconSource: currentWebView && currentWebView.loading ? "icons/process-stop.png" : "icons/view-refresh.png"
- onClicked: currentWebView && currentWebView.loading ? currentWebView.stop() : currentWebView.reload()
- activeFocusOnTab: !browserWindow.platformIsMac
- }
- TextField {
- id: addressBar
- Image {
- anchors.verticalCenter: addressBar.verticalCenter;
- x: 5
- z: 2
- id: faviconImage
- width: 16; height: 16
- source: currentWebView && currentWebView.icon
- }
- style: TextFieldStyle {
- padding {
- left: 26;
- }
- }
- focus: true
- Layout.fillWidth: true
- text: currentWebView && currentWebView.url
- onAccepted: currentWebView.url = utils.fromUserInput(text)
- }
- ToolButton {
- id: settingsMenuButton
- menu: Menu {
- MenuItem {
- id: loadImages
- text: "Autoload images"
- checkable: true
- checked: true
- }
- MenuItem {
- id: javaScriptEnabled
- text: "JavaScript On"
- checkable: true
- checked: true
- }
- MenuItem {
- id: errorPageEnabled
- text: "ErrorPage On"
- checkable: true
- checked: true
- }
- MenuItem {
- id: pluginsEnabled
- text: "Plugins On"
- checkable: true
- checked: true
- }
- MenuItem {
- id: thirdPartyCookiesEnabled
- text: "Third party cookies enabled"
- checkable: true
- checked: true
- onToggled: applicationRoot.thirdPartyCookiesEnabled = checked
- }
- MenuItem {
- id: offTheRecordEnabled
- text: "Off The Record"
- checkable: true
- checked: currentWebView.profile.offTheRecord
- onToggled: currentWebView.profile = checked ? otrProfile : testProfile;
- }
- MenuItem {
- id: httpDiskCacheEnabled
- text: "HTTP Disk Cache"
- checkable: !currentWebView.profile.offTheRecord
- checked: (currentWebView.profile.httpCacheType == WebEngineProfile.DiskHttpCache)
- onToggled: currentWebView.profile.httpCacheType = checked ? WebEngineProfile.DiskHttpCache : WebEngineProfile.MemoryHttpCache;
- }
- }
- }
- }
- ProgressBar {
- id: progressBar
- height: 3
- anchors {
- left: parent.left
- top: parent.bottom
- right: parent.right
- leftMargin: -parent.leftMargin
- rightMargin: -parent.rightMargin
- }
- style: ProgressBarStyle {
- background: Item {}
- }
- z: -2;
- minimumValue: 0
- maximumValue: 100
- value: (currentWebView && currentWebView.loadProgress < 100) ? currentWebView.loadProgress : 0
- }
- }
-
- TabView {
- id: tabs
- function createEmptyTab(profile) {
- var tab = addTab("", tabComponent)
- // We must do this first to make sure that tab.active gets set so that tab.item gets instantiated immediately.
- tab.active = true
- tab.title = Qt.binding(function() { return tab.item.title })
- tab.item.webView.profile = profile
- return tab
- }
-
- anchors.fill: parent
- Component.onCompleted: createEmptyTab(testProfile)
-
- Component {
- id: tabComponent
- Item {
- property alias webView: webEngineView
- property alias title: webEngineView.title
- Action {
- shortcut: "Ctrl+F"
- onTriggered: {
- findBar.visible = !findBar.visible
- if (findBar.visible) {
- findTextField.forceActiveFocus()
- }
- }
- }
- FeaturePermissionBar {
- id: permBar
- view: webEngineView
- anchors {
- left: parent.left
- right: parent.right
- top: parent.top
- }
- z: 3
- }
-
- WebEngineView {
- id: webEngineView
-
- anchors {
- fill: parent
- top: permBar.bottom
- }
-
- focus: true
-
- states: [
- State {
- name: "FullScreen"
- PropertyChanges {
- target: tabs
- frameVisible: false
- tabsVisible: false
- }
- PropertyChanges {
- target: navigationBar
- visible: false
- }
- }
- ]
- settings.autoLoadImages: appSettings.autoLoadImages
- settings.javascriptEnabled: appSettings.javaScriptEnabled
- settings.errorPageEnabled: appSettings.errorPageEnabled
- settings.pluginsEnabled: appSettings.pluginsEnabled
-
- onCertificateError: {
- if (!acceptedCertificates.shouldAutoAccept(error)){
- error.defer()
- sslDialog.enqueue(error)
- } else{
- error.ignoreCertificateError()
- }
- }
-
- onNewWindowRequested: {
- if (!request.userInitiated)
- print("Warning: Blocked a popup window.")
- else if (request.destination == WebEngineView.NewViewInTab) {
- var tab = tabs.createEmptyTab(currentWebView.profile)
- tabs.currentIndex = tabs.count - 1
- request.openIn(tab.item.webView)
- } else if (request.destination == WebEngineView.NewViewInBackgroundTab) {
- var tab = tabs.createEmptyTab(currentWebView.profile)
- request.openIn(tab.item.webView)
- } else if (request.destination == WebEngineView.NewViewInDialog) {
- var dialog = applicationRoot.createDialog(currentWebView.profile)
- request.openIn(dialog.currentWebView)
- } else {
- var window = applicationRoot.createWindow(currentWebView.profile)
- request.openIn(window.currentWebView)
- }
- }
-
- onFullScreenRequested: {
- if (request.toggleOn) {
- webEngineView.state = "FullScreen"
- browserWindow.previousVisibility = browserWindow.visibility
- browserWindow.showFullScreen()
- fullScreenNotification.show()
- } else {
- webEngineView.state = ""
- browserWindow.visibility = browserWindow.previousVisibility
- fullScreenNotification.hide()
- }
- request.accept()
- }
-
- onFeaturePermissionRequested: {
- permBar.securityOrigin = securityOrigin;
- permBar.requestedFeature = feature;
- permBar.visible = true;
- }
- }
-
- Rectangle {
- id: findBar
- anchors.top: webEngineView.top
- anchors.right: webEngineView.right
- width: 240
- height: 35
- border.color: "lightgray"
- border.width: 1
- radius: 5
- visible: false
- color: browserWindow.color
-
- RowLayout {
- anchors.centerIn: findBar
- TextField {
- id: findTextField
- onAccepted: {
- webEngineView.findText(text)
- }
- }
- ToolButton {
- id: findBackwardButton
- iconSource: "icons/go-previous.png"
- onClicked: webEngineView.findText(findTextField.text, WebEngineView.FindBackward)
- }
- ToolButton {
- id: findForwardButton
- iconSource: "icons/go-next.png"
- onClicked: webEngineView.findText(findTextField.text)
- }
- ToolButton {
- id: findCancelButton
- iconSource: "icons/process-stop.png"
- onClicked: findBar.visible = false
- }
- }
- }
- }
- }
- }
-
- QtObject{
- id:acceptedCertificates
-
- property var acceptedUrls : []
-
- function shouldAutoAccept(certificateError){
- var domain = utils.domainFromString(certificateError.url)
- return acceptedUrls.indexOf(domain) >= 0
- }
- }
-
- MessageDialog {
- id: sslDialog
-
- property var certErrors: []
- icon: StandardIcon.Warning
- standardButtons: StandardButton.No | StandardButton.Yes
- title: "Server's certificate not trusted"
- text: "Do you wish to continue?"
- detailedText: "If you wish so, you may continue with an unverified certificate. " +
- "Accepting an unverified certificate means " +
- "you may not be connected with the host you tried to connect to.\n" +
- "Do you wish to override the security check and continue?"
- onYes: {
- var cert = certErrors.shift()
- var domain = utils.domainFromString(cert.url)
- acceptedCertificates.acceptedUrls.push(domain)
- cert.ignoreCertificateError()
- presentError()
- }
- onNo: reject()
- onRejected: reject()
-
- function reject(){
- certErrors.shift().rejectCertificate()
- presentError()
- }
- function enqueue(error){
- certErrors.push(error)
- presentError()
- }
- function presentError(){
- visible = certErrors.length > 0
- }
- }
-
- FullScreenNotification {
- id: fullScreenNotification
- }
-
- DownloadView {
- id: downloadView
- visible: false
- anchors.fill: parent
- }
-
- function onDownloadRequested(download) {
- downloadView.visible = true
- downloadView.append(download)
- download.accept()
- }
-
- MessageDialog {
- id: notificationDialog
- width: 200
- standardButtons: StandardButton.Ok
- }
-
- function onPresentNotification(notification) {
- notificationDialog.title = notification.title
- notificationDialog.text = notification.origin.toString() + '\n' + notification.message
- notificationDialog.open()
- }
-
- ZoomController {
- id: zoomController
- y: parent.mapFromItem(currentWebView, 0 , 0).y - 4
- anchors.right: parent.right
- width: (parent.width > 800) ? parent.width * 0.25 : 220
- anchors.rightMargin: (parent.width > 400) ? 100 : 0
- }
- Binding {
- target: currentWebView
- property: "zoomFactor"
- value: zoomController.zoomFactor
- }
-}
diff --git a/tests/quicktestbrowser/ButtonWithMenu.qml b/tests/quicktestbrowser/ButtonWithMenu.qml
deleted file mode 100644
index 56a4d3533..000000000
--- a/tests/quicktestbrowser/ButtonWithMenu.qml
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-import QtQuick 2.3
-import QtQuick.Controls 1.2
-
-// The QtQuick controls guys are slackers, so we need to make our own stuff
-
-ToolButton {
- id: root
- property Menu longPressMenu
- function showMenu() {
- longPressMenu.__popup(Qt.rect(0, root.height, 0, 0), 0)
- }
-
- Binding {
- target: longPressMenu
- property: "__visualItem"
- value: root
- }
-
- MouseArea {
- anchors.fill: parent
- acceptedButtons: Qt.LeftButton | Qt.RightButton
- onClicked: {
- if (mouse.button == Qt.RightButton)
- showMenu()
- else
- root.clicked()
- }
- onPressAndHold: showMenu()
- }
-}
diff --git a/tests/quicktestbrowser/DownloadView.qml b/tests/quicktestbrowser/DownloadView.qml
deleted file mode 100644
index f4834edad..000000000
--- a/tests/quicktestbrowser/DownloadView.qml
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-import QtQuick 2.1
-import QtQuick.Controls 1.0
-import QtQuick.Controls.Styles 1.0
-import QtWebEngine 1.0
-import QtQuick.Layouts 1.0
-
-Rectangle {
- id: downloadView
- color: "lightgray"
-
- ListModel {
- id: downloadModel
- property var downloads: []
- }
-
- function append(download) {
- downloadModel.append(download)
- downloadModel.downloads.push(download)
- }
-
- Component {
- id: downloadItemDelegate
-
- Rectangle {
- width: listView.width
- height: childrenRect.height
- anchors.margins: 10
- radius: 3
- color: "transparent"
- border.color: "black"
- Rectangle {
- id: progressBar
-
- property real progress: downloadModel.downloads[index]
- ? downloadModel.downloads[index].receivedBytes / downloadModel.downloads[index].totalBytes : 0
-
- radius: 3
- color: width == listView.width ? "green" : "#2b74c7"
- width: listView.width * progress
- height: cancelButton.height
-
- Behavior on width {
- SmoothedAnimation { duration: 100 }
- }
- }
- Rectangle {
- anchors {
- left: parent.left
- right: parent.right
- leftMargin: 20
- }
- Label {
- id: label
- text: path
- anchors {
- verticalCenter: cancelButton.verticalCenter
- left: parent.left
- right: cancelButton.left
- }
- }
- Button {
- id: cancelButton
- anchors.right: parent.right
- iconSource: "icons/process-stop.png"
- onClicked: {
- var download = downloadModel.downloads[index]
-
- download.cancel()
-
- downloadModel.downloads = downloadModel.downloads.filter(function (el) {
- return el.id !== download.id;
- });
- downloadModel.remove(index)
- }
- }
- }
- }
-
- }
- ListView {
- id: listView
- anchors {
- topMargin: 10
- top: parent.top
- bottom: parent.bottom
- horizontalCenter: parent.horizontalCenter
- }
- width: parent.width - 20
- spacing: 5
-
- model: downloadModel
- delegate: downloadItemDelegate
-
- Text {
- visible: !listView.count
- horizontalAlignment: Text.AlignHCenter
- height: 30
- anchors {
- top: parent.top
- left: parent.left
- right: parent.right
- }
- font.pixelSize: 20
- text: "No active downloads."
- }
-
- Rectangle {
- color: "gray"
- anchors {
- bottom: parent.bottom
- left: parent.left
- right: parent.right
- }
- height: 30
- Button {
- id: okButton
- text: "OK"
- anchors.centerIn: parent
- onClicked: {
- downloadView.visible = false
- }
- }
- }
- }
-}
diff --git a/tests/quicktestbrowser/FeaturePermissionBar.qml b/tests/quicktestbrowser/FeaturePermissionBar.qml
deleted file mode 100644
index ca75875f5..000000000
--- a/tests/quicktestbrowser/FeaturePermissionBar.qml
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-import QtQuick 2.1
-import QtQuick.Controls 1.0
-import QtWebEngine 1.1
-import QtQuick.Layouts 1.0
-
-Rectangle {
- property var requestedFeature;
- property url securityOrigin;
- property WebEngineView view;
-
- id: permissionBar
- visible: false
- height: acceptButton.height + 4
-
-
- function textForFeature(feature) {
- switch (feature) {
- case WebEngineView.Geolocation: return 'Allow %1 to access your location information?'
- case WebEngineView.MediaAudioCapture: return 'Allow %1 to access your microphone?'
- case WebEngineView.MediaVideoCapture: return 'Allow %1 to access your webcam?'
- case WebEngineView.MediaAudioVideoCapture: return 'Allow %1 to access your microphone and webcam?'
- case WebEngineView.DesktopVideoCapture: return 'Allow %1 to capture video of your desktop?'
- case WebEngineView.DesktopAudioVideoCapture: return 'Allow %1 to capture audio and video of your desktop?'
- case WebEngineView.Notifications: return 'Allow %1 to show notification on your desktop?'
- default: break
- }
- return 'Grant permission for %1 to unknown or unsupported feature [' + feature + ']?'
- }
-
- onRequestedFeatureChanged: {
- message.text = textForFeature(requestedFeature).arg(securityOrigin);
- }
-
- RowLayout {
- anchors {
- fill: permissionBar
- leftMargin: 5
- rightMargin: 5
- }
- Label {
- id: message
- Layout.fillWidth: true
- }
-
- Button {
- id: acceptButton
- text: "Accept"
- Layout.alignment: Qt.AlignRight
- onClicked: {
- view.grantFeaturePermission(securityOrigin, requestedFeature, true);
- permissionBar.visible = false;
- }
- }
-
- Button {
- text: "Deny"
- Layout.alignment: Qt.AlignRight
- onClicked: {
- view.grantFeaturePermission(securityOrigin, requestedFeature, false);
- permissionBar.visible = false
- }
- }
- }
-}
diff --git a/tests/quicktestbrowser/FullScreenNotification.qml b/tests/quicktestbrowser/FullScreenNotification.qml
deleted file mode 100644
index e48d6c646..000000000
--- a/tests/quicktestbrowser/FullScreenNotification.qml
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-import QtQuick 2.5
-
-Rectangle {
- id: fullScreenNotification
- width: 500
- height: 40
- color: "white"
- radius: 7
-
- visible: false
- opacity: 0
-
- function show() {
- visible = true
- opacity = 1
- reset.start()
- }
-
- function hide() {
- reset.stop()
- opacity = 0
- }
-
- Behavior on opacity {
- NumberAnimation {
- duration: 750
- onStopped: {
- if (opacity == 0)
- visible = false
- }
- }
- }
-
- Timer {
- id: reset
- interval: 5000
- onTriggered: hide()
- }
-
- anchors.horizontalCenter: parent.horizontalCenter
- y: 125
-
- Text {
- id: message
- width: parent.width
-
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.verticalCenter: parent.verticalCenter
-
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
-
- wrapMode: Text.WordWrap
- elide: Text.ElideNone
- clip: true
-
- text: qsTr("You are now in fullscreen mode. Press ESC to quit!")
- }
-}
diff --git a/tests/quicktestbrowser/ZoomController.qml b/tests/quicktestbrowser/ZoomController.qml
deleted file mode 100644
index 2122c4365..000000000
--- a/tests/quicktestbrowser/ZoomController.qml
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-import QtQuick 2.3
-import QtQuick.Controls 1.2
-import QtQuick.Layouts 1.1
-
-Rectangle {
- property alias zoomFactor: slider.value ;
- function zoomIn() {
- visible = true
- visibilityTimer.restart()
- zoomFactor = zoomFactor + 0.25;
- }
- function zoomOut() {
- visible = true
- visibilityTimer.restart()
- zoomFactor = zoomFactor - 0.25;
- }
- function reset() { zoomFactor = 1.0 }
-
- width: 220
- height: 30
- color: palette.window
- visible: false
- radius: 4
-
- SystemPalette {
- id: palette
- }
- Timer {
- id: visibilityTimer
- interval: 3000
- repeat: false
- onTriggered: zoomController.visible = false
- }
-
- RowLayout {
- anchors.margins: 4
- anchors.fill: parent
- ToolButton {
- id: plusButton
- text: '+'
- onClicked: zoomIn()
- }
- ToolButton {
- text: '\u2014'
- id: minusButton
- onClicked: zoomOut()
- }
- Slider {
- id: slider
- maximumValue: 5.0
- minimumValue: 0.25
- Layout.fillWidth: true;
- stepSize: 0.05
- value: 1
- onValueChanged: visibilityTimer.restart()
- }
- Button {
- text: "Reset"
- onClicked: reset()
- }
- }
-}
diff --git a/tests/quicktestbrowser/main.cpp b/tests/quicktestbrowser/main.cpp
deleted file mode 100644
index c41eb2f56..000000000
--- a/tests/quicktestbrowser/main.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-// 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 "utils.h"
-
-#ifndef QT_NO_WIDGETS
-#include <QtWidgets/QApplication>
-typedef QApplication Application;
-#else
-#include <QtGui/QGuiApplication>
-typedef QGuiApplication Application;
-#endif
-#include <QtQml/QQmlApplicationEngine>
-#include <QtQml/QQmlContext>
-#include <QtQml/QQmlComponent>
-#include <QtWebEngineQuick/qtwebenginequickglobal.h>
-#include <QtWebEngineQuick/QQuickWebEngineProfile>
-#include <QtWebEngineCore/qwebenginecookiestore.h>
-
-static QUrl startupUrl()
-{
- QUrl ret;
- QStringList args(qApp->arguments());
- args.takeFirst();
- for (const QString &arg : std::as_const(args)) {
- if (arg.startsWith(QLatin1Char('-')))
- continue;
- ret = Utils::fromUserInput(arg);
- if (ret.isValid())
- return ret;
- }
- return QUrl(QStringLiteral("http://qt.io/"));
-}
-
-int main(int argc, char **argv)
-{
- Application app(argc, argv);
-
- // Enable dev tools by default for the test browser
- if (qgetenv("QTWEBENGINE_REMOTE_DEBUGGING").isNull())
- qputenv("QTWEBENGINE_REMOTE_DEBUGGING", "1337");
- QtWebEngineQuick::initialize();
-
- QQmlApplicationEngine appEngine;
- Utils utils;
- appEngine.rootContext()->setContextProperty("utils", &utils);
- appEngine.load(QUrl("qrc:/ApplicationRoot.qml"));
-
- QObject *rootObject = appEngine.rootObjects().first();
-
- QQuickWebEngineProfile *profile = new QQuickWebEngineProfile(rootObject);
-
- const QMetaObject *rootMeta = rootObject->metaObject();
- int index = rootMeta->indexOfProperty("thirdPartyCookiesEnabled");
- Q_ASSERT(index != -1);
- QMetaProperty thirdPartyCookiesProperty = rootMeta->property(index);
- profile->cookieStore()->setCookieFilter(
- [rootObject,&thirdPartyCookiesProperty](const QWebEngineCookieStore::FilterRequest &request)
- {
- return !request.thirdParty || thirdPartyCookiesProperty.read(rootObject).toBool();
- });
-
- index = rootMeta->indexOfProperty("testProfile");
- Q_ASSERT(index != -1);
- QMetaProperty profileProperty = rootMeta->property(index);
- profileProperty.write(rootObject, QVariant::fromValue(profile));
-
- QMetaObject::invokeMethod(rootObject, "load", Q_ARG(QVariant, startupUrl()));
-
- return app.exec();
-}
diff --git a/tests/quicktestbrowser/quicktestbrowser.pro b/tests/quicktestbrowser/quicktestbrowser.pro
deleted file mode 100644
index 304135836..000000000
--- a/tests/quicktestbrowser/quicktestbrowser.pro
+++ /dev/null
@@ -1,26 +0,0 @@
-requires(qtConfig(accessibility))
-
-TEMPLATE = app
-TARGET = quicktestbrowser
-
-macx: CONFIG -= app_bundle
-
-HEADERS = utils.h
-SOURCES = main.cpp
-
-OTHER_FILES += ApplicationRoot.qml \
- BrowserDialog.qml \
- BrowserWindow.qml \
- ButtonWithMenu.qml \
- ContextMenuExtras.qml \
- DownloadView.qml \
- FeaturePermissionBar.qml \
- FullScreenNotification.qml
-
-RESOURCES += resources.qrc
-
-QT += qml quick webenginequick
-
-qtHaveModule(widgets) {
- QT += widgets # QApplication is required to get native styling with QtQuickControls
-}
diff --git a/tests/quicktestbrowser/resources.qrc b/tests/quicktestbrowser/resources.qrc
deleted file mode 100644
index 8fbe286fb..000000000
--- a/tests/quicktestbrowser/resources.qrc
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE RCC><RCC version="1.0">
- <qresource prefix="/">
- <file>ApplicationRoot.qml</file>
- <file>BrowserDialog.qml</file>
- <file>BrowserWindow.qml</file>
- <file>FeaturePermissionBar.qml</file>
- <file>FullScreenNotification.qml</file>
- <file>ButtonWithMenu.qml</file>
- <file>DownloadView.qml</file>
- <file>ZoomController.qml</file>
- </qresource>
- <qresource prefix="icons">
- <!-- To the risk of this breaking more often, do not duplicate the resources since this application won't be deployed -->
- <file alias="go-next.png">../../examples/webenginequick/quicknanobrowser/icons/3rdparty/go-next.png</file>
- <file alias="go-previous.png">../../examples/webenginequick/quicknanobrowser/icons/3rdparty/go-previous.png</file>
- <file alias="process-stop.png">../../examples/webenginequick/quicknanobrowser/icons/3rdparty/process-stop.png</file>
- <file alias="view-refresh.png">../../examples/webenginequick/quicknanobrowser/icons/3rdparty/view-refresh.png</file>
- </qresource>
-</RCC>
diff --git a/tests/quicktestbrowser/utils.h b/tests/quicktestbrowser/utils.h
deleted file mode 100644
index 8521e9bf5..000000000
--- a/tests/quicktestbrowser/utils.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#ifndef UTILS_H
-#define UTILS_H
-
-#include <QtCore/QFileInfo>
-#include <QtCore/QUrl>
-
-class Utils : public QObject {
- Q_OBJECT
-public:
- Q_INVOKABLE static QUrl fromUserInput(const QString& userInput);
- Q_INVOKABLE static QString domainFromString(const QString& urlString);
-};
-
-inline QUrl Utils::fromUserInput(const QString& userInput)
-{
- QFileInfo fileInfo(userInput);
- if (fileInfo.exists())
- return QUrl::fromLocalFile(fileInfo.absoluteFilePath());
- return QUrl::fromUserInput(userInput);
-}
-
-inline QString Utils::domainFromString(const QString& urlString)
-{
- return QUrl::fromUserInput(urlString).host();
-}
-
-#endif // UTILS_H