diff options
Diffstat (limited to 'tests/auto')
34 files changed, 1897 insertions, 156 deletions
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 06430cf8e..59bcd5aef 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -1,9 +1,9 @@ TEMPLATE = subdirs -SUBDIRS = quick +qtHaveModule(webengine) { + SUBDIRS += quick +} qtHaveModule(webenginewidgets) { - SUBDIRS += widgets -# core tests depend on widgets for now - SUBDIRS += core + SUBDIRS += core widgets } diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp b/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp index 7d3ad1440..c0762aa14 100644 --- a/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp +++ b/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp @@ -65,6 +65,7 @@ private Q_SLOTS: void requestInterceptorByResourceType(); void firstPartyUrlHttp(); void passRefererHeader(); + void initiator(); }; tst_QWebEngineUrlRequestInterceptor::tst_QWebEngineUrlRequestInterceptor() @@ -95,11 +96,13 @@ struct RequestInfo { RequestInfo(QWebEngineUrlRequestInfo &info) : requestUrl(info.requestUrl()) , firstPartyUrl(info.firstPartyUrl()) + , initiator(info.initiator()) , resourceType(info.resourceType()) {} QUrl requestUrl; QUrl firstPartyUrl; + QUrl initiator; int resourceType; }; @@ -111,6 +114,7 @@ class TestRequestInterceptor : public QWebEngineUrlRequestInterceptor public: QList<RequestInfo> requestInfos; bool shouldIntercept; + QMap<QUrl, QUrl> requestInitiatorUrls; void interceptRequest(QWebEngineUrlRequestInfo &info) override { @@ -125,6 +129,7 @@ public: // Set referrer header info.setHttpHeader(kHttpHeaderRefererName, kHttpHeaderReferrerValue); + requestInitiatorUrls.insert(info.requestUrl(), info.initiator()); requestInfos.append(info); } @@ -553,5 +558,69 @@ void tst_QWebEngineUrlRequestInterceptor::passRefererHeader() QVERIFY(succeeded); } +void tst_QWebEngineUrlRequestInterceptor::initiator() +{ + QWebEngineProfile profile; + TestRequestInterceptor interceptor(/* intercept */ false); + profile.setUrlRequestInterceptor(&interceptor); + + QWebEnginePage page(&profile); + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + QUrl url = QUrl("https://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_video"); + page.setUrl(QUrl(url)); + if (!loadSpy.wait(15000) || !loadSpy.at(0).at(0).toBool()) + QSKIP("Couldn't load page from network, skipping test."); + + QList<RequestInfo> infos; + + // SubFrame + QTRY_VERIFY(interceptor.hasUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeSubFrame)); + infos = interceptor.getUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeSubFrame); + foreach (auto info, infos) + QCOMPARE(info.initiator, interceptor.requestInitiatorUrls[info.requestUrl]); + + // Stylesheet + QTRY_VERIFY(interceptor.hasUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeStylesheet)); + infos = interceptor.getUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeStylesheet); + foreach (auto info, infos) + QCOMPARE(info.initiator, interceptor.requestInitiatorUrls[info.requestUrl]); + + // Script + QTRY_VERIFY(interceptor.hasUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeScript)); + infos = interceptor.getUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeScript); + foreach (auto info, infos) + QCOMPARE(info.initiator, interceptor.requestInitiatorUrls[info.requestUrl]); + + // Image + QTRY_VERIFY(interceptor.hasUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeImage)); + infos = interceptor.getUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeImage); + foreach (auto info, infos) + QCOMPARE(info.initiator, interceptor.requestInitiatorUrls[info.requestUrl]); + + // FontResource + QTRY_VERIFY(interceptor.hasUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeFontResource)); + infos = interceptor.getUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeFontResource); + foreach (auto info, infos) + QCOMPARE(info.initiator, interceptor.requestInitiatorUrls[info.requestUrl]); + + // Media + QTRY_VERIFY(interceptor.hasUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeMedia)); + infos = interceptor.getUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeMedia); + foreach (auto info, infos) + QCOMPARE(info.initiator, interceptor.requestInitiatorUrls[info.requestUrl]); + + // Favicon + QTRY_VERIFY(interceptor.hasUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeFavicon)); + infos = interceptor.getUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeFavicon); + foreach (auto info, infos) + QCOMPARE(info.initiator, interceptor.requestInitiatorUrls[info.requestUrl]); + + // XMLHttpRequest + QTRY_VERIFY(interceptor.hasUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeXhr)); + infos = interceptor.getUrlRequestForType(QWebEngineUrlRequestInfo::ResourceTypeXhr); + foreach (auto info, infos) + QCOMPARE(info.initiator, interceptor.requestInitiatorUrls[info.requestUrl]); +} + QTEST_MAIN(tst_QWebEngineUrlRequestInterceptor) #include "tst_qwebengineurlrequestinterceptor.moc" diff --git a/tests/auto/quick/dialogs/BLACKLIST b/tests/auto/quick/dialogs/BLACKLIST new file mode 100644 index 000000000..10b7391a0 --- /dev/null +++ b/tests/auto/quick/dialogs/BLACKLIST @@ -0,0 +1,8 @@ +[contextMenuRequested] +osx-10.13 +[javaScriptDialogRequested] +osx-10.13 +[colorDialogRequested] +osx-10.13 +[fileDialogRequested] +osx-10.13 diff --git a/tests/auto/quick/publicapi/tst_publicapi.cpp b/tests/auto/quick/publicapi/tst_publicapi.cpp index 90b768ac7..9f7dfa8ad 100644 --- a/tests/auto/quick/publicapi/tst_publicapi.cpp +++ b/tests/auto/quick/publicapi/tst_publicapi.cpp @@ -35,6 +35,7 @@ #include <QtTest/QtTest> #include <QtWebEngine/QQuickWebEngineProfile> #include <QtWebEngine/QQuickWebEngineScript> +#include <QtWebEngineCore/QWebEngineFindTextResult> #include <QtWebEngineCore/QWebEngineNotification> #include <QtWebEngineCore/QWebEngineQuotaRequest> #include <QtWebEngineCore/QWebEngineRegisterProtocolHandlerRequest> @@ -80,10 +81,12 @@ static const QList<const QMetaObject *> typesToCheck = QList<const QMetaObject * << &QQuickWebEngineColorDialogRequest::staticMetaObject << &QQuickWebEngineFileDialogRequest::staticMetaObject << &QQuickWebEngineFormValidationMessageRequest::staticMetaObject + << &QQuickWebEngineTooltipRequest::staticMetaObject << &QQuickWebEngineContextMenuRequest::staticMetaObject << &QWebEngineQuotaRequest::staticMetaObject << &QWebEngineRegisterProtocolHandlerRequest::staticMetaObject << &QWebEngineNotification::staticMetaObject + << &QWebEngineFindTextResult::staticMetaObject ; static QList<const char *> knownEnumNames = QList<const char *>(); @@ -259,6 +262,12 @@ static const QStringList expectedAPI = QStringList() << "QQuickWebEngineDownloadItem.type --> DownloadType" << "QQuickWebEngineDownloadItem.typeChanged() --> void" << "QQuickWebEngineDownloadItem.view --> QQuickWebEngineView*" + << "QQuickWebEngineDownloadItem.url --> QUrl" + << "QQuickWebEngineDownloadItem.suggestedFileName --> QString" + << "QQuickWebEngineDownloadItem.downloadDirectory --> QString" + << "QQuickWebEngineDownloadItem.downloadDirectoryChanged() --> void" + << "QQuickWebEngineDownloadItem.downloadFileName --> QString" + << "QQuickWebEngineDownloadItem.downloadFileNameChanged() --> void" << "QQuickWebEngineFileDialogRequest.FileModeOpen --> FileMode" << "QQuickWebEngineFileDialogRequest.FileModeOpenMultiple --> FileMode" << "QQuickWebEngineFileDialogRequest.FileModeSave --> FileMode" @@ -269,6 +278,8 @@ static const QStringList expectedAPI = QStringList() << "QQuickWebEngineFileDialogRequest.dialogAccept(QStringList) --> void" << "QQuickWebEngineFileDialogRequest.dialogReject() --> void" << "QQuickWebEngineFileDialogRequest.mode --> FileMode" + << "QWebEngineFindTextResult.numberOfMatches --> int" + << "QWebEngineFindTextResult.activeMatchOrdinal --> int" << "QQuickWebEngineFormValidationMessageRequest.Hide --> RequestType" << "QQuickWebEngineFormValidationMessageRequest.Move --> RequestType" << "QQuickWebEngineFormValidationMessageRequest.Show --> RequestType" @@ -277,6 +288,13 @@ static const QStringList expectedAPI = QStringList() << "QQuickWebEngineFormValidationMessageRequest.subText --> QString" << "QQuickWebEngineFormValidationMessageRequest.text --> QString" << "QQuickWebEngineFormValidationMessageRequest.type --> RequestType" + << "QQuickWebEngineTooltipRequest.Hide --> RequestType" + << "QQuickWebEngineTooltipRequest.Show --> RequestType" + << "QQuickWebEngineTooltipRequest.x --> int" + << "QQuickWebEngineTooltipRequest.y --> int" + << "QQuickWebEngineTooltipRequest.text --> QString" + << "QQuickWebEngineTooltipRequest.type --> RequestType" + << "QQuickWebEngineTooltipRequest.accepted --> bool" << "QQuickWebEngineFullScreenRequest.accept() --> void" << "QQuickWebEngineFullScreenRequest.origin --> QUrl" << "QQuickWebEngineFullScreenRequest.reject() --> void" @@ -596,6 +614,9 @@ static const QStringList expectedAPI = QStringList() << "QQuickWebEngineView.LetterExtra --> PrintedPageSizeId" << "QQuickWebEngineView.LetterPlus --> PrintedPageSizeId" << "QQuickWebEngineView.LetterSmall --> PrintedPageSizeId" + << "QQuickWebEngineView.LifecycleState.Active --> LifecycleState" + << "QQuickWebEngineView.LifecycleState.Discarded --> LifecycleState" + << "QQuickWebEngineView.LifecycleState.Frozen --> LifecycleState" << "QQuickWebEngineView.LinkClickedNavigation --> NavigationType" << "QQuickWebEngineView.LoadFailedStatus --> LoadStatus" << "QQuickWebEngineView.LoadStartedStatus --> LoadStatus" @@ -628,6 +649,7 @@ static const QStringList expectedAPI = QStringList() << "QQuickWebEngineView.Prc32K --> PrintedPageSizeId" << "QQuickWebEngineView.Prc32KBig --> PrintedPageSizeId" << "QQuickWebEngineView.Quarto --> PrintedPageSizeId" + << "QQuickWebEngineView.RedirectNavigation --> NavigationType" << "QQuickWebEngineView.Redo --> WebAction" << "QQuickWebEngineView.Reload --> WebAction" << "QQuickWebEngineView.ReloadAndBypassCache --> WebAction" @@ -676,6 +698,7 @@ static const QStringList expectedAPI = QStringList() << "QQuickWebEngineView.findText(QString) --> void" << "QQuickWebEngineView.findText(QString,FindFlags) --> void" << "QQuickWebEngineView.findText(QString,FindFlags,QJSValue) --> void" + << "QQuickWebEngineView.findTextFinished(QWebEngineFindTextResult) --> void" << "QQuickWebEngineView.formValidationMessageRequested(QQuickWebEngineFormValidationMessageRequest*) --> void" << "QQuickWebEngineView.fullScreenCancelled() --> void" << "QQuickWebEngineView.fullScreenRequested(QQuickWebEngineFullScreenRequest) --> void" @@ -692,6 +715,8 @@ static const QStringList expectedAPI = QStringList() << "QQuickWebEngineView.isFullScreenChanged() --> void" << "QQuickWebEngineView.javaScriptConsoleMessage(JavaScriptConsoleMessageLevel,QString,int,QString) --> void" << "QQuickWebEngineView.javaScriptDialogRequested(QQuickWebEngineJavaScriptDialogRequest*) --> void" + << "QQuickWebEngineView.lifecycleState --> LifecycleState" + << "QQuickWebEngineView.lifecycleStateChanged(LifecycleState) --> void" << "QQuickWebEngineView.linkHovered(QUrl) --> void" << "QQuickWebEngineView.loadHtml(QString) --> void" << "QQuickWebEngineView.loadHtml(QString,QUrl) --> void" @@ -715,6 +740,8 @@ static const QStringList expectedAPI = QStringList() << "QQuickWebEngineView.quotaRequested(QWebEngineQuotaRequest) --> void" << "QQuickWebEngineView.recentlyAudible --> bool" << "QQuickWebEngineView.recentlyAudibleChanged(bool) --> void" + << "QQuickWebEngineView.recommendedState --> LifecycleState" + << "QQuickWebEngineView.recommendedStateChanged(LifecycleState) --> void" << "QQuickWebEngineView.registerProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest) --> void" << "QQuickWebEngineView.reload() --> void" << "QQuickWebEngineView.reloadAndBypassCache() --> void" @@ -736,6 +763,7 @@ static const QStringList expectedAPI = QStringList() #endif << "QQuickWebEngineView.title --> QString" << "QQuickWebEngineView.titleChanged() --> void" + << "QQuickWebEngineView.tooltipRequested(QQuickWebEngineTooltipRequest*) --> void" << "QQuickWebEngineView.triggerWebAction(WebAction) --> void" << "QQuickWebEngineView.url --> QUrl" << "QQuickWebEngineView.urlChanged() --> void" @@ -811,8 +839,9 @@ static void checkKnownType(const QByteArray &typeName) static void gatherAPI(const QString &prefix, const QMetaEnum &metaEnum, QStringList *output) { + const auto format = metaEnum.isScoped() ? "%1%3.%2 --> %3" : "%1%2 --> %3"; for (int i = 0; i < metaEnum.keyCount(); ++i) - *output << QString::fromLatin1("%1%2 --> %3").arg(prefix).arg(metaEnum.key(i)).arg(metaEnum.name()); + *output << QString::fromLatin1(format).arg(prefix).arg(metaEnum.key(i)).arg(metaEnum.name()); } static void gatherAPI(const QString &prefix, const QMetaProperty &property, QStringList *output) diff --git a/tests/auto/quick/qmltests/data/tst_action.qml b/tests/auto/quick/qmltests/data/tst_action.qml index 56a91d8d0..852d4145a 100644 --- a/tests/auto/quick/qmltests/data/tst_action.qml +++ b/tests/auto/quick/qmltests/data/tst_action.qml @@ -51,8 +51,8 @@ TestWebEngineView { { webAction: WebEngineView.Forward, text: "Forward", iconName: "go-next", enabled: false }, { webAction: WebEngineView.Stop, text: "Stop", iconName: "", enabled: false }, { webAction: WebEngineView.Reload, text: "Reload", iconName: "view-refresh", enabled: true }, - { webAction: WebEngineView.Cut, text: "Cut", iconName: "Cut", enabled: true }, - { webAction: WebEngineView.Copy, text: "Copy", iconName: "", enabled: true }, + { webAction: WebEngineView.Cut, text: "Cut", iconName: "Cut", enabled: false }, + { webAction: WebEngineView.Copy, text: "Copy", iconName: "", enabled: false }, { webAction: WebEngineView.Paste, text: "Paste", iconName: "", enabled: true }, { webAction: WebEngineView.Undo, text: "Undo", iconName: "", enabled: true }, { webAction: WebEngineView.Redo, text: "Redo", iconName: "", enabled: true }, @@ -76,7 +76,7 @@ TestWebEngineView { { webAction: WebEngineView.InspectElement, text: "Inspect", iconName: "", enabled: true }, { webAction: WebEngineView.ExitFullScreen, text: "Exit full screen", iconName: "", enabled: true }, { webAction: WebEngineView.RequestClose, text: "Close Page", iconName: "", enabled: true }, - { webAction: WebEngineView.Unselect, text: "Unselect", iconName: "", enabled: true }, + { webAction: WebEngineView.Unselect, text: "Unselect", iconName: "", enabled: false }, { webAction: WebEngineView.SavePage, text: "Save page", iconName: "", enabled: true }, { webAction: WebEngineView.ViewSource, text: "View page source", iconName: "view-source", enabled: true }, { webAction: WebEngineView.ToggleBold, text: "&Bold", iconName: "", enabled: true }, @@ -110,17 +110,17 @@ TestWebEngineView { webEngineView.url = Qt.resolvedUrl("test1.html"); verify(webEngineView.waitForLoadSucceeded()); - var copyAction = webEngineView.action(WebEngineView.Copy); - verify(copyAction); + var selectAction = webEngineView.action(WebEngineView.SelectAll); + verify(selectAction); var stopAction = webEngineView.action(WebEngineView.Stop); verify(stopAction); - var triggerSpy = createTemporaryObject(signalSpy, actionTests, {target: copyAction, signalName: "triggered"}); + var triggerSpy = createTemporaryObject(signalSpy, actionTests, {target: selectAction, signalName: "triggered"}); var stopTriggerSpy = createTemporaryObject(signalSpy, actionTests, {target: stopAction, signalName: "triggered"}); - verify(copyAction.enabled); - copyAction.trigger(); + verify(selectAction.enabled); + selectAction.trigger(); compare(triggerSpy.count, 1); verify(!stopAction.enabled); diff --git a/tests/auto/quick/qmltests/data/tst_download.qml b/tests/auto/quick/qmltests/data/tst_download.qml index 5eb704cce..e049f3621 100644 --- a/tests/auto/quick/qmltests/data/tst_download.qml +++ b/tests/auto/quick/qmltests/data/tst_download.qml @@ -28,7 +28,7 @@ import QtQuick 2.0 import QtTest 1.0 -import QtWebEngine 1.9 +import QtWebEngine 1.10 import Qt.labs.platform 1.0 TestWebEngineView { @@ -42,10 +42,22 @@ TestWebEngineView { property bool cancelDownload: false property var downloadState: [] property var downloadInterruptReason: null + property url downloadUrl: "" + property string suggestedFileName: "" + property string downloadDirectory: "" + property string downloadFileName: "" + property string downloadedPath: "" + property string downloadedSetPath: "" + property int downloadDirectoryChanged: 0 + property int downloadFileNameChanged: 0 + property int downloadPathChanged: 0 function urlToPath(url) { var path = url.toString() - path = path.replace(/^(file:\/{2})/,"") + if (Qt.platform.os !== "windows") + path = path.replace(/^(file:\/{2})/, "") + else + path = path.replace(/^(file:\/{3})/, "") return path } @@ -66,21 +78,36 @@ TestWebEngineView { ignoreUnknownSignals: true onStateChanged: downloadState.push(target.state) onInterruptReasonChanged: downloadInterruptReason = target.interruptReason + onDownloadDirectoryChanged: downloadDirectoryChanged++ + onDownloadFileNameChanged: downloadFileNameChanged++ + onPathChanged: downloadPathChanged++ } WebEngineProfile { id: testDownloadProfile onDownloadRequested: { + testDownloadProfile.downloadPath = urlToPath(StandardPaths.writableLocation(StandardPaths.TempLocation)) downloadState.push(download.state) downloadItemConnections.target = download if (cancelDownload) { download.cancel() } else { totalBytes = download.totalBytes - download.path = "testfile.zip" + + if (downloadedSetPath.length != 0) { + download.path = testDownloadProfile.downloadPath + downloadedSetPath + downloadedPath = download.path + } else { + download.downloadDirectory = downloadDirectory.length != 0 ? testDownloadProfile.downloadPath + downloadDirectory : testDownloadProfile.downloadPath + download.downloadFileName = downloadFileName.length != 0 ? downloadFileName : "testfile.zip" + downloadedPath = download.downloadDirectory + download.downloadFileName + } + download.accept() } + downloadUrl = download.url + suggestedFileName = download.suggestedFileName } onDownloadFinished: { receivedBytes = download.receivedBytes; @@ -99,6 +126,13 @@ TestWebEngineView { downloadItemConnections.target = null downloadState = [] downloadInterruptReason = null + downloadDirectoryChanged = 0 + downloadFileNameChanged = 0 + downloadPathChanged = 0 + downloadDirectory = "" + downloadFileName = "" + downloadedPath = "" + downloadedSetPath = "" } function test_downloadRequest() { @@ -106,6 +140,8 @@ TestWebEngineView { webEngineView.url = Qt.resolvedUrl("download.zip") downLoadRequestedSpy.wait() compare(downLoadRequestedSpy.count, 1) + compare(downloadUrl, webEngineView.url) + compare(suggestedFileName, "download.zip") compare(downloadState[0], WebEngineDownloadItem.DownloadRequested) verify(!downloadInterruptReason) } @@ -115,6 +151,8 @@ TestWebEngineView { webEngineView.url = Qt.resolvedUrl("download.zip") downLoadRequestedSpy.wait() compare(downLoadRequestedSpy.count, 1) + compare(downloadUrl, webEngineView.url) + compare(suggestedFileName, "download.zip") compare(totalBytes, 325) verify(!downloadInterruptReason) } @@ -124,6 +162,8 @@ TestWebEngineView { webEngineView.url = Qt.resolvedUrl("download.zip") downLoadRequestedSpy.wait() compare(downLoadRequestedSpy.count, 1) + compare(downloadUrl, webEngineView.url) + compare(suggestedFileName, "download.zip") compare(downloadState[0], WebEngineDownloadItem.DownloadRequested) tryCompare(downloadState, "1", WebEngineDownloadItem.DownloadInProgress) downloadFinishedSpy.wait() @@ -138,6 +178,8 @@ TestWebEngineView { webEngineView.url = Qt.resolvedUrl("download.zip") downLoadRequestedSpy.wait() compare(downLoadRequestedSpy.count, 1) + compare(downloadUrl, webEngineView.url) + compare(suggestedFileName, "download.zip") compare(downloadFinishedSpy.count, 1) tryCompare(downloadState, "1", WebEngineDownloadItem.DownloadCancelled) tryCompare(webEngineView, "downloadInterruptReason", WebEngineDownloadItem.UserCanceled) @@ -153,5 +195,50 @@ TestWebEngineView { testDownloadProfile.downloadPath = downloadPath; compare(testDownloadProfile.downloadPath, downloadPath); } + + function test_downloadToDirectoryWithFileName() { + compare(downLoadRequestedSpy.count, 0); + compare(downloadDirectoryChanged, 0); + compare(downloadFileNameChanged, 0); + downloadDirectory = "/test/"; + downloadFileName = "test.zip"; + webEngineView.url = Qt.resolvedUrl("download.zip"); + downLoadRequestedSpy.wait(); + compare(downLoadRequestedSpy.count, 1); + compare(downloadUrl, webEngineView.url); + compare(suggestedFileName, "download.zip"); + compare(downloadState[0], WebEngineDownloadItem.DownloadRequested); + tryCompare(downloadState, "1", WebEngineDownloadItem.DownloadInProgress); + compare(downloadedPath, testDownloadProfile.downloadPath + downloadDirectory + downloadFileName); + compare(downloadDirectoryChanged, 1); + compare(downloadFileNameChanged, 1); + compare(downloadPathChanged, 2); + downloadFinishedSpy.wait(); + compare(totalBytes, receivedBytes); + tryCompare(downloadState, "2", WebEngineDownloadItem.DownloadCompleted); + verify(!downloadInterruptReason); + } + + function test_downloadWithSetPath() { + compare(downLoadRequestedSpy.count, 0); + compare(downloadDirectoryChanged, 0); + compare(downloadFileNameChanged, 0); + downloadedSetPath = "/test/test.zip"; + webEngineView.url = Qt.resolvedUrl("download.zip"); + downLoadRequestedSpy.wait(); + compare(downLoadRequestedSpy.count, 1); + compare(downloadUrl, webEngineView.url); + compare(suggestedFileName, "download.zip"); + compare(downloadState[0], WebEngineDownloadItem.DownloadRequested); + tryCompare(downloadState, "1", WebEngineDownloadItem.DownloadInProgress); + compare(downloadedPath, testDownloadProfile.downloadPath + downloadedSetPath); + compare(downloadDirectoryChanged, 1); + compare(downloadFileNameChanged, 1); + compare(downloadPathChanged, 2); + downloadFinishedSpy.wait(); + compare(totalBytes, receivedBytes); + tryCompare(downloadState, "2", WebEngineDownloadItem.DownloadCompleted); + verify(!downloadInterruptReason); + } } } diff --git a/tests/auto/quick/qmltests/data/tst_findText.qml b/tests/auto/quick/qmltests/data/tst_findText.qml index 14053a675..040d324e6 100644 --- a/tests/auto/quick/qmltests/data/tst_findText.qml +++ b/tests/auto/quick/qmltests/data/tst_findText.qml @@ -38,9 +38,16 @@ TestWebEngineView { property int matchCount: 0 property bool findFailed: false + SignalSpy { + id: findTextSpy + target: webEngineView + signalName: "findTextFinished" + } + function clear() { findFailed = false matchCount = -1 + findTextSpy.clear() } function findCallbackCalled() { return matchCount != -1 } @@ -104,6 +111,9 @@ TestWebEngineView { webEngineView.findText("Hello", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 1) verify(!findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 1) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 1) } function test_findTextCaseInsensitive() { @@ -115,6 +125,9 @@ TestWebEngineView { webEngineView.findText("heLLo", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 1) verify(!findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 1) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 1) } function test_findTextManyMatches() { @@ -126,6 +139,9 @@ TestWebEngineView { webEngineView.findText("bla", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 100, 20000) verify(!findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 100) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 1) } @@ -138,6 +154,9 @@ TestWebEngineView { webEngineView.findText("heLLo", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 0) verify(findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 0) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 0) } function test_findTextNotFound() { @@ -149,6 +168,9 @@ TestWebEngineView { webEngineView.findText("string-that-is-not-threre", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 0) verify(findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 0) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 0) } function test_findTextAfterNotFound() { @@ -160,6 +182,9 @@ TestWebEngineView { webEngineView.findText("hello", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 0) verify(findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 0) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 0) webEngineView.url = Qt.resolvedUrl("test1.html") verify(webEngineView.waitForLoadSucceeded()) @@ -168,6 +193,9 @@ TestWebEngineView { webEngineView.findText("hello", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 1) verify(!findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 1) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 1) } function test_findTextInModifiedDOMAfterNotFound() { @@ -182,6 +210,9 @@ TestWebEngineView { webEngineView.findText("hello", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 0, 20000) verify(findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 0) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 0) runJavaScript("document.body.innerHTML = 'blahellobla'"); tryVerify(function() { return getBodyInnerHTML() == "blahellobla"; }, 20000); @@ -190,6 +221,9 @@ TestWebEngineView { webEngineView.findText("hello", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 1) verify(!findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 1) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 1) } function test_findTextInterruptedByLoad() { @@ -227,5 +261,55 @@ TestWebEngineView { webEngineView.findText('New page', findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, 'matchCount', 1) } + + function test_findTextActiveMatchOrdinal() { + webEngineView.loadHtml( + "<html><body>" + + "foo bar foo bar foo" + + "</body></html>"); + verify(webEngineView.waitForLoadSucceeded()); + + // Iterate over all "foo" matches. + webEngineView.clear(); + for (var i = 1; i <= 3; ++i) { + webEngineView.findText("foo"); + findTextSpy.wait(); + compare(findTextSpy.count, i); + compare(findTextSpy.signalArguments[i-1][0].numberOfMatches, 3); + compare(findTextSpy.signalArguments[i-1][0].activeMatchOrdinal, i); + } + + // The last match is followed by the fist one. + webEngineView.clear(); + webEngineView.findText("foo"); + findTextSpy.wait(); + compare(findTextSpy.count, 1); + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 3); + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 1); + + // The first match is preceded by the last one. + webEngineView.clear(); + webEngineView.findText("foo", WebEngineView.FindBackward); + findTextSpy.wait(); + compare(findTextSpy.count, 1); + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 3); + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 3); + + // Finding another word resets the activeMatchOrdinal. + webEngineView.clear(); + webEngineView.findText("bar"); + findTextSpy.wait(); + compare(findTextSpy.count, 1); + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 2); + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 1); + + // If no match activeMatchOrdinal is 0. + webEngineView.clear(); + webEngineView.findText("bla"); + findTextSpy.wait(); + compare(findTextSpy.count, 1); + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 0); + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 0); + } } } diff --git a/tests/auto/quick/qquickwebengineview/BLACKLIST b/tests/auto/quick/qquickwebengineview/BLACKLIST index 49c5332ff..d4d5c9844 100644 --- a/tests/auto/quick/qquickwebengineview/BLACKLIST +++ b/tests/auto/quick/qquickwebengineview/BLACKLIST @@ -1,5 +1,2 @@ -[javascriptClipboard:default] -opensuse-leap - -[javascriptClipboard:canPaste] -opensuse-leap +[transparentWebEngineViews] +windows diff --git a/tests/auto/shared/https.pri b/tests/auto/shared/https.pri new file mode 100644 index 000000000..ce4c147f7 --- /dev/null +++ b/tests/auto/shared/https.pri @@ -0,0 +1,4 @@ +include($$PWD/http.pri) + +HEADERS += $$PWD/httpsserver.h +RESOURCES += $$PWD/httpsserver.qrc diff --git a/tests/auto/shared/httpserver.cpp b/tests/auto/shared/httpserver.cpp index b85af9901..e282fc8b8 100644 --- a/tests/auto/shared/httpserver.cpp +++ b/tests/auto/shared/httpserver.cpp @@ -31,9 +31,21 @@ Q_LOGGING_CATEGORY(gHttpServerLog, "HttpServer") -HttpServer::HttpServer(QObject *parent) : QObject(parent) +HttpServer::HttpServer(QObject *parent) : HttpServer(new QTcpServer, "http", parent) { - connect(&m_tcpServer, &QTcpServer::newConnection, this, &HttpServer::handleNewConnection); +} + +HttpServer::HttpServer(QTcpServer *tcpServer, const QString &protocol, QObject *parent) + : QObject(parent), m_tcpServer(tcpServer) +{ + m_url.setHost(QStringLiteral("127.0.0.1")); + m_url.setScheme(protocol); + connect(tcpServer, &QTcpServer::newConnection, this, &HttpServer::handleNewConnection); +} + +HttpServer::~HttpServer() +{ + delete m_tcpServer; } bool HttpServer::start() @@ -41,21 +53,18 @@ bool HttpServer::start() m_error = false; m_expectingError = false; - if (!m_tcpServer.listen()) { - qCWarning(gHttpServerLog).noquote() << m_tcpServer.errorString(); + if (!m_tcpServer->listen()) { + qCWarning(gHttpServerLog).noquote() << m_tcpServer->errorString(); return false; } - m_url.setScheme(QStringLiteral("http")); - m_url.setHost(QStringLiteral("127.0.0.1")); - m_url.setPort(m_tcpServer.serverPort()); - + m_url.setPort(m_tcpServer->serverPort()); return true; } bool HttpServer::stop() { - m_tcpServer.close(); + m_tcpServer->close(); return m_error == m_expectingError; } @@ -73,12 +82,12 @@ QUrl HttpServer::url(const QString &path) const void HttpServer::handleNewConnection() { - auto rr = new HttpReqRep(m_tcpServer.nextPendingConnection(), this); + auto rr = new HttpReqRep(m_tcpServer->nextPendingConnection(), this); connect(rr, &HttpReqRep::requestReceived, [this, rr]() { Q_EMIT newRequest(rr); rr->close(); }); - connect(rr, &HttpReqRep::responseSent, [this, rr]() { + connect(rr, &HttpReqRep::responseSent, [rr]() { qCInfo(gHttpServerLog).noquote() << rr->requestMethod() << rr->requestPath() << rr->responseStatus() << rr->responseBody().size(); }); diff --git a/tests/auto/shared/httpserver.h b/tests/auto/shared/httpserver.h index b4649244e..57f824bb5 100644 --- a/tests/auto/shared/httpserver.h +++ b/tests/auto/shared/httpserver.h @@ -59,19 +59,22 @@ class HttpServer : public QObject Q_OBJECT public: explicit HttpServer(QObject *parent = nullptr); + explicit HttpServer(QTcpServer *server, const QString &protocol, QObject *parent = nullptr); + + ~HttpServer() override; // Must be called to start listening. // // Returns true if a TCP port has been successfully bound. - Q_REQUIRED_RESULT bool start(); + Q_INVOKABLE Q_REQUIRED_RESULT bool start(); // Stops listening and performs final error checks. - Q_REQUIRED_RESULT bool stop(); + Q_INVOKABLE Q_REQUIRED_RESULT bool stop(); - void setExpectError(bool b); + Q_INVOKABLE void setExpectError(bool b); // Full URL for given relative path - QUrl url(const QString &path = QStringLiteral("/")) const; + Q_INVOKABLE QUrl url(const QString &path = QStringLiteral("/")) const; Q_SIGNALS: // Emitted after a HTTP request has been successfully parsed. @@ -81,7 +84,7 @@ private Q_SLOTS: void handleNewConnection(); private: - QTcpServer m_tcpServer; + QTcpServer *m_tcpServer; QUrl m_url; bool m_error = false; bool m_expectingError = false; diff --git a/tests/auto/shared/httpsserver.h b/tests/auto/shared/httpsserver.h new file mode 100644 index 000000000..32c8e8345 --- /dev/null +++ b/tests/auto/shared/httpsserver.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef HTTPSSERVER_H +#define HTTPSSERVER_H + +#include "httpreqrep.h" +#include "httpserver.h" + +#include <QDebug> +#include <QFile> +#include <QSslKey> +#include <QSslSocket> +#include <QSslConfiguration> +#include <QTcpServer> + +struct SslTcpServer : QTcpServer +{ + SslTcpServer() { + sslconf.setLocalCertificateChain(QSslCertificate::fromPath(":/resources/cert.pem")); + sslconf.setPrivateKey(readKey(":/resources/key.pem")); + } + + void incomingConnection(qintptr d) override { + auto socket = new QSslSocket(this); + socket->setSslConfiguration(sslconf); + + if (!socket->setSocketDescriptor(d)) { + qWarning() << "Failed to setup ssl socket!"; + delete socket; + return; + } + + connect(socket, QOverload<QSslSocket::SocketError>::of(&QSslSocket::error), + [] (QSslSocket::SocketError e) { qWarning() << "! Socket Error:" << e; }); + connect(socket, QOverload<const QList<QSslError> &>::of(&QSslSocket::sslErrors), + [] (const QList<QSslError> &le) { qWarning() << "! SSL Errors:\n" << le; }); + + addPendingConnection(socket); + socket->startServerEncryption(); + } + + QSslKey readKey(const QString &path) const { + QFile file(path); + file.open(QIODevice::ReadOnly); + return QSslKey(file.readAll(), QSsl::Rsa, QSsl::Pem); + } + + QSslConfiguration sslconf; +}; + +struct HttpsServer : HttpServer +{ + HttpsServer(QObject *parent = nullptr) : HttpServer(new SslTcpServer, "https", parent) { } +}; + +#endif diff --git a/tests/auto/shared/httpsserver.qrc b/tests/auto/shared/httpsserver.qrc new file mode 100644 index 000000000..ec57a1983 --- /dev/null +++ b/tests/auto/shared/httpsserver.qrc @@ -0,0 +1,6 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file>resources/cert.pem</file> + <file>resources/key.pem</file> +</qresource> +</RCC> diff --git a/tests/auto/shared/resources/cert.pem b/tests/auto/shared/resources/cert.pem new file mode 100644 index 000000000..3aaaf289c --- /dev/null +++ b/tests/auto/shared/resources/cert.pem @@ -0,0 +1,64 @@ +-----BEGIN CERTIFICATE----- +MIIEpDCCAoygAwIBAgIUO90aty9AMjvBvzfUhr1WwdBrKkMwDQYJKoZIhvcNAQEL +BQAwfzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xDzANBgNVBAoMBkJhZFNTTDEyMDAGA1UEAwwpQmFkU1NM +IEludGVybWVkaWF0ZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTkwODI2MTQ0 +NDIxWhcNMTkwODI3MTQ0NDIxWjBjMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2Fs +aWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0GA1UECgwGQmFkU1NM +MRYwFAYDVQQDDA0qLmJhZHNzbC50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAkybT/L4zJCqefpd+eYT6aQ0PtobQfFgP+n+z5wWoUxIAJnjb5ZW4 +7IJxka/2/ggzJOfrUBur54LkTfFQ+yX85eKYCuH0GLz+Rve50LDn0ya6qSgmEhDG +0bend2tMZY+Nl3B+5Ane1vua8hdJjv3ZO3e5UgpQwysL54eYyhEWWlbFWF11LhEd +MYp953UGLqoV4Mlw+Li8TmFwdKQx6icgBTuloXLzk9aUU+b6NbXdadNXkmzg09IC +sb8pnMXiF2P9Xm5rK0IoiRkSHxVnU12nQXh65Ns/2Dj5DcbHmVdvallfr4wnLeFP +UotysZnvFmE7FLMSr/eQfkTG+Jlb7ZhoGwIDAQABozQwMjAJBgNVHRMEAjAAMCUG +A1UdEQQeMByCDSouYmFkc3NsLnRlc3SCC2JhZHNzbC50ZXN0MA0GCSqGSIb3DQEB +CwUAA4ICAQA7Yc+QQzqSK15ibmaYrkqq+cumggsWLCprW8jvzhpWBt9IjToP5nsy +sKinYPoZR8jvZ1YVotcts7uQT7DkqeWkB+l+88c7gQdgujvBo6v9/g+jrXFKgsJD +IBmkho8hpd63Slqv2Yp4bYT20O5EvR9CQvwSkwTs+ylBNEs1Q+AbekxmBjuYUxHn +9xL4/GZ6ufoNv676iCoXo4mnDrCD8e8MRiZoU9Lq4G41HGiLWV0tM/M6BdVJYGzl +FcBg0ZKnQT9OCWEPRe3zyRS6a+MivPAzxS8z/kYaRN+C7H68Mib3xPDsEETz1MnO +uzGAPHAAgtYWYJi+CaaNWkgAv4n+UIQa0oyqPn4z5hLcsO+nMBws2Sg0mkQLilBX +N1ciCdVMi7sHKuLa7GVksq/RQrXnZcQhoYQRrZAaAHKbxyo/M2pNqmDiFJppdH7a +6Rj2vYf6ig/FXAzDGsDvf/tsGCxgJTFzGly+GsWVe40vyjfWHxWWDU/eGjfGO05k +Xzjm+kYGJnH2hfiIlX1Jeu/jjIodiSy31F0hvuKlJu8PfaQ7oo5neRzwRO6Wq9rR +7DMsQN6OtXGnnA+ogC0korA+aXev6wzbwYUhzMf1YTzEjrFNIXeIHsQSzq6lPcIE +JOly5wjyO/eNF7mpHyDX8brY6Hn+bgyDeKAmsUvhOCEXgaPpKlP4gQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGeTCCBGGgAwIBAgIUbVL7tFc7sgPIYnt+REVc0wiHdBcwDQYJKoZIhvcNAQEL +BQAwdzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xDzANBgNVBAoMBkJhZFNTTDEqMCgGA1UEAwwhQmFkU1NM +IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTE5MDgyNjE0NDQyMFoXDTI5 +MDgyMzE0NDQyMFowfzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx +FjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzANBgNVBAoMBkJhZFNTTDEyMDAGA1UE +AwwpQmFkU1NMIEludGVybWVkaWF0ZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgoU4q43DJEUyoAOeK31uyEgLn +s5CCd6XFmGp6wln0yupwmYRaDiCoSJ1qpmjYt+gIHpDAFS2ZzR4TbZORFirjY0cQ +6+IWwpBEQR0hOluWN99CqjdCxfuZwiTvTV3FQv1IJZ13g23Uh2xRbnrzC2muDHzT +4ZNM3aayvziMGY6n33aksEc6WMZb3p/Qn2OepeC7EzZiy4tXKPf9OaOPbae5aJWZ +bOzzydFLkV4UqZb5FfySt8toIivPeIlRCiPodWLb2y5DYUXyWBk1dpbIcVa/LusV +vsBELeJ+BFDRH1NHtwOrhOkZHKMr3SQ1YRlNDEeHUVmQkori397j9JjpPzScQJ6r +d/W4mGyzgRmguIy9IpKMbxX5/1A6c6l5q0HqMgPv84GWxlhav4xwsOf90iT2vLPZ +yllVCgCsCfvLEyVFhER18HAo8mTkQqKL7ZO96xXHgugA7dFN/C3BdC9kYP/GbAwd +J0R6qKrfSiyyk1VbjWfFdFH/G/bT9H0nrjMj5tCT4q/zDCb5HkBp3BOoyUKb9yyt +a1Cht/Iu3f1SlQzsrDBt9iMMCjXoNNAJcV7ZZ6HCxcWwfAwxgylQgq8UG60shxhn +CBPhcA8JM+mk2nghTU2pxwY/KpAd0H4/a79b0DE97dCOnNHzyP3tqP8RenG549B0 +gsNO60aG01k6P9jFuQIDAQABo4H0MIHxMB0GA1UdDgQWBBQgvWmDuYqQ6xX7y8xc +cgky1FO7jzCBtAYDVR0jBIGsMIGpgBTUGo+svIaoSMF/shILSbeiQ1zAQKF7pHkw +dzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNh +biBGcmFuY2lzY28xDzANBgNVBAoMBkJhZFNTTDEqMCgGA1UEAwwhQmFkU1NMIFJv +b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5ghR1qCPxzkfCSCwMFHm98245f0pk0zAM +BgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAnGr6 +t1+KNGZV9hmAE3SyMzHRpgwtqIG4kl94A7Pz3CbA8+q7u7DW8l1GdaNx2J2wo+R5 +rJi02V5e7TNa7ZS5S9WGYHZ2y6QOjXuT28VMAPX+3HAgxk3RMxocpLpkPp8hhD/9 +S5KxA6AQDUN6av8E3xeuuWYWmTvAXNHK5ABXDFxxTp902ozNnZaSk2DxAUqcsOD4 +ago0IhRdkFGe1Q7F8gOxtlUL5owNL4uhRP8BbwOja2Gopn2+kA9CNqdwPI4Ipjlr +yo61oCqzy3RAXOUct8WAvybacADmJODAxDq9O5fAZuYZScjjj1ASowmbyDH/Wb9z ++WfiKKH4BfgOIukzK3I1M9wiSDefIodCFfEVXbdNudZj8f9Gw4RrZwkUuxDLeRWG +ReDtzAWq7G0Diw3uX40S4jaj3MeS6oHp2Nrj/VyjSRiYTeN/pnA9N0M5VuCYYvXD +f50rrigjQfOgb4TmnyJAjXWVkXW7Fa+ooLsbvlfr8wP8f31y1cgWPHTVIv6Kmug7 +Bg88k3x5gLTXmutDjseORonhGMRdAxHgJVf5aKfzdRpwXZTDZJXhsAz9OdlOhNZd +UrYo680QugA0V3H5D8Egbr2AUUSMDkn133COjeOIDknFxX3qDqeTzqLZCAEBIoKn +Adpix0jvG1Ys4Ayq6K2wQFdGFjtl6LsiGC7pWWU= +-----END CERTIFICATE----- diff --git a/tests/auto/shared/resources/key.pem b/tests/auto/shared/resources/key.pem new file mode 100644 index 000000000..89922679a --- /dev/null +++ b/tests/auto/shared/resources/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAkybT/L4zJCqefpd+eYT6aQ0PtobQfFgP+n+z5wWoUxIAJnjb +5ZW47IJxka/2/ggzJOfrUBur54LkTfFQ+yX85eKYCuH0GLz+Rve50LDn0ya6qSgm +EhDG0bend2tMZY+Nl3B+5Ane1vua8hdJjv3ZO3e5UgpQwysL54eYyhEWWlbFWF11 +LhEdMYp953UGLqoV4Mlw+Li8TmFwdKQx6icgBTuloXLzk9aUU+b6NbXdadNXkmzg +09ICsb8pnMXiF2P9Xm5rK0IoiRkSHxVnU12nQXh65Ns/2Dj5DcbHmVdvallfr4wn +LeFPUotysZnvFmE7FLMSr/eQfkTG+Jlb7ZhoGwIDAQABAoIBADRXy3BL98UVo+tD +2ClBtBFKJBy5N9ADQyvH4SZ8TLO/423L7+xqpaz7eYppHWKfaBHorTuBnFRtquhO +vo+Xo63iPFMirMFf+NMlq2MgilYBoMQrE9+5N//BZECGWlaGCcekrH5RRIMUXLlg +rzm98lfE7pbQNIo39bQV97NpAJqBWPuoIvCrbRCysGoA5j7ptZ/EhSlC00eA7ybD +CeYHmh8NrsapKOTGb5u1v3paV8X/mH6vKmsVs7n6LC0opBxzM8eAHEAQ6h8rmz9H +y99FWDYha3lOS4SLkTnuRnNHOMLJajPq3Isu+BgzLWuRGnKZ3rmuUFwPNkCZTvsV +dTdBE4ECgYEAw6jBEil0e8Pc9sGqnz93e8qrYE9wSPso4q3BNJgTbN48kon6mqh7 +gQVgEP/75Th5YrJUrY9Pd/8H9uoMOxbDXgOXG/xNnhC0L+7aM8nhKlxCLndY1e56 +/YymYYH4+D9ZD2u526mK/nmCg2QGOkCVYYp7NXe/mA0g34drKjefmj8CgYEAwIhq +rZhlfAvQThSOqQA9zA7NXPDh4KzIjr8htVu5YvVcv5W2uhsni9DXFaloPnhuLdJ7 +MnPF2WqzQ9YqFrGn/9/OTqeE23f60ed04qLGM4BApb45y5Kw6sCPnWu7dMYfny9i +XeZA2A+ODmqVkrU+ZNVzqzS1krYyUP3exd1voyUCgYEAqPRARH6np3gqhqoVvA4C +D1OjSTdPrrWzSIriG5h2rbv6ck/Tp1l1zKPnoMZrrjRmHWQA2x61cNk4926DwUKW +0cgn5HKqU6P49Ks8oRvi48FnJNjKTXHxoqChy/GAHF4Xecl8ZMKy06v5l5v4BLVg +SSpb2n/dYl9z05IMaBhAKeECgYBKB2n1S6ah1q0GiLL92mDoiDyAYwKG8AjBkk40 +vIsAuNUruTYkQvKmuOsqohO6CXZb2hWSpZ9KZNN+3ucaCL9PDE/4QEM+W9iuQu/X +gLzy6npxAD6avtGVweq2ncjbMp7QB1ksP69pJDn74xGV8miGPuiVyNOUEMgyChtR +Oz6EnQKBgEth0w80CBg6b3NKuASoc/vC08njZQvWpe5xrzY2DL8epVKb1qf6+8SE +eX34cIcSaonEZ2g67MAeIG6jtmPwxWk4EYAsO1u4XiyziABkoNyLKVH4hZg61BsV +jL7R5UrUvBbhKLFOwkcB4Kwdwu7COB/UKa5XJBTMbuw1UTyxlUeI +-----END RSA PRIVATE KEY----- diff --git a/tests/auto/widgets/accessibility/tst_accessibility.cpp b/tests/auto/widgets/accessibility/tst_accessibility.cpp index b0165cfbc..d69a4c0a7 100644 --- a/tests/auto/widgets/accessibility/tst_accessibility.cpp +++ b/tests/auto/widgets/accessibility/tst_accessibility.cpp @@ -389,6 +389,7 @@ void tst_Accessibility::roles() QWebEngineView webView; webView.setHtml("<html><body>" + html + "</body></html>"); + webView.show(); QSignalSpy spyFinished(&webView, &QWebEngineView::loadFinished); QVERIFY(spyFinished.wait()); diff --git a/tests/auto/widgets/certificateerror/certificateerror.pro b/tests/auto/widgets/certificateerror/certificateerror.pro new file mode 100644 index 000000000..73ba7515b --- /dev/null +++ b/tests/auto/widgets/certificateerror/certificateerror.pro @@ -0,0 +1,3 @@ +include(../tests.pri) +include(../../shared/https.pri) +QT *= core-private diff --git a/tests/auto/widgets/certificateerror/tst_certificateerror.cpp b/tests/auto/widgets/certificateerror/tst_certificateerror.cpp new file mode 100644 index 000000000..5fd765ed5 --- /dev/null +++ b/tests/auto/widgets/certificateerror/tst_certificateerror.cpp @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <httpsserver.h> +#include <util.h> + +#include <QWebEngineCertificateError> +#include <QWebEnginePage> +#include <QWebEngineSettings> + +#include <QtTest/QtTest> + +class tst_CertificateError : public QObject +{ + Q_OBJECT +public: + tst_CertificateError() { } + +private Q_SLOTS: + void handleError_data(); + void handleError(); +}; + +struct PageWithCertificateErrorHandler : QWebEnginePage +{ + PageWithCertificateErrorHandler(bool defer, bool accept, QObject *p = nullptr) + : QWebEnginePage(p), deferError(defer), acceptCertificate(accept) { + connect(this, &QWebEnginePage::loadFinished, [&] (bool result) { spyLoad(result); }); + } + + bool deferError, acceptCertificate; + + CallbackSpy<bool> spyLoad; + QScopedPointer<QWebEngineCertificateError> error; + + bool certificateError(const QWebEngineCertificateError &e) override { + error.reset(new QWebEngineCertificateError(e)); + if (deferError) + error->defer(); + return acceptCertificate; + } +}; + +void tst_CertificateError::handleError_data() +{ + QTest::addColumn<bool>("deferError"); + QTest::addColumn<bool>("acceptCertificate"); + QTest::addColumn<QString>("expectedContent"); + QTest::addRow("Reject") << false << false << QString(); + QTest::addRow("DeferReject") << true << false << QString(); + QTest::addRow("DeferAccept") << true << true << "TEST"; +} + +void tst_CertificateError::handleError() +{ + HttpsServer server; + server.setExpectError(true); + QVERIFY(server.start()); + + connect(&server, &HttpsServer::newRequest, [&] (HttpReqRep *rr) { + rr->setResponseBody(QByteArrayLiteral("<html><body>TEST</body></html>")); + rr->sendResponse(); + }); + + QFETCH(bool, deferError); + QFETCH(bool, acceptCertificate); + QFETCH(QString, expectedContent); + + PageWithCertificateErrorHandler page(deferError, acceptCertificate); + page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); + + page.setUrl(server.url()); + QTRY_VERIFY(page.error); + QVERIFY(page.error->isOverridable()); + auto chain = page.error->chain(); + QCOMPARE(chain.size(), 2); + QCOMPARE(chain[0].serialNumber(), "3b:dd:1a:b7:2f:40:32:3b:c1:bf:37:d4:86:bd:56:c1:d0:6b:2a:43"); + QCOMPARE(chain[1].serialNumber(), "6d:52:fb:b4:57:3b:b2:03:c8:62:7b:7e:44:45:5c:d3:08:87:74:17"); + + if (deferError) { + QVERIFY(page.error->deferred()); + QVERIFY(!page.error->answered()); + QVERIFY(!page.spyLoad.wasCalled()); + QCOMPARE(toPlainTextSync(&page), QString()); + + if (acceptCertificate) + page.error->ignoreCertificateError(); + else + page.error->rejectCertificate(); + + QVERIFY(page.error->answered()); + page.error.reset(); + } + + bool loadResult = page.spyLoad.waitForResult(); + QVERIFY(page.spyLoad.wasCalled()); + QCOMPARE(loadResult, acceptCertificate); + QCOMPARE(toPlainTextSync(&page), expectedContent); +} + +QTEST_MAIN(tst_CertificateError) +#include <tst_certificateerror.moc> diff --git a/tests/auto/widgets/loadsignals/tst_loadsignals.cpp b/tests/auto/widgets/loadsignals/tst_loadsignals.cpp index e614f3751..c0bb8d5c5 100644 --- a/tests/auto/widgets/loadsignals/tst_loadsignals.cpp +++ b/tests/auto/widgets/loadsignals/tst_loadsignals.cpp @@ -238,7 +238,8 @@ void tst_LoadSignals::fileDownloadDoesNotTriggerLoadSignals_qtbug66661() connect(item, &QWebEngineDownloadItem::stateChanged, [&downloadState](QWebEngineDownloadItem::DownloadState newState){ downloadState = newState; }); - item->setPath(tempDir.filePath(QFileInfo(item->path()).fileName())); + item->setDownloadDirectory(tempDir.filePath(QFileInfo(item->path()).path())); + item->setDownloadFileName(QFileInfo(item->path()).fileName()); item->accept(); }); diff --git a/tests/auto/widgets/origins/resources/redirect.css b/tests/auto/widgets/origins/resources/redirect.css new file mode 100644 index 000000000..41d7560cc --- /dev/null +++ b/tests/auto/widgets/origins/resources/redirect.css @@ -0,0 +1,8 @@ +@font-face { + font-family: 'MyWebFont'; + src: url('redirect1:/resources/Akronim-Regular.woff2') format('woff2'); +} + +body { + font-family: 'MyWebFont', Fallback, sans-serif; +} diff --git a/tests/auto/widgets/origins/resources/redirect.html b/tests/auto/widgets/origins/resources/redirect.html new file mode 100644 index 000000000..04948e14b --- /dev/null +++ b/tests/auto/widgets/origins/resources/redirect.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <head> + <title>redirect</title> + <link rel="stylesheet" href="redirect1:/resources/redirect.css"> + </head> + <body> + Text + </body> +</html> diff --git a/tests/auto/widgets/origins/tst_origins.cpp b/tests/auto/widgets/origins/tst_origins.cpp index c1307c5e6..02d5bfba3 100644 --- a/tests/auto/widgets/origins/tst_origins.cpp +++ b/tests/auto/widgets/origins/tst_origins.cpp @@ -122,6 +122,18 @@ void registerSchemes() scheme.setDefaultPort(42); QWebEngineUrlScheme::registerScheme(scheme); } + + { + QWebEngineUrlScheme scheme(QBAL("redirect1")); + scheme.setFlags(QWebEngineUrlScheme::CorsEnabled); + QWebEngineUrlScheme::registerScheme(scheme); + } + + { + QWebEngineUrlScheme scheme(QBAL("redirect2")); + scheme.setFlags(QWebEngineUrlScheme::CorsEnabled); + QWebEngineUrlScheme::registerScheme(scheme); + } } Q_CONSTRUCTOR_FUNCTION(registerSchemes) @@ -145,13 +157,26 @@ public: profile->installUrlSchemeHandler(QBAL("HostSyntax-ContentSecurityPolicyIgnored"), this); profile->installUrlSchemeHandler(QBAL("HostAndPortSyntax"), this); profile->installUrlSchemeHandler(QBAL("HostPortAndUserInformationSyntax"), this); + profile->installUrlSchemeHandler(QBAL("redirect1"), this); + profile->installUrlSchemeHandler(QBAL("redirect2"), this); } + QVector<QUrl> &requests() { return m_requests; } + private: void requestStarted(QWebEngineUrlRequestJob *job) override { + QUrl url = job->requestUrl(); + m_requests << url; + + if (url.scheme() == QBAL("redirect1")) { + url.setScheme(QBAL("redirect2")); + job->redirect(url); + return; + } + QString pathPrefix = QSL(THIS_DIR); - QString pathSuffix = job->requestUrl().path(); + QString pathSuffix = url.path(); QFile *file = new QFile(pathPrefix + pathSuffix, job); if (!file->open(QIODevice::ReadOnly)) { job->fail(QWebEngineUrlRequestJob::RequestFailed); @@ -160,8 +185,12 @@ private: QByteArray mimeType = QBAL("text/html"); if (pathSuffix.endsWith(QSL(".js"))) mimeType = QBAL("application/javascript"); + else if (pathSuffix.endsWith(QSL(".css"))) + mimeType = QBAL("text/css"); job->reply(mimeType, file); } + + QVector<QUrl> m_requests; }; class tst_Origins final : public QObject { @@ -169,6 +198,7 @@ class tst_Origins final : public QObject { private Q_SLOTS: void initTestCase(); + void cleanup(); void cleanupTestCase(); void jsUrlCanon(); @@ -187,6 +217,7 @@ private Q_SLOTS: void serviceWorker(); void viewSource(); void createObjectURL(); + void redirect(); private: bool load(const QUrl &url) @@ -209,10 +240,19 @@ private: void tst_Origins::initTestCase() { + QTest::ignoreMessage( + QtWarningMsg, + QRegularExpression("Please register the custom scheme 'tst'.*")); + m_page = new QWebEnginePage(&m_profile, nullptr); m_handler = new TstUrlSchemeHandler(&m_profile); } +void tst_Origins::cleanup() +{ + m_handler->requests().clear(); +} + void tst_Origins::cleanupTestCase() { delete m_handler; @@ -346,11 +386,7 @@ void tst_Origins::jsUrlOrigin() QCOMPARE(eval(QSL("new URL(\"file:/etc/passwd\").origin")), QVariant(QSL("file://"))); QCOMPARE(eval(QSL("new URL(\"file://foo.com/etc/passwd\").origin")), QVariant(QSL("file://"))); - // The qrc scheme should behave like file. - QCOMPARE(eval(QSL("new URL(\"qrc:/crysis.css\").origin")), QVariant(QSL("qrc://"))); - QCOMPARE(eval(QSL("new URL(\"qrc://foo.com/crysis.css\").origin")), QVariant(QSL("qrc://"))); - - // Unregistered schemes behaves like opaque origins. + // Unregistered schemes behave like file. QCOMPARE(eval(QSL("new URL(\"tst:/banana\").origin")), QVariant(QSL("tst://"))); QCOMPARE(eval(QSL("new URL(\"tst://foo.com/banana\").origin")), QVariant(QSL("tst://"))); @@ -367,8 +403,9 @@ void tst_Origins::jsUrlOrigin() QVariant(QSL("hostportanduserinformationsyntax://foo"))); // A PathSyntax scheme should have a 'universal' origin. - QCOMPARE(eval(QSL("new URL(\"PathSyntax:foo\").origin")), - QVariant(QSL("pathsyntax://"))); + QCOMPARE(eval(QSL("new URL(\"PathSyntax:foo\").origin")), QVariant(QSL("pathsyntax:"))); + QCOMPARE(eval(QSL("new URL(\"qrc:/crysis.css\").origin")), QVariant(QSL("qrc:"))); + QCOMPARE(eval(QSL("new URL(\"qrc://foo.com/crysis.css\").origin")), QVariant(QSL("qrc:"))); // The NoAccessAllowed flag forces opaque origins. QCOMPARE(eval(QSL("new URL(\"PathSyntax-NoAccessAllowed:foo\").origin")), @@ -709,7 +746,7 @@ void tst_Origins::serviceWorker() QVERIFY(load(QSL("qrc:/resources/serviceWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); QVERIFY(eval(QSL("error")).toString() - .contains(QSL("The URL protocol of the current origin ('qrc://') is not supported."))); + .contains(QSL("The URL protocol of the current origin ('qrc:') is not supported."))); QVERIFY(load(QSL("tst:/resources/serviceWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); @@ -724,7 +761,7 @@ void tst_Origins::serviceWorker() QVERIFY(load(QSL("PathSyntax-Secure:/resources/serviceWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); QVERIFY(eval(QSL("error")).toString() - .contains(QSL("The URL protocol of the current origin ('pathsyntax-secure://') is not supported."))); + .contains(QSL("The URL protocol of the current origin ('pathsyntax-secure:') is not supported."))); QVERIFY(load(QSL("PathSyntax-ServiceWorkersAllowed:/resources/serviceWorker.html"))); QTRY_VERIFY(eval(QSL("done")).toBool()); @@ -775,5 +812,18 @@ void tst_Origins::createObjectURL() QVERIFY(eval(QSL("result")).toString().startsWith(QSL("blob:tst:"))); } +void tst_Origins::redirect() +{ + QVERIFY(load(QSL("redirect1:/resources/redirect.html"))); + QTRY_COMPARE(m_handler->requests().size(), 7); + QCOMPARE(m_handler->requests()[0], QUrl(QStringLiteral("redirect1:/resources/redirect.html"))); + QCOMPARE(m_handler->requests()[1], QUrl(QStringLiteral("redirect2:/resources/redirect.html"))); + QCOMPARE(m_handler->requests()[2], QUrl(QStringLiteral("redirect1:/resources/redirect.css"))); + QCOMPARE(m_handler->requests()[3], QUrl(QStringLiteral("redirect2:/resources/redirect.css"))); + QCOMPARE(m_handler->requests()[4], QUrl(QStringLiteral("redirect1:/resources/Akronim-Regular.woff2"))); + QCOMPARE(m_handler->requests()[5], QUrl(QStringLiteral("redirect1:/resources/Akronim-Regular.woff2"))); + QCOMPARE(m_handler->requests()[6], QUrl(QStringLiteral("redirect2:/resources/Akronim-Regular.woff2"))); +} + QTEST_MAIN(tst_Origins) #include "tst_origins.moc" diff --git a/tests/auto/widgets/qwebenginedownloaditem/tst_qwebenginedownloaditem.cpp b/tests/auto/widgets/qwebenginedownloaditem/tst_qwebenginedownloaditem.cpp index 3566c2216..6dc7f03c1 100644 --- a/tests/auto/widgets/qwebenginedownloaditem/tst_qwebenginedownloaditem.cpp +++ b/tests/auto/widgets/qwebenginedownloaditem/tst_qwebenginedownloaditem.cpp @@ -78,6 +78,7 @@ private Q_SLOTS: void downloadToNonExistentDir(); void downloadToReadOnlyDir(); void downloadPathValidation(); + void downloadToDirectoryWithFileName(); private: void saveLink(QPoint linkPos); @@ -447,6 +448,8 @@ void tst_QWebEngineDownloadItem::downloadLink() QByteArray slashFileName = QByteArrayLiteral("/") + fileName; QString suggestedPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + slashFileName; + QString downloadDirectory = tmpDir.path(); + QString downloadFileName = fileName; QString downloadPath = tmpDir.path() + slashFileName; QUrl downloadUrl = m_server->url(slashFileName); int acceptedCount = 0; @@ -460,7 +463,7 @@ void tst_QWebEngineDownloadItem::downloadLink() QCOMPARE(item->type(), expectedDownloadType(userAction, fileDisposition)); QCOMPARE(item->isSavePageDownload(), false); QCOMPARE(item->mimeType(), QString(fileMimeTypeDetected)); - QCOMPARE(item->path(), suggestedPath); + QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), suggestedPath); QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat); QCOMPARE(item->url(), downloadUrl); QCOMPARE(item->page(), m_page); @@ -474,14 +477,15 @@ void tst_QWebEngineDownloadItem::downloadLink() QCOMPARE(item->type(), expectedDownloadType(userAction, fileDisposition)); QCOMPARE(item->isSavePageDownload(), false); QCOMPARE(item->mimeType(), QString(fileMimeTypeDetected)); - QCOMPARE(item->path(), downloadPath); + QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), downloadPath); QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat); QCOMPARE(item->url(), downloadUrl); QCOMPARE(item->page(), m_page); finishedCount++; }); - item->setPath(downloadPath); + item->setDownloadDirectory(downloadDirectory); + item->setDownloadFileName(downloadFileName); item->accept(); acceptedCount++; @@ -575,7 +579,8 @@ void tst_QWebEngineDownloadItem::downloadTwoLinks() QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat); QCOMPARE(item->mimeType(), QStringLiteral("text/plain")); QString filePart = QChar('/') + item->url().fileName(); - QCOMPARE(item->path(), standardDir + filePart); + QString fileName = item->url().fileName(); + QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), standardDir + filePart); // type() is broken due to race condition in DownloadManagerDelegateQt if (action1 == ClickLink && action2 == ClickLink) { @@ -590,7 +595,8 @@ void tst_QWebEngineDownloadItem::downloadTwoLinks() connect(item, &QWebEngineDownloadItem::finished, [&]() { finishedCount++; }); - item->setPath(tmpDir.path() + filePart); + item->setDownloadDirectory(tmpDir.path()); + item->setDownloadFileName(fileName); item->accept(); acceptedCount++; @@ -655,7 +661,7 @@ void tst_QWebEngineDownloadItem::downloadPage() QCOMPARE(item->isSavePageDownload(), true); // FIXME(juvaldma): why is mimeType always the same? QCOMPARE(item->mimeType(), QStringLiteral("application/x-mimearchive")); - QCOMPARE(item->path(), downloadPath); + QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), downloadPath); QCOMPARE(item->savePageFormat(), savePageFormat); QCOMPARE(item->url(), downloadUrl); QCOMPARE(item->page(), m_page); @@ -670,7 +676,7 @@ void tst_QWebEngineDownloadItem::downloadPage() QCOMPARE(item->type(), QWebEngineDownloadItem::SavePage); QCOMPARE(item->isSavePageDownload(), true); QCOMPARE(item->mimeType(), QStringLiteral("application/x-mimearchive")); - QCOMPARE(item->path(), downloadPath); + QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), downloadPath); QCOMPARE(item->savePageFormat(), savePageFormat); QCOMPARE(item->url(), downloadUrl); QCOMPARE(item->page(), m_page); @@ -869,6 +875,7 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilename() QFETCH(QString, extension); QString fileName = QString("%1.%2").arg(baseName).arg(extension); QString downloadedFilePath; + QString suggestedFileName; bool downloadFinished = false; QTemporaryDir tmpDir; @@ -890,6 +897,7 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilename() // Set up profile and download handler ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { + suggestedFileName = item->suggestedFileName(); item->accept(); connect(item, &QWebEngineDownloadItem::finished, [&, item]() { QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); @@ -899,7 +907,7 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilename() QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(item->type(), QWebEngineDownloadItem::Attachment); QCOMPARE(item->isSavePageDownload(), false); - downloadedFilePath = item->path(); + downloadedFilePath = QDir(item->downloadDirectory()).filePath(item->downloadFileName()); downloadFinished = true; }); }); @@ -915,6 +923,7 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilename() QTRY_VERIFY(downloadFinished); QVERIFY(QFile(downloadedFilePath).exists()); QCOMPARE(downloadedFilePath, m_profile->downloadPath() + "/" + baseName + " (" + QString::number(i) + ")." + extension); + QCOMPARE(suggestedFileName, fileName); } } @@ -925,6 +934,7 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilenameWithTimestamp() QString extension("txt"); QString fileName = QString("%1.%2").arg(baseName).arg(extension); QString downloadedFilePath; + QString suggestedFileName; bool downloadFinished = false; QTemporaryDir tmpDir; @@ -945,6 +955,7 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilenameWithTimestamp() // Set up profile and download handler ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { + suggestedFileName = item->suggestedFileName(); item->accept(); connect(item, &QWebEngineDownloadItem::finished, [&, item]() { QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); @@ -954,7 +965,7 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilenameWithTimestamp() QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(item->page(), m_page); downloadFinished = true; - downloadedFilePath = item->path(); + downloadedFilePath = QDir(item->downloadDirectory()).filePath(item->downloadFileName()); }); }); @@ -975,6 +986,7 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilenameWithTimestamp() QTRY_VERIFY(downloadFinished); QVERIFY(QFile(downloadedFilePath).exists()); QCOMPARE(downloadedFilePath, m_profile->downloadPath() + "/" + baseName + " (100)." + extension); + QCOMPARE(suggestedFileName, fileName); // Check if the downloaded files are suffixed with timestamp after the 100th download. for (int i = 101; i < 103; i++) { @@ -988,6 +1000,7 @@ void tst_QWebEngineDownloadItem::downloadUniqueFilenameWithTimestamp() // ISO 8601 Date and time in UTC QRegExp timestamp("^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9])([0-5][0-9])([0-5][0-9])([.][0-9]+)?(Z|[+-](?:2[0-3]|[01][0-9])[0-5][0-9])?$"); QVERIFY(timestamp.exactMatch(match.captured(1))); + QCOMPARE(suggestedFileName, fileName); } } @@ -1014,6 +1027,7 @@ void tst_QWebEngineDownloadItem::downloadToNonExistentDir() QString extension("txt"); QString fileName = QString("%1.%2").arg(baseName).arg(extension); QString downloadedFilePath; + QString suggestedFileName; bool downloadFinished = false; QTemporaryDir tmpDir; @@ -1035,6 +1049,7 @@ void tst_QWebEngineDownloadItem::downloadToNonExistentDir() // Set up profile and download handler ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { + suggestedFileName = item->suggestedFileName(); item->accept(); connect(item, &QWebEngineDownloadItem::finished, [&, item]() { QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); @@ -1044,7 +1059,7 @@ void tst_QWebEngineDownloadItem::downloadToNonExistentDir() QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(item->page(), m_page); downloadFinished = true; - downloadedFilePath = item->path(); + downloadedFilePath = QDir(item->downloadDirectory()).filePath(item->downloadFileName()); }); }); @@ -1056,6 +1071,7 @@ void tst_QWebEngineDownloadItem::downloadToNonExistentDir() QTRY_VERIFY(downloadFinished); QVERIFY(QFile(downloadedFilePath).exists()); QCOMPARE(downloadedFilePath, nonExistentDownloadPath + "/" + fileName); + QCOMPARE(suggestedFileName, fileName); } void tst_QWebEngineDownloadItem::downloadToReadOnlyDir() @@ -1067,6 +1083,7 @@ void tst_QWebEngineDownloadItem::downloadToReadOnlyDir() QString extension("txt"); QString fileName = QString("%1.%2").arg(baseName).arg(extension); QString downloadedFilePath; + QString suggestedFileName; bool downloadAccepted = false; bool downloadFinished = false; @@ -1089,6 +1106,7 @@ void tst_QWebEngineDownloadItem::downloadToReadOnlyDir() QPointer<QWebEngineDownloadItem> downloadItem; ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { + suggestedFileName = item->suggestedFileName(); downloadItem = item; item->accept(); connect(item, &QWebEngineDownloadItem::finished, [&, item]() { @@ -1109,6 +1127,7 @@ void tst_QWebEngineDownloadItem::downloadToReadOnlyDir() QCOMPARE(downloadItem->isFinished(), false); QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::FileAccessDenied); QVERIFY(!QFile(downloadedFilePath).exists()); + QCOMPARE(suggestedFileName, fileName); // Clear m_requestedDownloads explicitly because download is accepted but never finished. m_requestedDownloads.clear(); @@ -1233,5 +1252,135 @@ void tst_QWebEngineDownloadItem::downloadPathValidation() QDir::setCurrent(oldPath); } +void tst_QWebEngineDownloadItem::downloadToDirectoryWithFileName() +{ + QString downloadDirectory; + QString downloadFileName; + QString downloadedFilePath; + QString downloadedSuggestedFileName; + QString fileName = "test.txt"; + QString uniqueFileName = "test (1).txt"; + + bool downloadFinished = false; + + QTemporaryDir tmpDir; + QVERIFY(tmpDir.isValid()); + m_profile->setDownloadPath(tmpDir.path()); + + // Set up HTTP server + ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { + if (rr->requestMethod() == "GET" && rr->requestPath() == ("/" + fileName)) { + rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("application/octet-stream")); + rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); + rr->setResponseBody(QByteArrayLiteral("a")); + rr->sendResponse(); + } else { + rr->setResponseStatus(404); + rr->sendResponse(); + } + }); + + // Set up profile and download handler + ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { + + if (!downloadDirectory.isEmpty()) { + item->setDownloadDirectory(downloadDirectory); + QCOMPARE(item->downloadDirectory(), downloadDirectory); + } + + if (!downloadFileName.isEmpty()) { + item->setDownloadFileName(downloadFileName); + QCOMPARE(item->downloadFileName(), downloadFileName); + } + + QCOMPARE(item->path(), QDir(item->downloadDirectory()).filePath(item->downloadFileName())); + item->accept(); + + connect(item, &QWebEngineDownloadItem::finished, [&, item]() { + QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); + QCOMPARE(item->isFinished(), true); + QCOMPARE(item->totalBytes(), item->receivedBytes()); + QVERIFY(item->receivedBytes() > 0); + QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); + QCOMPARE(item->page(), m_page); + downloadFinished = true; + downloadedFilePath = QDir(item->downloadDirectory()).filePath(item->downloadFileName()); + downloadedSuggestedFileName = item->suggestedFileName(); + }); + }); + + // Download file to the default download directory. + downloadDirectory = ""; + downloadFileName = ""; + m_page->setUrl(m_server->url("/" + fileName)); + QTRY_VERIFY(downloadFinished); + QVERIFY(QFile(downloadedFilePath).exists()); + QCOMPARE(downloadedFilePath, QDir(m_profile->downloadPath()).filePath(fileName)); + QCOMPARE(downloadedSuggestedFileName, fileName); + + // Download the same file to another directory + downloadFinished = false; + downloadDirectory = m_profile->downloadPath() + QDir::separator() + "test1" + QDir::separator(); + downloadFileName = ""; + m_page->setUrl(m_server->url("/" + fileName)); + QTRY_VERIFY(downloadFinished); + QVERIFY(QFile(downloadedFilePath).exists()); + QCOMPARE(downloadedFilePath, QDir(downloadDirectory).filePath(fileName)); + QCOMPARE(downloadedSuggestedFileName, fileName); + + // Download the same file to the same directory and the file name must be unique. + downloadFinished = false; + downloadDirectory = m_profile->downloadPath() + QDir::separator() + "test1" + QDir::separator(); + downloadFileName = ""; + m_page->setUrl(m_server->url("/" + fileName)); + QTRY_VERIFY(downloadFinished); + QVERIFY(QFile(downloadedFilePath).exists()); + QCOMPARE(downloadedFilePath, QDir(downloadDirectory).filePath(uniqueFileName)); + QCOMPARE(downloadedSuggestedFileName, fileName); + + // Download another file to the same directory and set file name by + // QWebEngineDownloadItem::setDownloadDirectory() and setDownloadFileName() to avoid uniquification. + downloadFinished = false; + downloadDirectory = m_profile->downloadPath() + QDir::separator() + "test1" + QDir::separator(); + downloadFileName = "test1.txt"; + m_page->setUrl(m_server->url("/" + fileName)); + QTRY_VERIFY(downloadFinished); + QVERIFY(QFile(downloadedFilePath).exists()); + QCOMPARE(downloadedFilePath, QDir(downloadDirectory).filePath(downloadFileName)); + QCOMPARE(downloadedSuggestedFileName, fileName); + + // Download the same file to another directory without uniquifying the file name + downloadFinished = false; + downloadDirectory = m_profile->downloadPath() + QDir::separator() + "test2" + QDir::separator(); + downloadFileName = "test1.txt"; + m_page->setUrl(m_server->url("/" + fileName)); + QTRY_VERIFY(downloadFinished); + QVERIFY(QFile(downloadedFilePath).exists()); + QCOMPARE(downloadedFilePath, QDir(downloadDirectory).filePath(downloadFileName)); + QCOMPARE(downloadedSuggestedFileName, fileName); + + // Download the same file to same directory and set file name by + // QWebEngineDownloadItem::setDownloadDirectory() and setDownloadFileName() to avoid uniquification. + downloadFinished = false; + downloadDirectory = m_profile->downloadPath() + QDir::separator() + "test2" + QDir::separator(); + downloadFileName = "test1.txt"; + m_page->setUrl(m_server->url("/" + fileName)); + QTRY_VERIFY(downloadFinished); + QVERIFY(QFile(downloadedFilePath).exists()); + QCOMPARE(downloadedFilePath, QDir(downloadDirectory).filePath(downloadFileName)); + QCOMPARE(downloadedSuggestedFileName, fileName); + + // Download the same file in the same directory. + // Use the suggested file name (test.txt) and the file name will not be unique because this file name don't yet exists. + downloadFinished = false; + downloadDirectory = m_profile->downloadPath() + QDir::separator() + "test2" + QDir::separator(); + downloadFileName = ""; + m_page->setUrl(m_server->url("/" + fileName)); + QTRY_VERIFY(downloadFinished); + QVERIFY(QFile(downloadedFilePath).exists()); + QCOMPARE(downloadedFilePath, QDir(downloadDirectory).filePath(fileName)); + QCOMPARE(downloadedSuggestedFileName, fileName); +} + QTEST_MAIN(tst_QWebEngineDownloadItem) #include "tst_qwebenginedownloaditem.moc" diff --git a/tests/auto/widgets/qwebenginepage/BLACKLIST b/tests/auto/widgets/qwebenginepage/BLACKLIST index af47c70f7..7857ee818 100644 --- a/tests/auto/widgets/qwebenginepage/BLACKLIST +++ b/tests/auto/widgets/qwebenginepage/BLACKLIST @@ -4,8 +4,5 @@ osx [mouseMovementProperties] windows -[getUserMediaRequestDesktopVideoManyPages] -windows - -[getUserMediaRequestDesktopVideoManyRequests] +[fullScreenRequested] windows diff --git a/tests/auto/widgets/qwebenginepage/resources/lifecycle.html b/tests/auto/widgets/qwebenginepage/resources/lifecycle.html new file mode 100644 index 000000000..aa477a359 --- /dev/null +++ b/tests/auto/widgets/qwebenginepage/resources/lifecycle.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> + <head> + <title>Lifecycle</title> + <script> + let frozenness = 0; + document.addEventListener("freeze", function() { + frozenness += 1; + }); + document.addEventListener("resume", function() { + frozenness -= 1; + }); + </script> + </head> + <body> + </body> +</html> diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp index 6373bf2d9..5edd778e2 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp @@ -47,6 +47,7 @@ #include <qnetworkreply.h> #include <qnetworkrequest.h> #include <qwebenginedownloaditem.h> +#include <qwebenginefindtextresult.h> #include <qwebenginefullscreenrequest.h> #include <qwebenginehistory.h> #include <qwebenginenotification.h> @@ -127,6 +128,8 @@ private Q_SLOTS: void findText(); void findTextResult(); void findTextSuccessiveShouldCallAllCallbacks(); + void findTextCalledOnMatch(); + void findTextActiveMatchOrdinal(); void deleteQWebEngineViewTwice(); void loadSignalsOrder_data(); void loadSignalsOrder(); @@ -202,9 +205,23 @@ private Q_SLOTS: void sendNotification(); void contentsSize(); + void setLifecycleState(); + void setVisible(); + void discardPreservesProperties(); + void discardBeforeInitialization(); + void automaticUndiscard(); + void setLifecycleStateWithDevTools(); + void discardPreservesCommittedLoad(); + void discardAbortsPendingLoad(); + void discardAbortsPendingLoadAndPreservesCommittedLoad(); + void recommendedState(); + void recommendedStateAuto(); + void setLifecycleStateAndReload(); + void editActionsWithExplicitFocus(); void editActionsWithInitialFocus(); void editActionsWithFocusOnIframe(); + void editActionsWithoutSelection(); void customUserAgentInNewTab(); @@ -290,9 +307,9 @@ protected: { Q_UNUSED(url); Q_UNUSED(isMainFrame); - if (type == QWebEnginePage::NavigationTypeFormSubmitted) - return m_acceptNavigationRequest; - return true; + if (type == QWebEnginePage::NavigationTypeTyped) + return true; + return m_acceptNavigationRequest; } }; @@ -579,7 +596,7 @@ void tst_QWebEnginePage::acceptNavigationRequestNavigationType() << QWebEnginePage::NavigationTypeBackForward << QWebEnginePage::NavigationTypeReload << QWebEnginePage::NavigationTypeTyped - << QWebEnginePage::NavigationTypeOther; + << QWebEnginePage::NavigationTypeRedirect; QVERIFY(expectedList.count() == page.navigations.count()); for (int i = 0; i < expectedList.count(); ++i) { QCOMPARE(page.navigations[i].type, expectedList[i]); @@ -927,18 +944,24 @@ void tst_QWebEnginePage::findText() // Invoking a stopFinding operation will not change or clear the currently selected text, // if nothing was found beforehand. { - CallbackSpy<bool> spy; - m_view->findText("", 0, spy.ref()); - QVERIFY(spy.wasCalled()); + CallbackSpy<bool> callbackSpy; + QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); + m_view->findText("", 0, callbackSpy.ref()); + QVERIFY(callbackSpy.wasCalled()); + QCOMPARE(signalSpy.count(), 1); QTRY_COMPARE(m_view->selectedText(), QString("foo bar")); } // Invoking a startFinding operation with text that won't be found, will clear the current // selection. { - CallbackSpy<bool> spy; - m_view->findText("Will not be found", 0, spy.ref()); - QCOMPARE(spy.waitForResult(), false); + CallbackSpy<bool> callbackSpy; + QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); + m_view->findText("Will not be found", 0, callbackSpy.ref()); + QCOMPARE(callbackSpy.waitForResult(), false); + QTRY_COMPARE(signalSpy.count(), 1); + auto result = signalSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); + QCOMPARE(result.numberOfMatches(), 0); QTRY_VERIFY(m_view->selectedText().isEmpty()); } @@ -949,24 +972,36 @@ void tst_QWebEnginePage::findText() // Invoking a startFinding operation with text that will be found, will clear the current // selection as well. { - CallbackSpy<bool> spy; - m_view->findText("foo", 0, spy.ref()); - QVERIFY(spy.waitForResult()); + CallbackSpy<bool> callbackSpy; + QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); + m_view->findText("foo", 0, callbackSpy.ref()); + QVERIFY(callbackSpy.waitForResult()); + QTRY_COMPARE(signalSpy.count(), 1); QTRY_VERIFY(m_view->selectedText().isEmpty()); } // Invoking a stopFinding operation after text was found, will set the selected text to the // found text. { - CallbackSpy<bool> spy; - m_view->findText("", 0, spy.ref()); - QTRY_VERIFY(spy.wasCalled()); + CallbackSpy<bool> callbackSpy; + QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); + m_view->findText("", 0, callbackSpy.ref()); + QTRY_VERIFY(callbackSpy.wasCalled()); + QTRY_COMPARE(signalSpy.count(), 1); QTRY_COMPARE(m_view->selectedText(), QString("foo")); } } void tst_QWebEnginePage::findTextResult() { + QSignalSpy findTextSpy(m_view->page(), &QWebEnginePage::findTextFinished); + auto signalResult = [&findTextSpy]() -> QVector<int> { + if (findTextSpy.count() != 1) + return QVector<int>({-1, -1}); + auto r = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); + return QVector<int>({ r.numberOfMatches(), r.activeMatchOrdinal() }); + }; + // findText will abort in blink if the view has an empty size. m_view->resize(800, 600); m_view->show(); @@ -976,15 +1011,21 @@ void tst_QWebEnginePage::findTextResult() QTRY_COMPARE(loadSpy.count(), 1); QCOMPARE(findTextSync(m_page, ""), false); + QCOMPARE(signalResult(), QVector<int>({0, 0})); const QStringList words = { "foo", "bar" }; for (const QString &subString : words) { QCOMPARE(findTextSync(m_page, subString), true); + QCOMPARE(signalResult(), QVector<int>({1, 1})); + QCOMPARE(findTextSync(m_page, ""), false); + QCOMPARE(signalResult(), QVector<int>({0, 0})); } QCOMPARE(findTextSync(m_page, "blahhh"), false); + QCOMPARE(signalResult(), QVector<int>({0, 0})); QCOMPARE(findTextSync(m_page, ""), false); + QCOMPARE(signalResult(), QVector<int>({0, 0})); } void tst_QWebEnginePage::findTextSuccessiveShouldCallAllCallbacks() @@ -1010,6 +1051,91 @@ void tst_QWebEnginePage::findTextSuccessiveShouldCallAllCallbacks() QVERIFY(spy5.wasCalled()); } +void tst_QWebEnginePage::findTextCalledOnMatch() +{ + QSignalSpy loadSpy(m_view->page(), &QWebEnginePage::loadFinished); + + // findText will abort in blink if the view has an empty size. + m_view->resize(800, 600); + m_view->show(); + m_view->setHtml(QString("<html><head></head><body><div>foo bar</div></body></html>")); + QTRY_COMPARE(loadSpy.count(), 1); + + // CALLBACK + bool callbackCalled = false; + m_view->page()->findText("foo", 0, [this, &callbackCalled](bool found) { + QVERIFY(found); + + m_view->page()->findText("bar", 0, [&callbackCalled](bool found) { + QVERIFY(found); + callbackCalled = true; + }); + }); + QTRY_VERIFY(callbackCalled); + + // SIGNAL + int findTextFinishedCount = 0; + connect(m_view->page(), &QWebEnginePage::findTextFinished, [this, &findTextFinishedCount](QWebEngineFindTextResult result) { + QCOMPARE(result.numberOfMatches(), 1); + if (findTextFinishedCount == 0) + m_view->page()->findText("bar"); + findTextFinishedCount++; + }); + + m_view->page()->findText("foo"); + QTRY_COMPARE(findTextFinishedCount, 2); +} + +void tst_QWebEnginePage::findTextActiveMatchOrdinal() +{ + QSignalSpy loadSpy(m_view->page(), &QWebEnginePage::loadFinished); + QSignalSpy findTextSpy(m_view->page(), &QWebEnginePage::findTextFinished); + QWebEngineFindTextResult result; + + // findText will abort in blink if the view has an empty size. + m_view->resize(800, 600); + m_view->show(); + m_view->setHtml(QString("<html><head></head><body><div>foo bar foo bar foo</div></body></html>")); + QTRY_COMPARE(loadSpy.count(), 1); + + // Iterate over all "foo" matches. + for (int i = 1; i <= 3; ++i) { + m_view->page()->findText("foo", 0); + QTRY_COMPARE(findTextSpy.count(), 1); + result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); + QCOMPARE(result.numberOfMatches(), 3); + QCOMPARE(result.activeMatchOrdinal(), i); + } + + // The last match is followed by the fist one. + m_view->page()->findText("foo", 0); + QTRY_COMPARE(findTextSpy.count(), 1); + result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); + QCOMPARE(result.numberOfMatches(), 3); + QCOMPARE(result.activeMatchOrdinal(), 1); + + // The first match is preceded by the last one. + m_view->page()->findText("foo", QWebEnginePage::FindBackward); + QTRY_COMPARE(findTextSpy.count(), 1); + result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); + QCOMPARE(result.numberOfMatches(), 3); + QCOMPARE(result.activeMatchOrdinal(), 3); + + // Finding another word resets the activeMatchOrdinal. + m_view->page()->findText("bar", 0); + QTRY_COMPARE(findTextSpy.count(), 1); + result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); + QCOMPARE(result.numberOfMatches(), 2); + QCOMPARE(result.activeMatchOrdinal(), 1); + + // If no match activeMatchOrdinal is 0. + m_view->page()->findText("bla", 0); + QTRY_COMPARE(findTextSpy.count(), 1); + result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); + QCOMPARE(result.numberOfMatches(), 0); + QCOMPARE(result.activeMatchOrdinal(), 0); +} + static QWindow *findNewTopLevelWindow(const QWindowList &oldTopLevelWindows) { const auto tlws = QGuiApplication::topLevelWindows(); @@ -3406,6 +3532,645 @@ void tst_QWebEnginePage::contentsSize() QCOMPARE(m_page->contentsSize().height(), 1216); } +void tst_QWebEnginePage::setLifecycleState() +{ + qRegisterMetaType<QWebEnginePage::LifecycleState>("LifecycleState"); + + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished); + QSignalSpy lifecycleSpy(&page, &QWebEnginePage::lifecycleStateChanged); + QSignalSpy visibleSpy(&page, &QWebEnginePage::visibleChanged); + + page.load(QStringLiteral("qrc:/resources/lifecycle.html")); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); + QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QCOMPARE(visibleSpy.count(), 0); + QCOMPARE(page.isVisible(), false); + QCOMPARE(evaluateJavaScriptSync(&page, "document.wasDiscarded"), QVariant(false)); + QCOMPARE(evaluateJavaScriptSync(&page, "frozenness"), QVariant(0)); + + // Active -> Frozen + page.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); + QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Frozen); + QCOMPARE(visibleSpy.count(), 0); + QCOMPARE(page.isVisible(), false); + QCOMPARE(evaluateJavaScriptSync(&page, "document.wasDiscarded"), QVariant(false)); + QCOMPARE(evaluateJavaScriptSync(&page, "frozenness"), QVariant(1)); + + // Frozen -> Active + page.setLifecycleState(QWebEnginePage::LifecycleState::Active); + QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QCOMPARE(visibleSpy.count(), 0); + QCOMPARE(page.isVisible(), false); + QCOMPARE(evaluateJavaScriptSync(&page, "document.wasDiscarded"), QVariant(false)); + QCOMPARE(evaluateJavaScriptSync(&page, "frozenness"), QVariant(0)); + + // Active -> Discarded + page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Discarded); + QCOMPARE(visibleSpy.count(), 0); + QCOMPARE(page.isVisible(), false); + QTest::ignoreMessage(QtWarningMsg, "runJavaScript: disabled in Discarded state"); + QCOMPARE(evaluateJavaScriptSync(&page, "document.wasDiscarded"), QVariant()); + QTest::ignoreMessage(QtWarningMsg, "runJavaScript: disabled in Discarded state"); + QCOMPARE(evaluateJavaScriptSync(&page, "frozenness"), QVariant()); + QCOMPARE(loadSpy.count(), 0); + + // Discarded -> Frozen (illegal!) + QTest::ignoreMessage(QtWarningMsg, + "setLifecycleState: failed to transition from Discarded to Frozen state: " + "illegal transition"); + page.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); + QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Discarded); + + // Discarded -> Active + page.setLifecycleState(QWebEnginePage::LifecycleState::Active); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); + QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QCOMPARE(visibleSpy.count(), 0); + QCOMPARE(page.isVisible(), false); + QCOMPARE(evaluateJavaScriptSync(&page, "document.wasDiscarded"), QVariant(true)); + QCOMPARE(evaluateJavaScriptSync(&page, "frozenness"), QVariant(0)); + + // Active -> Frozen -> Discarded -> Active + page.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); + page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + page.setLifecycleState(QWebEnginePage::LifecycleState::Active); + QCOMPARE(lifecycleSpy.count(), 3); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QCOMPARE(visibleSpy.count(), 0); + QCOMPARE(page.isVisible(), false); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); + QCOMPARE(evaluateJavaScriptSync(&page, "document.wasDiscarded"), QVariant(true)); + QCOMPARE(evaluateJavaScriptSync(&page, "frozenness"), QVariant(0)); + + // Reload clears document.wasDiscarded + page.triggerAction(QWebEnginePage::Reload); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); + QCOMPARE(evaluateJavaScriptSync(&page, "document.wasDiscarded"), QVariant(false)); +} + +void tst_QWebEnginePage::setVisible() +{ + qRegisterMetaType<QWebEnginePage::LifecycleState>("LifecycleState"); + + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished); + QSignalSpy lifecycleSpy(&page, &QWebEnginePage::lifecycleStateChanged); + QSignalSpy visibleSpy(&page, &QWebEnginePage::visibleChanged); + + page.load(QStringLiteral("about:blank")); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); + QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QCOMPARE(visibleSpy.count(), 0); + QCOMPARE(page.isVisible(), false); + + // hidden -> visible + page.setVisible(true); + QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QCOMPARE(visibleSpy.count(), 1); + QCOMPARE(visibleSpy.takeFirst().value(0), QVariant(true)); + QCOMPARE(page.isVisible(), true); + + // Active -> Frozen (illegal) + QTest::ignoreMessage( + QtWarningMsg, + "setLifecycleState: failed to transition from Active to Frozen state: page is visible"); + page.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); + QCOMPARE(lifecycleSpy.count(), 0); + + // visible -> hidden + page.setVisible(false); + QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QCOMPARE(visibleSpy.count(), 1); + QCOMPARE(visibleSpy.takeFirst().value(0), QVariant(false)); + QCOMPARE(page.isVisible(), false); + + // Active -> Frozen + page.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); + QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Frozen); + + // hidden -> visible (triggers Frozen -> Active) + page.setVisible(true); + QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QCOMPARE(visibleSpy.count(), 1); + QCOMPARE(visibleSpy.takeFirst().value(0), QVariant(true)); + QCOMPARE(page.isVisible(), true); + + // Active -> Discarded (illegal) + QTest::ignoreMessage(QtWarningMsg, + "setLifecycleState: failed to transition from Active to Discarded state: " + "page is visible"); + page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + QCOMPARE(lifecycleSpy.count(), 0); + + // visible -> hidden + page.setVisible(false); + QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QCOMPARE(visibleSpy.count(), 1); + QCOMPARE(visibleSpy.takeFirst().value(0), QVariant(false)); + QCOMPARE(page.isVisible(), false); + + // Active -> Discarded + page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Discarded); + + // hidden -> visible (triggers Discarded -> Active) + page.setVisible(true); + QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QCOMPARE(visibleSpy.count(), 1); + QCOMPARE(visibleSpy.takeFirst().value(0), QVariant(true)); + QCOMPARE(page.isVisible(), true); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); +} + +void tst_QWebEnginePage::discardPreservesProperties() +{ + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished); + + page.load(QStringLiteral("about:blank")); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); + + // Change as many properties as possible to non-default values + bool audioMuted = true; + QVERIFY(page.isAudioMuted() != audioMuted); + page.setAudioMuted(audioMuted); + QColor backgroundColor = Qt::black; + QVERIFY(page.backgroundColor() != backgroundColor); + page.setBackgroundColor(backgroundColor); + qreal zoomFactor = 2; + QVERIFY(page.zoomFactor() != zoomFactor); + page.setZoomFactor(zoomFactor); +#if QT_CONFIG(webengine_webchannel) + QWebChannel *webChannel = new QWebChannel(&page); + page.setWebChannel(webChannel); +#endif + + // Take snapshot of the rest + QSizeF contentsSize = page.contentsSize(); + QIcon icon = page.icon(); + QUrl iconUrl = page.iconUrl(); + QUrl requestedUrl = page.requestedUrl(); + QString title = page.title(); + QUrl url = page.url(); + + // History should be preserved too + int historyCount = page.history()->count(); + QCOMPARE(historyCount, 1); + int historyIndex = page.history()->currentItemIndex(); + QCOMPARE(historyIndex, 0); + QWebEngineHistoryItem historyItem = page.history()->currentItem(); + QVERIFY(historyItem.isValid()); + + // Discard + undiscard + page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + page.setLifecycleState(QWebEnginePage::LifecycleState::Active); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); + + // Property changes should be preserved + QCOMPARE(page.isAudioMuted(), audioMuted); + QCOMPARE(page.backgroundColor(), backgroundColor); + QCOMPARE(page.contentsSize(), contentsSize); + QCOMPARE(page.icon(), icon); + QCOMPARE(page.iconUrl(), iconUrl); + QCOMPARE(page.requestedUrl(), requestedUrl); + QCOMPARE(page.title(), title); + QCOMPARE(page.url(), url); + QCOMPARE(page.zoomFactor(), zoomFactor); +#if QT_CONFIG(webengine_webchannel) + QCOMPARE(page.webChannel(), webChannel); +#endif + QCOMPARE(page.history()->count(), historyCount); + QCOMPARE(page.history()->currentItemIndex(), historyIndex); + QCOMPARE(page.history()->currentItem().url(), historyItem.url()); + QCOMPARE(page.history()->currentItem().originalUrl(), historyItem.originalUrl()); + QCOMPARE(page.history()->currentItem().title(), historyItem.title()); +} + +void tst_QWebEnginePage::discardBeforeInitialization() +{ + QWebEngineProfile profile; + QWebEnginePage page(&profile); + page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + // The call is ignored + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); +} + +void tst_QWebEnginePage::automaticUndiscard() +{ + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished); + + page.load(QStringLiteral("about:blank")); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); + + // setUrl + page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + page.setUrl(QStringLiteral("qrc:/resources/lifecycle.html")); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + + // setContent + page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + page.setContent(QByteArrayLiteral("foo")); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); +} + +void tst_QWebEnginePage::setLifecycleStateWithDevTools() +{ + QWebEngineProfile profile; + QWebEnginePage inspectedPage(&profile); + QWebEnginePage devToolsPage(&profile); + QSignalSpy devToolsSpy(&devToolsPage, &QWebEnginePage::loadFinished); + QSignalSpy inspectedSpy(&inspectedPage, &QWebEnginePage::loadFinished); + + // Ensure pages are initialized + inspectedPage.load(QStringLiteral("about:blank")); + devToolsPage.load(QStringLiteral("about:blank")); + QTRY_COMPARE(inspectedSpy.count(), 1); + QCOMPARE(inspectedSpy.takeFirst().value(0), QVariant(true)); + QTRY_COMPARE(devToolsSpy.count(), 1); + QCOMPARE(devToolsSpy.takeFirst().value(0), QVariant(true)); + + // Open DevTools with Frozen inspectedPage + inspectedPage.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); + inspectedPage.setDevToolsPage(&devToolsPage); + QCOMPARE(inspectedPage.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QTRY_COMPARE(devToolsSpy.count(), 1); + QCOMPARE(devToolsSpy.takeFirst().value(0), QVariant(true)); + inspectedPage.setDevToolsPage(nullptr); + + // Open DevTools with Discarded inspectedPage + inspectedPage.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + inspectedPage.setDevToolsPage(&devToolsPage); + QCOMPARE(inspectedPage.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QTRY_COMPARE(devToolsSpy.count(), 1); + QCOMPARE(devToolsSpy.takeFirst().value(0), QVariant(true)); + QTRY_COMPARE(inspectedSpy.count(), 1); + QCOMPARE(inspectedSpy.takeFirst().value(0), QVariant(true)); + inspectedPage.setDevToolsPage(nullptr); + + // Open DevTools with Frozen devToolsPage + devToolsPage.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); + devToolsPage.setInspectedPage(&inspectedPage); + QCOMPARE(devToolsPage.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QTRY_COMPARE(devToolsSpy.count(), 1); + QCOMPARE(devToolsSpy.takeFirst().value(0), QVariant(true)); + devToolsPage.setInspectedPage(nullptr); + + // Open DevTools with Discarded devToolsPage + devToolsPage.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + devToolsPage.setInspectedPage(&inspectedPage); + QCOMPARE(devToolsPage.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QTRY_COMPARE(devToolsSpy.count(), 2); + QCOMPARE(devToolsSpy.takeFirst().value(0), QVariant(false)); + QCOMPARE(devToolsSpy.takeFirst().value(0), QVariant(true)); + // keep DevTools open + + // Try to change state while DevTools are open + QTest::ignoreMessage( + QtWarningMsg, + "setLifecycleState: failed to transition from Active to Frozen state: DevTools open"); + inspectedPage.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); + QCOMPARE(inspectedPage.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QTest::ignoreMessage(QtWarningMsg, + "setLifecycleState: failed to transition from Active to Discarded state: " + "DevTools open"); + inspectedPage.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + QCOMPARE(inspectedPage.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QTest::ignoreMessage( + QtWarningMsg, + "setLifecycleState: failed to transition from Active to Frozen state: DevTools open"); + devToolsPage.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); + QCOMPARE(devToolsPage.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QTest::ignoreMessage(QtWarningMsg, + "setLifecycleState: failed to transition from Active to Discarded state: " + "DevTools open"); + devToolsPage.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + QCOMPARE(devToolsPage.lifecycleState(), QWebEnginePage::LifecycleState::Active); +} + +void tst_QWebEnginePage::discardPreservesCommittedLoad() +{ + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QSignalSpy loadStartedSpy(&page, &QWebEnginePage::loadStarted); + QSignalSpy loadFinishedSpy(&page, &QWebEnginePage::loadFinished); + QSignalSpy urlChangedSpy(&page, &QWebEnginePage::urlChanged); + QSignalSpy titleChangedSpy(&page, &QWebEnginePage::titleChanged); + + QString url = QStringLiteral("qrc:/resources/lifecycle.html"); + page.setUrl(url); + QTRY_COMPARE(loadStartedSpy.count(), 1); + loadStartedSpy.clear(); + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QCOMPARE(loadFinishedSpy.takeFirst().value(0), QVariant(true)); + QCOMPARE(urlChangedSpy.count(), 1); + QCOMPARE(urlChangedSpy.takeFirst().value(0), QVariant(QUrl(url))); + QCOMPARE(page.url(), url); + QCOMPARE(titleChangedSpy.count(), 2); + QCOMPARE(titleChangedSpy.takeFirst().value(0), QVariant(url)); + QString title = QStringLiteral("Lifecycle"); + QCOMPARE(titleChangedSpy.takeFirst().value(0), QVariant(title)); + QCOMPARE(page.title(), title); + + page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + QCOMPARE(loadStartedSpy.count(), 0); + QCOMPARE(loadFinishedSpy.count(), 0); + QCOMPARE(urlChangedSpy.count(), 0); + QCOMPARE(page.url(), QUrl(url)); + QCOMPARE(titleChangedSpy.count(), 0); + QCOMPARE(page.title(), title); + + page.setLifecycleState(QWebEnginePage::LifecycleState::Active); + QTRY_COMPARE(loadStartedSpy.count(), 1); + loadStartedSpy.clear(); + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QCOMPARE(loadFinishedSpy.takeFirst().value(0), QVariant(true)); + QCOMPARE(urlChangedSpy.count(), 0); + QCOMPARE(page.url(), url); + QCOMPARE(titleChangedSpy.count(), 0); + QCOMPARE(page.title(), title); +} + +void tst_QWebEnginePage::discardAbortsPendingLoad() +{ + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QSignalSpy loadStartedSpy(&page, &QWebEnginePage::loadStarted); + QSignalSpy loadFinishedSpy(&page, &QWebEnginePage::loadFinished); + QSignalSpy urlChangedSpy(&page, &QWebEnginePage::urlChanged); + QSignalSpy titleChangedSpy(&page, &QWebEnginePage::titleChanged); + + connect(&page, &QWebEnginePage::loadStarted, + [&]() { page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); }); + QUrl url = QStringLiteral("qrc:/resources/lifecycle.html"); + page.setUrl(url); + QTRY_COMPARE(loadStartedSpy.count(), 1); + loadStartedSpy.clear(); + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QCOMPARE(loadFinishedSpy.takeFirst().value(0), QVariant(false)); + QCOMPARE(urlChangedSpy.count(), 2); + QCOMPARE(urlChangedSpy.takeFirst().value(0), QVariant(url)); + QCOMPARE(urlChangedSpy.takeFirst().value(0), QVariant(QUrl())); + QCOMPARE(titleChangedSpy.count(), 0); + QCOMPARE(page.url(), QUrl()); + QCOMPARE(page.title(), QString()); + + page.setLifecycleState(QWebEnginePage::LifecycleState::Active); + QCOMPARE(loadStartedSpy.count(), 0); + QCOMPARE(loadFinishedSpy.count(), 0); + QCOMPARE(urlChangedSpy.count(), 0); + QCOMPARE(page.url(), QUrl()); + QCOMPARE(page.title(), QString()); +} + +void tst_QWebEnginePage::discardAbortsPendingLoadAndPreservesCommittedLoad() +{ + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QSignalSpy loadStartedSpy(&page, &QWebEnginePage::loadStarted); + QSignalSpy loadFinishedSpy(&page, &QWebEnginePage::loadFinished); + QSignalSpy urlChangedSpy(&page, &QWebEnginePage::urlChanged); + QSignalSpy titleChangedSpy(&page, &QWebEnginePage::titleChanged); + + QString url1 = QStringLiteral("qrc:/resources/lifecycle.html"); + page.setUrl(url1); + QTRY_COMPARE(loadStartedSpy.count(), 1); + loadStartedSpy.clear(); + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QCOMPARE(loadFinishedSpy.takeFirst().value(0), QVariant(true)); + QCOMPARE(urlChangedSpy.count(), 1); + QCOMPARE(urlChangedSpy.takeFirst().value(0), QVariant(QUrl(url1))); + QCOMPARE(page.url(), url1); + QCOMPARE(titleChangedSpy.count(), 2); + QCOMPARE(titleChangedSpy.takeFirst().value(0), QVariant(url1)); + QString title = QStringLiteral("Lifecycle"); + QCOMPARE(titleChangedSpy.takeFirst().value(0), QVariant(title)); + QCOMPARE(page.title(), title); + + connect(&page, &QWebEnginePage::loadStarted, + [&]() { page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); }); + QString url2 = QStringLiteral("about:blank"); + page.setUrl(url2); + QTRY_COMPARE(loadStartedSpy.count(), 1); + loadStartedSpy.clear(); + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QCOMPARE(loadFinishedSpy.takeFirst().value(0), QVariant(false)); + QCOMPARE(urlChangedSpy.count(), 2); + QCOMPARE(urlChangedSpy.takeFirst().value(0), QVariant(QUrl(url2))); + QCOMPARE(urlChangedSpy.takeFirst().value(0), QVariant(QUrl(url1))); + QCOMPARE(titleChangedSpy.count(), 0); + QCOMPARE(page.url(), url1); + QCOMPARE(page.title(), title); + + page.setLifecycleState(QWebEnginePage::LifecycleState::Active); + QCOMPARE(loadStartedSpy.count(), 0); + QCOMPARE(loadFinishedSpy.count(), 0); + QCOMPARE(urlChangedSpy.count(), 0); + QCOMPARE(page.url(), url1); + QCOMPARE(page.title(), title); +} + +void tst_QWebEnginePage::recommendedState() +{ + qRegisterMetaType<QWebEnginePage::LifecycleState>("LifecycleState"); + + QWebEngineProfile profile; + QWebEnginePage page(&profile); + + struct Event { + enum { StateChange, RecommendationChange } key; + QWebEnginePage::LifecycleState value; + }; + std::vector<Event> events; + connect(&page, &QWebEnginePage::lifecycleStateChanged, [&](QWebEnginePage::LifecycleState state) { + events.push_back(Event { Event::StateChange, state }); + }); + connect(&page, &QWebEnginePage::recommendedStateChanged, [&](QWebEnginePage::LifecycleState state) { + events.push_back(Event { Event::RecommendationChange, state }); + }); + + page.load(QStringLiteral("qrc:/resources/lifecycle.html")); + QTRY_COMPARE(events.size(), 1u); + QCOMPARE(events[0].key, Event::RecommendationChange); + QCOMPARE(events[0].value, QWebEnginePage::LifecycleState::Frozen); + events.clear(); + QCOMPARE(page.recommendedState(), QWebEnginePage::LifecycleState::Frozen); + + page.setVisible(true); + QTRY_COMPARE(events.size(), 1u); + QCOMPARE(events[0].key, Event::RecommendationChange); + QCOMPARE(events[0].value, QWebEnginePage::LifecycleState::Active); + events.clear(); + QCOMPARE(page.recommendedState(), QWebEnginePage::LifecycleState::Active); + + page.setVisible(false); + QTRY_COMPARE(events.size(), 1u); + QCOMPARE(events[0].key, Event::RecommendationChange); + QCOMPARE(events[0].value, QWebEnginePage::LifecycleState::Frozen); + events.clear(); + QCOMPARE(page.recommendedState(), QWebEnginePage::LifecycleState::Frozen); + + page.triggerAction(QWebEnginePage::Reload); + QTRY_COMPARE(events.size(), 2u); + QCOMPARE(events[0].key, Event::RecommendationChange); + QCOMPARE(events[0].value, QWebEnginePage::LifecycleState::Active); + QCOMPARE(events[1].key, Event::RecommendationChange); + QCOMPARE(events[1].value, QWebEnginePage::LifecycleState::Frozen); + events.clear(); + QCOMPARE(page.recommendedState(), QWebEnginePage::LifecycleState::Frozen); + + QWebEnginePage devTools; + page.setDevToolsPage(&devTools); + QTRY_COMPARE(events.size(), 1u); + QCOMPARE(events[0].key, Event::RecommendationChange); + QCOMPARE(events[0].value, QWebEnginePage::LifecycleState::Active); + events.clear(); + QCOMPARE(page.recommendedState(), QWebEnginePage::LifecycleState::Active); + + page.setDevToolsPage(nullptr); + QTRY_COMPARE(events.size(), 1u); + QCOMPARE(events[0].key, Event::RecommendationChange); + QCOMPARE(events[0].value, QWebEnginePage::LifecycleState::Frozen); + events.clear(); + QCOMPARE(page.recommendedState(), QWebEnginePage::LifecycleState::Frozen); + + page.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); + QTRY_COMPARE(events.size(), 2u); + QCOMPARE(events[0].key, Event::StateChange); + QCOMPARE(events[0].value, QWebEnginePage::LifecycleState::Frozen); + QCOMPARE(events[1].key, Event::RecommendationChange); + QCOMPARE(events[1].value, QWebEnginePage::LifecycleState::Discarded); + events.clear(); + QCOMPARE(page.recommendedState(), QWebEnginePage::LifecycleState::Discarded); + + page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + QTRY_COMPARE(events.size(), 1u); + QCOMPARE(events[0].key, Event::StateChange); + QCOMPARE(events[0].value, QWebEnginePage::LifecycleState::Discarded); + events.clear(); + QCOMPARE(page.recommendedState(), QWebEnginePage::LifecycleState::Discarded); +} + +void tst_QWebEnginePage::recommendedStateAuto() +{ + qRegisterMetaType<QWebEnginePage::LifecycleState>("LifecycleState"); + + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QSignalSpy lifecycleSpy(&page, &QWebEnginePage::lifecycleStateChanged); + connect(&page, &QWebEnginePage::recommendedStateChanged, &page, &QWebEnginePage::setLifecycleState); + + page.load(QStringLiteral("qrc:/resources/lifecycle.html")); + QTRY_COMPARE(lifecycleSpy.count(), 2); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); + + page.setVisible(true); + QTRY_COMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); + + page.setVisible(false); + QTRY_COMPARE(lifecycleSpy.count(), 2); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); + + page.triggerAction(QWebEnginePage::Reload); + QTRY_COMPARE(lifecycleSpy.count(), 3); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); + + QWebEnginePage devTools; + page.setDevToolsPage(&devTools); + QTRY_COMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); + + page.setDevToolsPage(nullptr); + QTRY_COMPARE(lifecycleSpy.count(), 2); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); +} + +void tst_QWebEnginePage::setLifecycleStateAndReload() +{ + qRegisterMetaType<QWebEnginePage::LifecycleState>("LifecycleState"); + + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished); + QSignalSpy lifecycleSpy(&page, &QWebEnginePage::lifecycleStateChanged); + + page.load(QStringLiteral("qrc:/resources/lifecycle.html")); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); + QCOMPARE(lifecycleSpy.count(), 0); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + + page.setLifecycleState(QWebEnginePage::LifecycleState::Frozen); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Frozen); + QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Frozen)); + + page.triggerAction(QWebEnginePage::Reload); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); + + page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Discarded); + QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Discarded)); + + page.triggerAction(QWebEnginePage::Reload); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + QCOMPARE(lifecycleSpy.count(), 1); + QCOMPARE(lifecycleSpy.takeFirst().value(0), QVariant::fromValue(QWebEnginePage::LifecycleState::Active)); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(loadSpy.takeFirst().value(0), QVariant(true)); +} + void tst_QWebEnginePage::editActionsWithExplicitFocus() { QWebEngineView view; @@ -3493,6 +4258,44 @@ void tst_QWebEnginePage::editActionsWithFocusOnIframe() QCOMPARE(page->selectedText(), QStringLiteral("inner")); } +void tst_QWebEnginePage::editActionsWithoutSelection() +{ + QWebEngineView view; + QWebEnginePage *page = view.page(); + view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + + QSignalSpy loadFinishedSpy(page, &QWebEnginePage::loadFinished); + QSignalSpy selectionChangedSpy(page, &QWebEnginePage::selectionChanged); + QSignalSpy actionChangedSpy(page->action(QWebEnginePage::SelectAll), &QAction::changed); + + page->setHtml(QString("<html><body><div>foo bar</div></body></html>")); + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(actionChangedSpy.count(), 1); + + QVERIFY(!page->action(QWebEnginePage::Cut)->isEnabled()); + QVERIFY(!page->action(QWebEnginePage::Copy)->isEnabled()); + QVERIFY(page->action(QWebEnginePage::Paste)->isEnabled()); + QVERIFY(page->action(QWebEnginePage::Undo)->isEnabled()); + QVERIFY(page->action(QWebEnginePage::Redo)->isEnabled()); + QVERIFY(page->action(QWebEnginePage::SelectAll)->isEnabled()); + QVERIFY(page->action(QWebEnginePage::PasteAndMatchStyle)->isEnabled()); + QVERIFY(!page->action(QWebEnginePage::Unselect)->isEnabled()); + + page->triggerAction(QWebEnginePage::SelectAll); + QTRY_COMPARE(selectionChangedSpy.count(), 1); + QCOMPARE(page->hasSelection(), true); + QCOMPARE(page->selectedText(), QStringLiteral("foo bar")); + + QVERIFY(page->action(QWebEnginePage::Cut)->isEnabled()); + QVERIFY(page->action(QWebEnginePage::Copy)->isEnabled()); + QVERIFY(page->action(QWebEnginePage::Paste)->isEnabled()); + QVERIFY(page->action(QWebEnginePage::Undo)->isEnabled()); + QVERIFY(page->action(QWebEnginePage::Redo)->isEnabled()); + QVERIFY(page->action(QWebEnginePage::SelectAll)->isEnabled()); + QVERIFY(page->action(QWebEnginePage::PasteAndMatchStyle)->isEnabled()); + QVERIFY(page->action(QWebEnginePage::Unselect)->isEnabled()); +} + void tst_QWebEnginePage::customUserAgentInNewTab() { HttpServer server; diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc index cf32486e7..013a307de 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc @@ -23,6 +23,7 @@ <file>resources/foo.txt</file> <file>resources/bar.txt</file> <file>resources/path with spaces.txt</file> + <file>resources/lifecycle.html</file> </qresource> <qresource prefix='/shared'> <file alias='notification.html'>../../shared/data/notification.html</file> diff --git a/tests/auto/widgets/qwebengineprofile/BLACKLIST b/tests/auto/widgets/qwebengineprofile/BLACKLIST index fc1c957dd..55806eec4 100644 --- a/tests/auto/widgets/qwebengineprofile/BLACKLIST +++ b/tests/auto/widgets/qwebengineprofile/BLACKLIST @@ -1,5 +1,3 @@ -[clearDataFromCache] -* [disableCache] * diff --git a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp index 8b75067ee..25afa5849 100644 --- a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp +++ b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp @@ -40,6 +40,8 @@ #include <QtWebEngineWidgets/qwebengineview.h> #include <QtWebEngineWidgets/qwebenginedownloaditem.h> +#include <mutex> + class tst_QWebEngineProfile : public QObject { Q_OBJECT @@ -255,18 +257,18 @@ public: bool isSequential() const override { return true; } qint64 bytesAvailable() const override { - QMutexLocker lock(&m_mutex); + const std::lock_guard<QRecursiveMutex> lock(m_mutex); return m_bytesAvailable; } bool atEnd() const override { - QMutexLocker lock(&m_mutex); + const std::lock_guard<QRecursiveMutex> lock(m_mutex); return (m_data.size() >= 1000 && m_bytesRead >= 1000); } protected: void timerEvent(QTimerEvent *) override { - QMutexLocker lock(&m_mutex); + const std::lock_guard<QRecursiveMutex> lock(m_mutex); m_bytesAvailable += 200; m_data.append(200, 'c'); emit readyRead(); @@ -278,7 +280,7 @@ protected: qint64 readData(char *data, qint64 maxlen) override { - QMutexLocker lock(&m_mutex); + const std::lock_guard<QRecursiveMutex> lock(m_mutex); qint64 len = qMin(qint64(m_bytesAvailable), maxlen); if (len) { memcpy(data, m_data.constData() + m_bytesRead, len); @@ -295,7 +297,12 @@ protected: } private: +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) mutable QMutex m_mutex{QMutex::Recursive}; + using QRecursiveMutex = QMutex; +#else + mutable QRecursiveMutex m_mutex; +#endif QByteArray m_data; QBasicTimer m_timer; int m_bytesRead; @@ -519,7 +526,7 @@ void tst_QWebEngineProfile::urlSchemeHandlerRequestHeaders() QWebEngineProfile profile; profile.installUrlSchemeHandler("myscheme", &handler); - profile.setRequestInterceptor(&interceptor); + profile.setUrlRequestInterceptor(&interceptor); QWebEnginePage page(&profile); QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); diff --git a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp index 9a2ee9311..90361f2c9 100644 --- a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp +++ b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp @@ -30,6 +30,26 @@ #include <QWebChannel> #endif +static bool verifyOrder(QStringList orderList) +{ + QStringList expected = { + "DocumentCreation", + "DOMContentLoaded", + "DocumentReady", + "load", + "Deferred" + }; + + if (orderList.at(4) == "load (timeout)") { + if (orderList.at(3) != "Deferred") + return false; + expected[3] = "Deferred"; + expected[4] = "load (timeout)"; + } + + return orderList == expected; +} + class tst_QWebEngineScript: public QObject { Q_OBJECT @@ -101,15 +121,28 @@ void tst_QWebEngineScript::loadEvents() script.setRunsOnSubFrames(true); if (injectionPoint == QWebEngineScript::DocumentCreation) { script.setSourceCode(QStringLiteral(R"( - var log = ["DocumentCreation"]; - for (let type of ["DOMContentLoaded", "load"]) { - window.addEventListener(type, () => log.push(type)); + var log = ['DocumentCreation']; + var timestamps = {'DocumentCreation': Date.now()}; + for (let type of ['DOMContentLoaded', 'load']) { + window.addEventListener(type, () => { + timestamps[type] = Date.now(); + if (type === 'load' && log.includes('Deferred') && timestamps['Deferred'] - timestamps['DOMContentLoaded'] > 500) + log.push(type + ' (timeout)'); + else + log.push(type); + }); } )")); } else if (injectionPoint == QWebEngineScript::DocumentReady) { - script.setSourceCode(QStringLiteral(R"(log.push("DocumentReady"))")); + script.setSourceCode(QStringLiteral(R"( + timestamps['DocumentReady'] = Date.now(); + log.push('DocumentReady'); + )")); } else { - script.setSourceCode(QStringLiteral(R"(log.push("Deferred"))")); + script.setSourceCode(QStringLiteral(R"( + timestamps['Deferred'] = Date.now(); + log.push('Deferred'); + )")); } return script; } @@ -144,38 +177,38 @@ void tst_QWebEngineScript::loadEvents() profile.pages.emplace_back(profile); Page &page = profile.pages.back(); - const QStringList expected = { - "DocumentCreation", - "DOMContentLoaded", - "DocumentReady", - "load", - "Deferred" - }; - // Single frame / setHtml page.setHtml(QStringLiteral("<!DOCTYPE html><html><head><title>mr</title></head><body></body></html>")); QTRY_COMPARE(page.spy.count(), 1); QCOMPARE(page.spy.takeFirst().value(0).toBool(), true); - QCOMPARE(page.eval("window.log", QWebEngineScript::MainWorld).toStringList(), expected); - QCOMPARE(page.eval("window.log", QWebEngineScript::ApplicationWorld).toStringList(), expected); + QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::MainWorld).toStringList())); + QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::ApplicationWorld).toStringList())); + + // After discard + page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + page.setLifecycleState(QWebEnginePage::LifecycleState::Active); + QTRY_COMPARE(page.spy.count(), 1); + QCOMPARE(page.spy.takeFirst().value(0).toBool(), true); + QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::MainWorld).toStringList())); + QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::ApplicationWorld).toStringList())); // Multiple frames page.load(QUrl("qrc:/resources/test_iframe_main.html")); QTRY_COMPARE(page.spy.count(), 1); QCOMPARE(page.spy.takeFirst().value(0).toBool(), true); - QCOMPARE(page.eval("window.log", QWebEngineScript::MainWorld).toStringList(), expected); - QCOMPARE(page.eval("window.log", QWebEngineScript::ApplicationWorld).toStringList(), expected); - QCOMPARE(page.eval("window[0].log", QWebEngineScript::MainWorld).toStringList(), expected); - QCOMPARE(page.eval("window[0].log", QWebEngineScript::ApplicationWorld).toStringList(), expected); - QCOMPARE(page.eval("window[0][0].log", QWebEngineScript::MainWorld).toStringList(), expected); - QCOMPARE(page.eval("window[0][0].log", QWebEngineScript::ApplicationWorld).toStringList(), expected); + QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::MainWorld).toStringList())); + QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::ApplicationWorld).toStringList())); + QVERIFY(verifyOrder(page.eval("window[0].log", QWebEngineScript::MainWorld).toStringList())); + QVERIFY(verifyOrder(page.eval("window[0].log", QWebEngineScript::ApplicationWorld).toStringList())); + QVERIFY(verifyOrder(page.eval("window[0][0].log", QWebEngineScript::MainWorld).toStringList())); + QVERIFY(verifyOrder(page.eval("window[0][0].log", QWebEngineScript::ApplicationWorld).toStringList())); // Cross-process navigation page.load(QUrl("chrome://gpu")); - QTRY_COMPARE(page.spy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(page.spy.count(), 1, 20000); QCOMPARE(page.spy.takeFirst().value(0).toBool(), true); - QCOMPARE(page.eval("window.log", QWebEngineScript::MainWorld).toStringList(), expected); - QCOMPARE(page.eval("window.log", QWebEngineScript::ApplicationWorld).toStringList(), expected); + QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::MainWorld).toStringList())); + QVERIFY(verifyOrder(page.eval("window.log", QWebEngineScript::ApplicationWorld).toStringList())); // Using window.open from JS QVERIFY(profile.pages.size() == 1); @@ -183,10 +216,10 @@ void tst_QWebEngineScript::loadEvents() QTRY_VERIFY(profile.pages.size() == 2); QTRY_COMPARE(profile.pages.front().spy.count(), 1); QTRY_COMPARE(profile.pages.back().spy.count(), 1); - QCOMPARE(profile.pages.front().eval("window.log", QWebEngineScript::MainWorld).toStringList(), expected); - QCOMPARE(profile.pages.front().eval("window.log", QWebEngineScript::ApplicationWorld).toStringList(), expected); - QCOMPARE(profile.pages.back().eval("window.log", QWebEngineScript::MainWorld).toStringList(), expected); - QCOMPARE(profile.pages.back().eval("window.log", QWebEngineScript::ApplicationWorld).toStringList(), expected); + QVERIFY(verifyOrder(profile.pages.front().eval("window.log", QWebEngineScript::MainWorld).toStringList())); + QVERIFY(verifyOrder(profile.pages.front().eval("window.log", QWebEngineScript::ApplicationWorld).toStringList())); + QVERIFY(verifyOrder(profile.pages.back().eval("window.log", QWebEngineScript::MainWorld).toStringList())); + QVERIFY(verifyOrder(profile.pages.back().eval("window.log", QWebEngineScript::ApplicationWorld).toStringList())); } void tst_QWebEngineScript::scriptWorld_data() @@ -531,6 +564,11 @@ void tst_QWebEngineScript::navigation() page.setUrl(url3); QTRY_COMPARE(spyTextChanged.count(), 3); QCOMPARE(testObject.text(), url3); + + page.setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + page.setUrl(url1); + QTRY_COMPARE(spyTextChanged.count(), 4); + QCOMPARE(testObject.text(), url1); } // Try to set TestObject::text to an invalid UTF-16 string. diff --git a/tests/auto/widgets/qwebenginesettings/tst_qwebenginesettings.cpp b/tests/auto/widgets/qwebenginesettings/tst_qwebenginesettings.cpp index 0704cf383..b4061b984 100644 --- a/tests/auto/widgets/qwebenginesettings/tst_qwebenginesettings.cpp +++ b/tests/auto/widgets/qwebenginesettings/tst_qwebenginesettings.cpp @@ -177,7 +177,7 @@ void tst_QWebEngineSettings::setInAcceptNavigationRequest() { NavigationRequestOverride page; QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); - QWebEngineSettings::globalSettings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false); + QWebEngineSettings::defaultSettings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false); QVERIFY(!page.settings()->testAttribute(QWebEngineSettings::JavascriptEnabled)); page.load(QUrl("about:blank")); diff --git a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp index 1bb65d7df..71c4aa9b5 100644 --- a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp +++ b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp @@ -203,6 +203,7 @@ private Q_SLOTS: void setViewDeletesImplicitPage(); void setPagePreservesExplicitPage(); void setViewPreservesExplicitPage(); + void closeDiscardsPage(); }; // This will be called before the first test function is executed. @@ -238,31 +239,46 @@ void tst_QWebEngineView::renderHints() QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); +#if QT_DEPRECATED_SINCE(5, 14) QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); +#endif + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); webView.setRenderHint(QPainter::Antialiasing, true); QVERIFY(webView.renderHints() & QPainter::Antialiasing); QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); +#if QT_DEPRECATED_SINCE(5, 14) QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); +#endif + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); webView.setRenderHint(QPainter::Antialiasing, false); QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); +#if QT_DEPRECATED_SINCE(5, 14) QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); +#endif + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); webView.setRenderHint(QPainter::SmoothPixmapTransform, true); QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); +#if QT_DEPRECATED_SINCE(5, 14) QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); +#endif + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); webView.setRenderHint(QPainter::SmoothPixmapTransform, false); QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); QVERIFY(!(webView.renderHints() & QPainter::SmoothPixmapTransform)); +#if QT_DEPRECATED_SINCE(5, 14) QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); #endif + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); +#endif } void tst_QWebEngineView::getWebKitVersion() @@ -481,14 +497,14 @@ void tst_QWebEngineView::microFocusCoordinates() evaluateJavaScriptSync(webView.page(), "document.getElementById('input1').focus()"); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); - QTRY_VERIFY(webView.focusProxy()->inputMethodQuery(Qt::ImMicroFocus).isValid()); - QVariant initialMicroFocus = webView.focusProxy()->inputMethodQuery(Qt::ImMicroFocus); + QTRY_VERIFY(webView.focusProxy()->inputMethodQuery(Qt::ImCursorRectangle).isValid()); + QVariant initialMicroFocus = webView.focusProxy()->inputMethodQuery(Qt::ImCursorRectangle); evaluateJavaScriptSync(webView.page(), "window.scrollBy(0, 50)"); QTRY_VERIFY(scrollSpy.count() > 0); - QTRY_VERIFY(webView.focusProxy()->inputMethodQuery(Qt::ImMicroFocus).isValid()); - QVariant currentMicroFocus = webView.focusProxy()->inputMethodQuery(Qt::ImMicroFocus); + QTRY_VERIFY(webView.focusProxy()->inputMethodQuery(Qt::ImCursorRectangle).isValid()); + QVariant currentMicroFocus = webView.focusProxy()->inputMethodQuery(Qt::ImCursorRectangle); QCOMPARE(initialMicroFocus.toRect().translated(QPoint(0,-50)), currentMicroFocus.toRect()); } @@ -1830,11 +1846,14 @@ void tst_QWebEngineView::softwareInputPanel() void tst_QWebEngineView::inputContextQueryInput() { - TestInputContext testContext; QWebEngineView view; view.resize(640, 480); view.show(); + // testContext will be destroyed before the view, so no events are sent accidentally + // when the view is destroyed. + TestInputContext testContext; + QSignalSpy selectionChangedSpy(&view, SIGNAL(selectionChanged())); QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); view.setHtml("<html><body>" @@ -2196,6 +2215,22 @@ void tst_QWebEngineView::textSelectionOutOfInputField() QVERIFY(!view.hasSelection()); QVERIFY(view.page()->selectedText().isEmpty()); + // Select text by ctrl+a + QTest::keyClick(view.windowHandle(), Qt::Key_A, Qt::ControlModifier); + QVERIFY(selectionChangedSpy.wait()); + QCOMPARE(selectionChangedSpy.count(), 3); + QVERIFY(view.hasSelection()); + QCOMPARE(view.page()->selectedText(), QString("This is a text")); + + // Deselect text via discard+undiscard + view.hide(); + view.page()->setLifecycleState(QWebEnginePage::LifecycleState::Discarded); + view.show(); + QVERIFY(loadFinishedSpy.wait()); + QCOMPARE(selectionChangedSpy.count(), 4); + QVERIFY(!view.hasSelection()); + QVERIFY(view.page()->selectedText().isEmpty()); + selectionChangedSpy.clear(); view.setHtml("<html><body>" " This is a text" @@ -2971,7 +3006,7 @@ void tst_QWebEngineView::mouseLeave() QVBoxLayout *layout = new QVBoxLayout; layout->setAlignment(Qt::AlignTop); layout->setSpacing(0); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(label); layout->addWidget(view); containerWidget->setLayout(layout); @@ -3070,7 +3105,6 @@ void tst_QWebEngineView::webUIURLs_data() QTest::newRow("supervised-user-internals") << QUrl("chrome://supervised-user-internals") << false; QTest::newRow("sync-internals") << QUrl("chrome://sync-internals") << false; QTest::newRow("system") << QUrl("chrome://system") << false; - QTest::newRow("taskscheduler-internals") << QUrl("chrome://taskscheduler-internals") << true; QTest::newRow("terms") << QUrl("chrome://terms") << false; QTest::newRow("thumbnails") << QUrl("chrome://thumbnails") << false; QTest::newRow("tracing") << QUrl("chrome://tracing") << false; @@ -3292,5 +3326,21 @@ void tst_QWebEngineView::setViewPreservesExplicitPage() QVERIFY(explicitPage1); // should not be deleted } +void tst_QWebEngineView::closeDiscardsPage() +{ + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QWebEngineView view; + view.setPage(&page); + view.resize(300, 300); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + QCOMPARE(page.isVisible(), true); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Active); + view.close(); + QCOMPARE(page.isVisible(), false); + QCOMPARE(page.lifecycleState(), QWebEnginePage::LifecycleState::Discarded); +} + QTEST_MAIN(tst_QWebEngineView) #include "tst_qwebengineview.moc" diff --git a/tests/auto/widgets/spellchecking/tst_spellchecking.cpp b/tests/auto/widgets/spellchecking/tst_spellchecking.cpp index d02fc78b9..64df05d89 100644 --- a/tests/auto/widgets/spellchecking/tst_spellchecking.cpp +++ b/tests/auto/widgets/spellchecking/tst_spellchecking.cpp @@ -72,10 +72,7 @@ class tst_Spellchecking : public QObject private Q_SLOTS: void init(); void cleanup(); - void initTestCase(); - void spellCheckLanguage(); - void spellCheckLanguages(); - void spellCheckEnabled(); + void settings(); void spellcheck(); void spellcheck_data(); @@ -84,19 +81,8 @@ private: WebView *m_view; }; -void tst_Spellchecking::initTestCase() -{ - QWebEngineProfile *profile = QWebEngineProfile::defaultProfile(); - QVERIFY(profile); - QVERIFY(!profile->isSpellCheckEnabled()); - QVERIFY(profile->spellCheckLanguages().isEmpty()); -} - void tst_Spellchecking::init() { - QWebEngineProfile *profile = QWebEngineProfile::defaultProfile(); - profile->setSpellCheckEnabled(false); - profile->setSpellCheckLanguages(QStringList()); m_view = new WebView(); } @@ -106,7 +92,6 @@ void tst_Spellchecking::load() m_view->show(); QSignalSpy spyFinished(m_view->page(), &QWebEnginePage::loadFinished); QVERIFY(spyFinished.wait()); - } void tst_Spellchecking::cleanup() @@ -114,29 +99,57 @@ void tst_Spellchecking::cleanup() delete m_view; } -void tst_Spellchecking::spellCheckLanguage() +void tst_Spellchecking::settings() { - QWebEngineProfile *profile = QWebEngineProfile::defaultProfile(); - QVERIFY(profile); - profile->setSpellCheckLanguages({"en-US"}); - QVERIFY(profile->spellCheckLanguages() == QStringList({"en-US"})); -} + // Default profile has spellchecking disabled -void tst_Spellchecking::spellCheckLanguages() -{ - QWebEngineProfile *profile = QWebEngineProfile::defaultProfile(); - QVERIFY(profile); - profile->setSpellCheckLanguages({"en-US","de-DE"}); - QVERIFY(profile->spellCheckLanguages() == QStringList({"en-US","de-DE"})); -} + QVERIFY(!QWebEngineProfile::defaultProfile()->isSpellCheckEnabled()); + QVERIFY(QWebEngineProfile::defaultProfile()->spellCheckLanguages().isEmpty()); + // New named profiles have spellchecking disabled -void tst_Spellchecking::spellCheckEnabled() -{ - QWebEngineProfile *profile = QWebEngineProfile::defaultProfile(); - QVERIFY(profile); - profile->setSpellCheckEnabled(true); - QVERIFY(profile->isSpellCheckEnabled()); + auto profile1 = std::make_unique<QWebEngineProfile>(QStringLiteral("Profile1")); + QVERIFY(!profile1->isSpellCheckEnabled()); + QVERIFY(profile1->spellCheckLanguages().isEmpty()); + + auto profile2 = std::make_unique<QWebEngineProfile>(QStringLiteral("Profile2")); + QVERIFY(!profile2->isSpellCheckEnabled()); + QVERIFY(profile2->spellCheckLanguages().isEmpty()); + + // New otr profiles have spellchecking disabled + + auto profile3 = std::make_unique<QWebEngineProfile>(); + QVERIFY(!profile2->isSpellCheckEnabled()); + QVERIFY(profile2->spellCheckLanguages().isEmpty()); + + // Settings can be changed + + profile1->setSpellCheckEnabled(true); + QVERIFY(profile1->isSpellCheckEnabled()); + + profile1->setSpellCheckLanguages({"en-US"}); + QVERIFY(profile1->spellCheckLanguages() == QStringList({"en-US"})); + + profile1->setSpellCheckLanguages({"en-US","de-DE"}); + QVERIFY(profile1->spellCheckLanguages() == QStringList({"en-US","de-DE"})); + + // Settings are per profile + + QVERIFY(!profile2->isSpellCheckEnabled()); + QVERIFY(profile2->spellCheckLanguages().isEmpty()); + + QVERIFY(!profile3->isSpellCheckEnabled()); + QVERIFY(profile3->spellCheckLanguages().isEmpty()); + + // Settings are not persisted + + // TODO(juvaldma): Write from dtor currently usually happens *after* the + // read from the ctor, so this test would pass even if settings were + // persisted. It would start to fail on the second run though. + profile1.reset(); + profile1 = std::make_unique<QWebEngineProfile>(QStringLiteral("Profile1")); + QVERIFY(!profile1->isSpellCheckEnabled()); + QVERIFY(profile1->spellCheckLanguages().isEmpty()); } void tst_Spellchecking::spellcheck() diff --git a/tests/auto/widgets/widgets.pro b/tests/auto/widgets/widgets.pro index 92159bf83..df553df55 100644 --- a/tests/auto/widgets/widgets.pro +++ b/tests/auto/widgets/widgets.pro @@ -4,6 +4,7 @@ QT_FOR_CONFIG += webenginecore webenginecore-private TEMPLATE = subdirs SUBDIRS += \ + certificateerror \ defaultsurfaceformat \ devtools \ faviconmanager \ |