/* Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) Copyright (C) 2009 Girish Ramakrishnan Copyright (C) 2010 Holger Hans Peter Freyther This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "../util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void removeRecursive(const QString& dirname) { QDir dir(dirname); QFileInfoList entries(dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)); for (int i = 0; i < entries.count(); ++i) if (entries[i].isDir()) removeRecursive(entries[i].filePath()); else dir.remove(entries[i].fileName()); QDir().rmdir(dirname); } class TestInputContext : public QPlatformInputContext { public: TestInputContext() : m_visible(false) { QInputMethodPrivate* inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod()); inputMethodPrivate->testContext = this; } ~TestInputContext() { QInputMethodPrivate* inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod()); inputMethodPrivate->testContext = 0; } virtual void showInputPanel() { m_visible = true; } virtual void hideInputPanel() { m_visible = false; } virtual bool isInputPanelVisible() const { return m_visible; } bool m_visible; }; class tst_QWebEnginePage : public QObject { Q_OBJECT public: tst_QWebEnginePage(); virtual ~tst_QWebEnginePage(); bool eventFilter(QObject *watched, QEvent *event); public Q_SLOTS: void init(); void cleanup(); void cleanupFiles(); private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void thirdPartyCookiePolicy(); void contextMenuCopy(); void contextMenuPopulatedOnce(); void acceptNavigationRequest(); void acceptNavigationRequestNavigationType(); void geolocationRequestJS_data(); void geolocationRequestJS(); void loadFinished(); void actionStates(); void popupFormSubmission(); void userStyleSheet(); void userStyleSheetFromLocalFileUrl(); void userStyleSheetFromQrcUrl(); void loadHtml5Video(); void modified(); void contextMenuCrash(); void updatePositionDependentActionsCrash(); void createPluginWithPluginsEnabled(); void createPluginWithPluginsDisabled(); void destroyPlugin_data(); void destroyPlugin(); void createViewlessPlugin_data(); void createViewlessPlugin(); void graphicsWidgetPlugin(); void multiplePageGroupsAndLocalStorage(); void cursorMovements(); void textSelection(); void textEditing(); void backActionUpdate(); void protectBindingsRuntimeObjectsFromCollector(); void localURLSchemes(); void testOptionalJSObjects(); void testLocalStorageVisibility(); void testEnablePersistentStorage(); void consoleOutput(); void inputMethods_data(); void inputMethods(); void inputMethodsTextFormat_data(); void inputMethodsTextFormat(); void defaultTextEncoding(); void errorPageExtension(); void errorPageExtensionLoadFinished(); void userAgentNewlineStripping(); void undoActionHaveCustomText(); void renderWidgetHostViewNotShowTopLevel(); void getUserMediaRequest(); void crashTests_LazyInitializationOfMainFrame(); void screenshot_data(); void screenshot(); #if defined(ENABLE_WEBGL) && ENABLE_WEBGL void acceleratedWebGLScreenshotWithoutView(); void unacceleratedWebGLScreenshotWithoutView(); #endif void testJSPrompt(); void testStopScheduledPageRefresh(); void findText(); void findTextResult(); void findTextSuccessiveShouldCallAllCallbacks(); void supportedContentType(); // [Qt] tst_QWebEnginePage::infiniteLoopJS() timeouts with DFG JIT // https://bugs.webkit.org/show_bug.cgi?id=79040 // void infiniteLoopJS(); void navigatorCookieEnabled(); void deleteQWebEngineViewTwice(); void renderOnRepaintRequestedShouldNotRecurse(); void loadSignalsOrder_data(); void loadSignalsOrder(); void openWindowDefaultSize(); void cssMediaTypeGlobalSetting(); void cssMediaTypePageSetting(); #ifdef Q_OS_MAC void macCopyUnicodeToClipboard(); #endif void runJavaScript(); void fullScreenRequested(); // Tests from tst_QWebEngineFrame void horizontalScrollAfterBack(); void symmetricUrl(); void progressSignal(); void urlChange(); void requestedUrlAfterSetAndLoadFailures(); void javaScriptWindowObjectCleared_data(); void javaScriptWindowObjectCleared(); void javaScriptWindowObjectClearedOnEvaluate(); void asyncAndDelete(); void earlyToHtml(); void setHtml(); void setHtmlWithImageResource(); void setHtmlWithStylesheetResource(); void setHtmlWithBaseURL(); void setHtmlWithJSAlert(); void inputFieldFocus(); void hitTestContent(); void baseUrl_data(); void baseUrl(); void renderHints(); void scrollPosition(); void scrollToAnchor(); void scrollbarsOff(); void evaluateWillCauseRepaint(); void setContent_data(); void setContent(); void setCacheLoadControlAttribute(); void setUrlWithPendingLoads(); void setUrlToEmpty(); void setUrlToInvalid(); void setUrlHistory(); void setUrlUsingStateObject(); void setUrlThenLoads_data(); void setUrlThenLoads(); void loadFinishedAfterNotFoundError(); void loadInSignalHandlers_data(); void loadInSignalHandlers(); void restoreHistory(); void toPlainTextLoadFinishedRace_data(); void toPlainTextLoadFinishedRace(); void setZoomFactor(); private: QWebEngineView* m_view; QWebEnginePage* m_page; QWebEngineView* m_inputFieldsTestView; int m_inputFieldTestPaintCount; QString tmpDirPath() const { static QString tmpd = QDir::tempPath() + "/tst_qwebenginepage-" + QDateTime::currentDateTime().toString(QLatin1String("yyyyMMddhhmmss")); return tmpd; } }; tst_QWebEnginePage::tst_QWebEnginePage() { } tst_QWebEnginePage::~tst_QWebEnginePage() { } bool tst_QWebEnginePage::eventFilter(QObject* watched, QEvent* event) { // used on the inputFieldFocus test if (watched == m_inputFieldsTestView) { if (event->type() == QEvent::Paint) m_inputFieldTestPaintCount++; } return QObject::eventFilter(watched, event); } void tst_QWebEnginePage::init() { m_view = new QWebEngineView(); m_page = m_view->page(); m_page->settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); } void tst_QWebEnginePage::cleanup() { delete m_view; } void tst_QWebEnginePage::cleanupFiles() { removeRecursive(tmpDirPath()); } void tst_QWebEnginePage::initTestCase() { cleanupFiles(); // In case there are old files from previous runs // Set custom path since the CI doesn't install test plugins. // Stolen from qtlocation/tests/auto/positionplugintest. QString searchPath = QCoreApplication::applicationDirPath(); #ifdef Q_OS_WIN searchPath += QStringLiteral("/.."); #endif searchPath += QStringLiteral("/../../../plugins"); QCoreApplication::addLibraryPath(searchPath); } void tst_QWebEnginePage::cleanupTestCase() { cleanupFiles(); // Be nice } class NavigationRequestOverride : public QWebEnginePage { public: NavigationRequestOverride(QWebEngineView* parent, bool initialValue) : QWebEnginePage(parent), m_acceptNavigationRequest(initialValue) {} bool m_acceptNavigationRequest; protected: virtual bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) { Q_UNUSED(url); Q_UNUSED(type); Q_UNUSED(isMainFrame); return m_acceptNavigationRequest; } }; void tst_QWebEnginePage::acceptNavigationRequest() { QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool))); NavigationRequestOverride* newPage = new NavigationRequestOverride(m_view, false); m_view->setPage(newPage); m_view->setHtml(QString("
" "
"), QUrl()); QTRY_COMPARE(loadSpy.count(), 1); evaluateJavaScriptSync(m_view->page(), "tstform.submit();"); newPage->m_acceptNavigationRequest = true; evaluateJavaScriptSync(m_view->page(), "tstform.submit();"); QTRY_COMPARE(loadSpy.count(), 2); QCOMPARE(toPlainTextSync(m_view->page()), QString("foo?")); // Restore default page m_view->setPage(0); } class JSTestPage : public QWebEnginePage { Q_OBJECT public: JSTestPage(QObject* parent = 0) : QWebEnginePage(parent) {} virtual bool shouldInterruptJavaScript() { return true; } public Q_SLOTS: void requestPermission(const QUrl &origin, QWebEnginePage::Feature feature) { if (m_allowGeolocation) setFeaturePermission(origin, feature, PermissionGrantedByUser); else setFeaturePermission(origin, feature, PermissionDeniedByUser); } public: void setGeolocationPermission(bool allow) { m_allowGeolocation = allow; } private: bool m_allowGeolocation; }; // [Qt] tst_QWebEnginePage::infiniteLoopJS() timeouts with DFG JIT // https://bugs.webkit.org/show_bug.cgi?id=79040 /* void tst_QWebEnginePage::infiniteLoopJS() { JSTestPage* newPage = new JSTestPage(m_view); m_view->setPage(newPage); m_view->setHtml(QString("test"), QUrl()); m_view->page()->evaluateJavaScript("var run = true; var a = 1; while (run) { a++; }"); delete newPage; } */ void tst_QWebEnginePage::geolocationRequestJS_data() { QTest::addColumn("allowed"); QTest::addColumn("errorCode"); QTest::newRow("allowed") << true << 0; QTest::newRow("not allowed") << false << 1; } void tst_QWebEnginePage::geolocationRequestJS() { QFETCH(bool, allowed); QFETCH(int, errorCode); QWebEngineView *view = new QWebEngineView; JSTestPage *newPage = new JSTestPage(view); newPage->setView(view); newPage->setGeolocationPermission(allowed); connect(newPage, SIGNAL(featurePermissionRequested(const QUrl&, QWebEnginePage::Feature)), newPage, SLOT(requestPermission(const QUrl&, QWebEnginePage::Feature))); QSignalSpy spyLoadFinished(newPage, SIGNAL(loadFinished(bool))); newPage->setHtml(QString("test"), QUrl()); QTRY_COMPARE(spyLoadFinished.count(), 1); if (evaluateJavaScriptSync(newPage, QLatin1String("!navigator.geolocation")).toBool()) { delete view; W_QSKIP("Geolocation is not supported.", SkipSingle); } evaluateJavaScriptSync(newPage, "var errorCode = 0; function error(err) { errorCode = err.code; } function success(pos) { } navigator.geolocation.getCurrentPosition(success, error)"); QTRY_COMPARE(evaluateJavaScriptSync(newPage, "errorCode").toInt(), errorCode); delete view; } void tst_QWebEnginePage::loadFinished() { QWebEnginePage page; QSignalSpy spyLoadStarted(&page, SIGNAL(loadStarted())); QSignalSpy spyLoadFinished(&page, SIGNAL(loadFinished(bool))); page.load(QUrl("data:text/html,foo \">" "")); QTRY_COMPARE(spyLoadFinished.count(), 1); QEXPECT_FAIL("", "Behavior change: Load signals are emitted only for the main frame in QtWebEngine.", Continue); QTRY_VERIFY_WITH_TIMEOUT(spyLoadStarted.count() > 1, 100); QEXPECT_FAIL("", "Behavior change: Load signals are emitted only for the main frame in QtWebEngine.", Continue); QTRY_VERIFY_WITH_TIMEOUT(spyLoadFinished.count() > 1, 100); spyLoadFinished.clear(); page.load(QUrl("data:text/html,")); QTRY_COMPARE(spyLoadFinished.count(), 1); QCOMPARE(spyLoadFinished.count(), 1); } void tst_QWebEnginePage::actionStates() { QWebEnginePage* page = m_view->page(); page->load(QUrl("qrc:///resources/script.html")); QAction* reloadAction = page->action(QWebEnginePage::Reload); QAction* stopAction = page->action(QWebEnginePage::Stop); QTRY_VERIFY(reloadAction->isEnabled()); QTRY_VERIFY(!stopAction->isEnabled()); } class ConsolePage : public QWebEnginePage { public: ConsolePage(QObject* parent = 0) : QWebEnginePage(parent) {} virtual void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString& message, int lineNumber, const QString& sourceID) { levels.append(level); messages.append(message); lineNumbers.append(lineNumber); sourceIDs.append(sourceID); } QList levels; QStringList messages; QList lineNumbers; QStringList sourceIDs; }; void tst_QWebEnginePage::consoleOutput() { ConsolePage page; // We don't care about the result but want this to be synchronous evaluateJavaScriptSync(&page, "this is not valid JavaScript"); QCOMPARE(page.messages.count(), 1); QCOMPARE(page.lineNumbers.at(0), 1); } class TestPage : public QWebEnginePage { Q_OBJECT public: TestPage(QObject* parent = 0) : QWebEnginePage(parent) { connect(this, SIGNAL(geometryChangeRequested(QRect)), this, SLOT(slotGeometryChangeRequested(QRect))); } struct Navigation { NavigationType type; QUrl url; bool isMainFrame; }; QList navigations; virtual bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) { Navigation n; n.url = url; n.type = type; n.isMainFrame = isMainFrame; navigations.append(n); return true; } QList createdWindows; virtual QWebEnginePage* createWindow(WebWindowType) { TestPage* page = new TestPage(this); createdWindows.append(page); return page; } QRect requestedGeometry; private Q_SLOTS: void slotGeometryChangeRequested(const QRect& geom) { requestedGeometry = geom; } }; void tst_QWebEnginePage::acceptNavigationRequestNavigationType() { TestPage page; QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); page.load(QUrl("qrc:///resources/script.html")); QTRY_COMPARE(loadSpy.count(), 1); QTRY_COMPARE(page.navigations.count(), 1); page.load(QUrl("qrc:///resources/content.html")); QTRY_COMPARE(loadSpy.count(), 2); QTRY_COMPARE(page.navigations.count(), 2); page.triggerAction(QWebEnginePage::Stop); QVERIFY(page.history()->canGoBack()); page.triggerAction(QWebEnginePage::Back); QTRY_COMPARE(loadSpy.count(), 3); QTRY_COMPARE(page.navigations.count(), 3); page.triggerAction(QWebEnginePage::Reload); QTRY_COMPARE(loadSpy.count(), 4); QTRY_COMPARE(page.navigations.count(), 4); QList expectedList; expectedList << QWebEnginePage::NavigationTypeTyped << QWebEnginePage::NavigationTypeTyped << QWebEnginePage::NavigationTypeBackForward << QWebEnginePage::NavigationTypeReload; QVERIFY(expectedList.count() == page.navigations.count()); for (int i = 0; i < expectedList.count(); ++i) { QCOMPARE(page.navigations[i].type, expectedList[i]); } } void tst_QWebEnginePage::popupFormSubmission() { TestPage page; page.settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true); page.setHtml("
"\ ""\ "
"); page.runJavaScript("window.open('', 'myNewWin', 'width=500,height=300,toolbar=0')"); evaluateJavaScriptSync(&page, "document.form1.submit();"); QTest::qWait(500); // The number of popup created should be one. QVERIFY(page.createdWindows.size() == 1); QString url = page.createdWindows.takeFirst()->url().toString(); // Check if the form submission was OK. QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118597", Continue); QVERIFY(url.contains("?foo=bar")); } class TestNetworkManager : public QNetworkAccessManager { public: TestNetworkManager(QObject* parent) : QNetworkAccessManager(parent) {} QList requestedUrls; QList requests; protected: virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest &request, QIODevice* outgoingData) { requests.append(request); requestedUrls.append(request.url()); return QNetworkAccessManager::createRequest(op, request, outgoingData); } }; void tst_QWebEnginePage::userStyleSheet() { #if !defined(QWEBENGINEPAGE_SETNETWORKACCESSMANAGER) QSKIP("QWEBENGINEPAGE_SETNETWORKACCESSMANAGER"); #else TestNetworkManager* networkManager = new TestNetworkManager(m_page); m_page->setNetworkAccessManager(networkManager); m_page->settings()->setUserStyleSheetUrl(QUrl("data:text/css;charset=utf-8;base64," + QByteArray("p { background-image: url('http://does.not/exist.png');}").toBase64())); m_view->setHtml("

hello world

"); QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); QVERIFY(networkManager->requestedUrls.count() >= 1); QCOMPARE(networkManager->requestedUrls.at(0), QUrl("http://does.not/exist.png")); #endif } void tst_QWebEnginePage::userStyleSheetFromLocalFileUrl() { #if !defined(QWEBENGINEPAGE_SETNETWORKACCESSMANAGER) QSKIP("QWEBENGINEPAGE_SETNETWORKACCESSMANAGER"); #else TestNetworkManager* networkManager = new TestNetworkManager(m_page); m_page->setNetworkAccessManager(networkManager); QUrl styleSheetUrl = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebenginepage/resources/user.css")); m_page->settings()->setUserStyleSheetUrl(styleSheetUrl); m_view->setHtml("

hello world

"); QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); QVERIFY(networkManager->requestedUrls.count() >= 1); QCOMPARE(networkManager->requestedUrls.at(0), QUrl("http://does.not/exist.png")); #endif } void tst_QWebEnginePage::userStyleSheetFromQrcUrl() { #if !defined(QWEBENGINEPAGE_SETNETWORKACCESSMANAGER) QSKIP("QWEBENGINEPAGE_SETNETWORKACCESSMANAGER"); #else TestNetworkManager* networkManager = new TestNetworkManager(m_page); m_page->setNetworkAccessManager(networkManager); m_page->settings()->setUserStyleSheetUrl(QUrl("qrc:///resources/user.css")); m_view->setHtml("

hello world

"); QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); QVERIFY(networkManager->requestedUrls.count() >= 1); QCOMPARE(networkManager->requestedUrls.at(0), QUrl("http://does.not/exist.png")); #endif } void tst_QWebEnginePage::loadHtml5Video() { #if defined(WTF_USE_QT_MULTIMEDIA) && WTF_USE_QT_MULTIMEDIA QByteArray url("http://does.not/exist?a=1%2Cb=2"); m_view->setHtml("

"); QTest::qWait(2000); QUrl mUrl = DumpRenderTreeSupportQt::mediaContentUrlByElementId(m_page->mainFrame()->handle(), "video"); QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=65452", Continue); QCOMPARE(mUrl.toEncoded(), url); #else W_QSKIP("This test requires Qt Multimedia", SkipAll); #endif } void tst_QWebEnginePage::modified() { #if !defined(QWEBENGINEPAGE_ISMODIFIED) QSKIP("QWEBENGINEPAGE_ISMODIFIED"); #else m_page->setUrl(QUrl("data:text/html,blub")); QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); m_page->setUrl(QUrl("data:text/html,blah")); QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); QVERIFY(!m_page->isModified()); m_page->runJavaScript("document.getElementById('foo').focus()"); evaluateJavaScriptSync(m_page, "document.execCommand('InsertText', true, 'Test');"); QVERIFY(m_page->isModified()); evaluateJavaScriptSync(m_page, "document.execCommand('Undo', true);"); QVERIFY(!m_page->isModified()); evaluateJavaScriptSync(m_page, "document.execCommand('Redo', true);"); QVERIFY(m_page->isModified()); QVERIFY(m_page->history()->canGoBack()); QVERIFY(!m_page->history()->canGoForward()); QCOMPARE(m_page->history()->count(), 2); QVERIFY(m_page->history()->backItem().isValid()); QVERIFY(!m_page->history()->forwardItem().isValid()); m_page->history()->back(); QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); QVERIFY(!m_page->history()->canGoBack()); QVERIFY(m_page->history()->canGoForward()); QVERIFY(!m_page->isModified()); QCOMPARE(m_page->history()->currentItemIndex(), 0); m_page->history()->setMaximumItemCount(3); QCOMPARE(m_page->history()->maximumItemCount(), 3); QVariant variant("string test"); m_page->history()->currentItem().setUserData(variant); QVERIFY(m_page->history()->currentItem().userData().toString() == "string test"); m_page->setUrl(QUrl("data:text/html,This is second page")); m_page->setUrl(QUrl("data:text/html,This is third page")); QCOMPARE(m_page->history()->count(), 2); m_page->setUrl(QUrl("data:text/html,This is fourth page")); QCOMPARE(m_page->history()->count(), 2); m_page->setUrl(QUrl("data:text/html,This is fifth page")); QVERIFY(::waitForSignal(m_page, SIGNAL(saveFrameStateRequested(QWebEngineFrame*,QWebEngineHistoryItem*)))); #endif } // https://bugs.webkit.org/show_bug.cgi?id=51331 void tst_QWebEnginePage::updatePositionDependentActionsCrash() { #if !defined(QWEBENGINEPAGE_UPDATEPOSITIONDEPENDENTACTIONS) QSKIP("QWEBENGINEPAGE_UPDATEPOSITIONDEPENDENTACTIONS"); #else QWebEngineView view; view.setHtml("

test"); QPoint pos(0, 0); view.page()->updatePositionDependentActions(pos); QMenu* contextMenu = 0; foreach (QObject* child, view.children()) { contextMenu = qobject_cast(child); if (contextMenu) break; } QVERIFY(!contextMenu); #endif } // https://bugs.webkit.org/show_bug.cgi?id=20357 void tst_QWebEnginePage::contextMenuCrash() { #if !defined(QWEBENGINEPAGE_SWALLOWCONTEXTMENUEVENT) QSKIP("QWEBENGINEPAGE_SWALLOWCONTEXTMENUEVENT"); #else QWebEngineView view; view.setHtml("

test"); QPoint pos(0, 0); QContextMenuEvent event(QContextMenuEvent::Mouse, pos); view.page()->swallowContextMenuEvent(&event); view.page()->updatePositionDependentActions(pos); QMenu* contextMenu = 0; foreach (QObject* child, view.children()) { contextMenu = qobject_cast(child); if (contextMenu) break; } QVERIFY(contextMenu); delete contextMenu; #endif } #if defined(QWEBENGINEPAGE_CREATEPLUGIN) class PluginPage : public QWebEnginePage { public: PluginPage(QObject *parent = 0) : QWebEnginePage(parent) {} struct CallInfo { CallInfo(const QString &c, const QUrl &u, const QStringList &pn, const QStringList &pv, QObject *r) : classid(c), url(u), paramNames(pn), paramValues(pv), returnValue(r) {} QString classid; QUrl url; QStringList paramNames; QStringList paramValues; QObject *returnValue; }; QList calls; protected: virtual QObject *createPlugin(const QString &classid, const QUrl &url, const QStringList ¶mNames, const QStringList ¶mValues) { QObject *result = 0; if (classid == "pushbutton") result = new QPushButton(); #ifndef QT_NO_INPUTDIALOG else if (classid == "lineedit") result = new QLineEdit(); #endif else if (classid == "graphicswidget") result = new QGraphicsWidget(); if (result) result->setObjectName(classid); calls.append(CallInfo(classid, url, paramNames, paramValues, result)); return result; } }; static void createPlugin(QWebEngineView *view) { QSignalSpy loadSpy(view, SIGNAL(loadFinished(bool))); PluginPage* newPage = new PluginPage(view); view->setPage(newPage); // type has to be application/x-qt-plugin view->setHtml(QString("")); QTRY_COMPARE(loadSpy.count(), 1); QCOMPARE(newPage->calls.count(), 0); view->setHtml(QString("")); QTRY_COMPARE(loadSpy.count(), 2); QCOMPARE(newPage->calls.count(), 1); { PluginPage::CallInfo ci = newPage->calls.takeFirst(); QCOMPARE(ci.classid, QString::fromLatin1("pushbutton")); QCOMPARE(ci.url, QUrl()); QCOMPARE(ci.paramNames.count(), 3); QCOMPARE(ci.paramValues.count(), 3); QCOMPARE(ci.paramNames.at(0), QString::fromLatin1("type")); QCOMPARE(ci.paramValues.at(0), QString::fromLatin1("application/x-qt-plugin")); QCOMPARE(ci.paramNames.at(1), QString::fromLatin1("classid")); QCOMPARE(ci.paramValues.at(1), QString::fromLatin1("pushbutton")); QCOMPARE(ci.paramNames.at(2), QString::fromLatin1("id")); QCOMPARE(ci.paramValues.at(2), QString::fromLatin1("mybutton")); QVERIFY(ci.returnValue != 0); QVERIFY(ci.returnValue->inherits("QPushButton")); } // test JS bindings QCOMPARE(evaluateJavaScriptSync(newPage, "document.getElementById('mybutton').toString()").toString(), QString::fromLatin1("[object HTMLObjectElement]")); QCOMPARE(evaluateJavaScriptSync(newPage, "mybutton.toString()").toString(), QString::fromLatin1("[object HTMLObjectElement]")); QCOMPARE(evaluateJavaScriptSync(newPage, "typeof mybutton.objectName").toString(), QString::fromLatin1("string")); QCOMPARE(evaluateJavaScriptSync(newPage, "mybutton.objectName").toString(), QString::fromLatin1("pushbutton")); QCOMPARE(evaluateJavaScriptSync(newPage, "typeof mybutton.clicked").toString(), QString::fromLatin1("function")); QCOMPARE(evaluateJavaScriptSync(newPage, "mybutton.clicked.toString()").toString(), QString::fromLatin1("function clicked() {\n [native code]\n}")); view->setHtml(QString("" "" "" "
"), QUrl("http://foo.bar.baz")); QTRY_COMPARE(loadSpy.count(), 3); QCOMPARE(newPage->calls.count(), 2); { PluginPage::CallInfo ci = newPage->calls.takeFirst(); QCOMPARE(ci.classid, QString::fromLatin1("lineedit")); QCOMPARE(ci.url, QUrl()); QCOMPARE(ci.paramNames.count(), 3); QCOMPARE(ci.paramValues.count(), 3); QCOMPARE(ci.paramNames.at(0), QString::fromLatin1("type")); QCOMPARE(ci.paramValues.at(0), QString::fromLatin1("application/x-qt-plugin")); QCOMPARE(ci.paramNames.at(1), QString::fromLatin1("classid")); QCOMPARE(ci.paramValues.at(1), QString::fromLatin1("lineedit")); QCOMPARE(ci.paramNames.at(2), QString::fromLatin1("id")); QCOMPARE(ci.paramValues.at(2), QString::fromLatin1("myedit")); QVERIFY(ci.returnValue != 0); QVERIFY(ci.returnValue->inherits("QLineEdit")); } { PluginPage::CallInfo ci = newPage->calls.takeFirst(); QCOMPARE(ci.classid, QString::fromLatin1("pushbutton")); QCOMPARE(ci.url, QUrl()); QCOMPARE(ci.paramNames.count(), 3); QCOMPARE(ci.paramValues.count(), 3); QCOMPARE(ci.paramNames.at(0), QString::fromLatin1("type")); QCOMPARE(ci.paramValues.at(0), QString::fromLatin1("application/x-qt-plugin")); QCOMPARE(ci.paramNames.at(1), QString::fromLatin1("classid")); QCOMPARE(ci.paramValues.at(1), QString::fromLatin1("pushbutton")); QCOMPARE(ci.paramNames.at(2), QString::fromLatin1("id")); QCOMPARE(ci.paramValues.at(2), QString::fromLatin1("mybutton")); QVERIFY(ci.returnValue != 0); QVERIFY(ci.returnValue->inherits("QPushButton")); } } #endif void tst_QWebEnginePage::graphicsWidgetPlugin() { #if !defined(QWEBENGINEPAGE_CREATEPLUGIN) QSKIP("QWEBENGINEPAGE_CREATEPLUGIN"); #else m_view->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); QGraphicsWebView webView; QSignalSpy loadSpy(&webView, SIGNAL(loadFinished(bool))); PluginPage* newPage = new PluginPage(&webView); webView.setPage(newPage); // type has to be application/x-qt-plugin webView.setHtml(QString("")); QTRY_COMPARE(loadSpy.count(), 1); QCOMPARE(newPage->calls.count(), 0); webView.setHtml(QString("")); QTRY_COMPARE(loadSpy.count(), 2); QCOMPARE(newPage->calls.count(), 1); { PluginPage::CallInfo ci = newPage->calls.takeFirst(); QCOMPARE(ci.classid, QString::fromLatin1("graphicswidget")); QCOMPARE(ci.url, QUrl()); QCOMPARE(ci.paramNames.count(), 3); QCOMPARE(ci.paramValues.count(), 3); QCOMPARE(ci.paramNames.at(0), QString::fromLatin1("type")); QCOMPARE(ci.paramValues.at(0), QString::fromLatin1("application/x-qt-plugin")); QCOMPARE(ci.paramNames.at(1), QString::fromLatin1("classid")); QCOMPARE(ci.paramValues.at(1), QString::fromLatin1("graphicswidget")); QCOMPARE(ci.paramNames.at(2), QString::fromLatin1("id")); QCOMPARE(ci.paramValues.at(2), QString::fromLatin1("mygraphicswidget")); QVERIFY(ci.returnValue); QVERIFY(ci.returnValue->inherits("QGraphicsWidget")); } // test JS bindings QCOMPARE(evaluateJavaScriptSync(newPage, "document.getElementById('mygraphicswidget').toString()").toString(), QString::fromLatin1("[object HTMLObjectElement]")); QCOMPARE(evaluateJavaScriptSync(newPage, "mygraphicswidget.toString()").toString(), QString::fromLatin1("[object HTMLObjectElement]")); QCOMPARE(evaluateJavaScriptSync(newPage, "typeof mygraphicswidget.objectName").toString(), QString::fromLatin1("string")); QCOMPARE(evaluateJavaScriptSync(newPage, "mygraphicswidget.objectName").toString(), QString::fromLatin1("graphicswidget")); QCOMPARE(evaluateJavaScriptSync(newPage, "typeof mygraphicswidget.geometryChanged").toString(), QString::fromLatin1("function")); QCOMPARE(evaluateJavaScriptSync(newPage, "mygraphicswidget.geometryChanged.toString()").toString(), QString::fromLatin1("function geometryChanged() {\n [native code]\n}")); #endif } void tst_QWebEnginePage::createPluginWithPluginsEnabled() { #if !defined(QWEBENGINEPAGE_CREATEPLUGIN) QSKIP("QWEBENGINEPAGE_CREATEPLUGIN"); #else m_view->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); createPlugin(m_view); #endif } void tst_QWebEnginePage::createPluginWithPluginsDisabled() { #if !defined(QWEBENGINEPAGE_CREATEPLUGIN) QSKIP("QWEBENGINEPAGE_CREATEPLUGIN"); #else // Qt Plugins should be loaded by QtWebEngine even when PluginsEnabled is // false. The client decides whether a Qt plugin is enabled or not when // it decides whether or not to instantiate it. m_view->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, false); createPlugin(m_view); #endif } #if defined(QWEBENGINEPAGE_CREATEPLUGIN) // Standard base class for template PluginTracerPage. In tests it is used as interface. class PluginCounterPage : public QWebEnginePage { public: int m_count; QPointer m_widget; QObject* m_pluginParent; PluginCounterPage(QObject* parent = 0) : QWebEnginePage(parent) , m_count(0) , m_pluginParent(0) { settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); } ~PluginCounterPage() { if (m_pluginParent) m_pluginParent->deleteLater(); } }; template class PluginTracerPage : public PluginCounterPage { public: PluginTracerPage(QObject* parent = 0) : PluginCounterPage(parent) { // this is a dummy parent object for the created plugin m_pluginParent = new T; } virtual QObject* createPlugin(const QString&, const QUrl&, const QStringList&, const QStringList&) { m_count++; m_widget = new T; // need a cast to the specific type, as QObject::setParent cannot be called, // because it is not virtual. Instead it is necessary to call QWidget::setParent, // which also takes a QWidget* instead of a QObject*. Therefore we need to // upcast to T*, which is a QWidget. static_cast(m_widget.data())->setParent(static_cast(m_pluginParent)); return m_widget.data(); } }; class PluginFactory { public: enum FactoredType {QWidgetType, QGraphicsWidgetType}; static PluginCounterPage* create(FactoredType type, QObject* parent = 0) { PluginCounterPage* result = 0; switch (type) { case QWidgetType: result = new PluginTracerPage(parent); break; case QGraphicsWidgetType: result = new PluginTracerPage(parent); break; default: {/*Oops*/}; } return result; } static void prepareTestData() { QTest::addColumn("type"); QTest::newRow("QWidget") << (int)PluginFactory::QWidgetType; QTest::newRow("QGraphicsWidget") << (int)PluginFactory::QGraphicsWidgetType; } }; #endif void tst_QWebEnginePage::destroyPlugin_data() { #if defined(QWEBENGINEPAGE_CREATEPLUGIN) PluginFactory::prepareTestData(); #endif } void tst_QWebEnginePage::destroyPlugin() { #if !defined(QWEBENGINEPAGE_CREATEPLUGIN) QSKIP("QWEBENGINEPAGE_CREATEPLUGIN"); #else QFETCH(int, type); PluginCounterPage* page = PluginFactory::create((PluginFactory::FactoredType)type, m_view); m_view->setPage(page); // we create the plugin, so the widget should be constructed QString content(""); m_view->setHtml(content); QVERIFY(page->m_widget); QCOMPARE(page->m_count, 1); // navigate away, the plugin widget should be destructed m_view->setHtml("Hi"); QTestEventLoop::instance().enterLoop(1); QVERIFY(!page->m_widget); #endif } void tst_QWebEnginePage::createViewlessPlugin_data() { #if defined(QWEBENGINEPAGE_CREATEPLUGIN) PluginFactory::prepareTestData(); #endif } void tst_QWebEnginePage::createViewlessPlugin() { #if !defined(QWEBENGINEPAGE_CREATEPLUGIN) QSKIP("QWEBENGINEPAGE_CREATEPLUGIN"); #else QFETCH(int, type); PluginCounterPage* page = PluginFactory::create((PluginFactory::FactoredType)type); QString content(""); page->setHtml(content); QCOMPARE(page->m_count, 1); QVERIFY(page->m_widget); QVERIFY(page->m_pluginParent); QVERIFY(page->m_widget.data()->parent() == page->m_pluginParent); delete page; #endif } void tst_QWebEnginePage::multiplePageGroupsAndLocalStorage() { #if !defined(QWEBENGINESETTINGS_SETLOCALSTORAGEPATH) QSKIP("QWEBENGINESETTINGS_SETLOCALSTORAGEPATH"); #else QDir dir(tmpDirPath()); dir.mkdir("path1"); dir.mkdir("path2"); QWebEngineView view1; QWebEngineView view2; view1.page()->settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, true); view1.page()->settings()->setLocalStoragePath(QDir::toNativeSeparators(tmpDirPath() + "/path1")); DumpRenderTreeSupportQt::webPageSetGroupName(view1.page()->handle(), "group1"); view2.page()->settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, true); view2.page()->settings()->setLocalStoragePath(QDir::toNativeSeparators(tmpDirPath() + "/path2")); DumpRenderTreeSupportQt::webPageSetGroupName(view2.page()->handle(), "group2"); QCOMPARE(DumpRenderTreeSupportQt::webPageGroupName(view1.page()->handle()), QString("group1")); QCOMPARE(DumpRenderTreeSupportQt::webPageGroupName(view2.page()->handle()), QString("group2")); view1.setHtml(QString(" "), QUrl("http://www.myexample.com")); view2.setHtml(QString(" "), QUrl("http://www.myexample.com")); evaluateJavaScriptSync(view1.page(), "localStorage.test='value1';"); evaluateJavaScriptSync(view2.page(), "localStorage.test='value2';"); view1.setHtml(QString(" "), QUrl("http://www.myexample.com")); view2.setHtml(QString(" "), QUrl("http://www.myexample.com")); QVariant s1 = evaluateJavaScriptSync(view1.page(), "localStorage.test"); QCOMPARE(s1.toString(), QString("value1")); QVariant s2 = evaluateJavaScriptSync(view2.page(), "localStorage.test"); QCOMPARE(s2.toString(), QString("value2")); QTest::qWait(1000); QFile::remove(QDir::toNativeSeparators(tmpDirPath() + "/path1/http_www.myexample.com_0.localstorage")); QFile::remove(QDir::toNativeSeparators(tmpDirPath() + "/path2/http_www.myexample.com_0.localstorage")); dir.rmdir(QDir::toNativeSeparators("./path1")); dir.rmdir(QDir::toNativeSeparators("./path2")); #endif } class CursorTrackedPage : public QWebEnginePage { public: CursorTrackedPage(QWidget *parent = 0): QWebEnginePage(parent) { } QString selectedText() { return evaluateJavaScriptSync(this, "window.getSelection().toString()").toString(); } int selectionStartOffset() { return evaluateJavaScriptSync(this, "window.getSelection().getRangeAt(0).startOffset").toInt(); } int selectionEndOffset() { return evaluateJavaScriptSync(this, "window.getSelection().getRangeAt(0).endOffset").toInt(); } // true if start offset == end offset, i.e. no selected text int isSelectionCollapsed() { return evaluateJavaScriptSync(this, "window.getSelection().getRangeAt(0).collapsed").toBool(); } bool hasSelection() { return !selectedText().isEmpty(); } }; void tst_QWebEnginePage::cursorMovements() { #if !defined(QWEBENGINEPAGE_SELECTEDTEXT) QSKIP("QWEBENGINEPAGE_SELECTEDTEXT"); #else CursorTrackedPage* page = new CursorTrackedPage; QString content("

The quick brown fox

jumps over the lazy dog

May the source
be with you!

"); page->setHtml(content); // this will select the first paragraph QString script = "var range = document.createRange(); " \ "var node = document.getElementById(\"one\"); " \ "range.selectNode(node); " \ "getSelection().addRange(range);"; evaluateJavaScriptSync(page, script); QCOMPARE(page->selectedText().trimmed(), QString::fromLatin1("The quick brown fox")); QRegExp regExp(" style=\".*\""); regExp.setMinimal(true); QCOMPARE(page->selectedHtml().trimmed().replace(regExp, ""), QString::fromLatin1("

The quick brown fox

")); // these actions must exist QVERIFY(page->action(QWebEnginePage::MoveToNextChar) != 0); QVERIFY(page->action(QWebEnginePage::MoveToPreviousChar) != 0); QVERIFY(page->action(QWebEnginePage::MoveToNextWord) != 0); QVERIFY(page->action(QWebEnginePage::MoveToPreviousWord) != 0); QVERIFY(page->action(QWebEnginePage::MoveToNextLine) != 0); QVERIFY(page->action(QWebEnginePage::MoveToPreviousLine) != 0); QVERIFY(page->action(QWebEnginePage::MoveToStartOfLine) != 0); QVERIFY(page->action(QWebEnginePage::MoveToEndOfLine) != 0); QVERIFY(page->action(QWebEnginePage::MoveToStartOfBlock) != 0); QVERIFY(page->action(QWebEnginePage::MoveToEndOfBlock) != 0); QVERIFY(page->action(QWebEnginePage::MoveToStartOfDocument) != 0); QVERIFY(page->action(QWebEnginePage::MoveToEndOfDocument) != 0); // right now they are disabled because contentEditable is false QCOMPARE(page->action(QWebEnginePage::MoveToNextChar)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::MoveToPreviousChar)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::MoveToNextWord)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::MoveToPreviousWord)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::MoveToNextLine)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::MoveToPreviousLine)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::MoveToStartOfLine)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::MoveToEndOfLine)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::MoveToStartOfBlock)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::MoveToEndOfBlock)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::MoveToStartOfDocument)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::MoveToEndOfDocument)->isEnabled(), false); // make it editable before navigating the cursor page->setContentEditable(true); // here the actions are enabled after contentEditable is true QCOMPARE(page->action(QWebEnginePage::MoveToNextChar)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::MoveToPreviousChar)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::MoveToNextWord)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::MoveToPreviousWord)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::MoveToNextLine)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::MoveToPreviousLine)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::MoveToStartOfLine)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::MoveToEndOfLine)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::MoveToStartOfBlock)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::MoveToEndOfBlock)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::MoveToStartOfDocument)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::MoveToEndOfDocument)->isEnabled(), true); // cursor will be before the word "jump" page->triggerAction(QWebEnginePage::MoveToNextChar); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 0); // cursor will be between 'j' and 'u' in the word "jump" page->triggerAction(QWebEnginePage::MoveToNextChar); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 1); // cursor will be between 'u' and 'm' in the word "jump" page->triggerAction(QWebEnginePage::MoveToNextChar); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 2); // cursor will be after the word "jump" page->triggerAction(QWebEnginePage::MoveToNextWord); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 5); // cursor will be after the word "lazy" page->triggerAction(QWebEnginePage::MoveToNextWord); page->triggerAction(QWebEnginePage::MoveToNextWord); page->triggerAction(QWebEnginePage::MoveToNextWord); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 19); // cursor will be between 'z' and 'y' in "lazy" page->triggerAction(QWebEnginePage::MoveToPreviousChar); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 18); // cursor will be between 'a' and 'z' in "lazy" page->triggerAction(QWebEnginePage::MoveToPreviousChar); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 17); // cursor will be before the word "lazy" page->triggerAction(QWebEnginePage::MoveToPreviousWord); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 15); // cursor will be before the word "quick" page->triggerAction(QWebEnginePage::MoveToPreviousWord); page->triggerAction(QWebEnginePage::MoveToPreviousWord); page->triggerAction(QWebEnginePage::MoveToPreviousWord); page->triggerAction(QWebEnginePage::MoveToPreviousWord); page->triggerAction(QWebEnginePage::MoveToPreviousWord); page->triggerAction(QWebEnginePage::MoveToPreviousWord); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 4); // cursor will be between 'p' and 's' in the word "jumps" page->triggerAction(QWebEnginePage::MoveToNextWord); page->triggerAction(QWebEnginePage::MoveToNextWord); page->triggerAction(QWebEnginePage::MoveToNextWord); page->triggerAction(QWebEnginePage::MoveToNextChar); page->triggerAction(QWebEnginePage::MoveToNextChar); page->triggerAction(QWebEnginePage::MoveToNextChar); page->triggerAction(QWebEnginePage::MoveToNextChar); page->triggerAction(QWebEnginePage::MoveToNextChar); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 4); // cursor will be before the word "jumps" page->triggerAction(QWebEnginePage::MoveToStartOfLine); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 0); // cursor will be after the word "dog" page->triggerAction(QWebEnginePage::MoveToEndOfLine); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 23); // cursor will be between 'w' and 'n' in "brown" page->triggerAction(QWebEnginePage::MoveToStartOfLine); page->triggerAction(QWebEnginePage::MoveToPreviousWord); page->triggerAction(QWebEnginePage::MoveToPreviousWord); page->triggerAction(QWebEnginePage::MoveToNextChar); page->triggerAction(QWebEnginePage::MoveToNextChar); page->triggerAction(QWebEnginePage::MoveToNextChar); page->triggerAction(QWebEnginePage::MoveToNextChar); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 14); // cursor will be after the word "fox" page->triggerAction(QWebEnginePage::MoveToEndOfLine); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 19); // cursor will be before the word "The" page->triggerAction(QWebEnginePage::MoveToStartOfDocument); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 0); // cursor will be after the word "you!" page->triggerAction(QWebEnginePage::MoveToEndOfDocument); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 12); // cursor will be before the word "be" page->triggerAction(QWebEnginePage::MoveToStartOfBlock); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 0); // cursor will be after the word "you!" page->triggerAction(QWebEnginePage::MoveToEndOfBlock); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 12); // try to move before the document start page->triggerAction(QWebEnginePage::MoveToStartOfDocument); page->triggerAction(QWebEnginePage::MoveToPreviousChar); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 0); page->triggerAction(QWebEnginePage::MoveToStartOfDocument); page->triggerAction(QWebEnginePage::MoveToPreviousWord); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 0); // try to move past the document end page->triggerAction(QWebEnginePage::MoveToEndOfDocument); page->triggerAction(QWebEnginePage::MoveToNextChar); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 12); page->triggerAction(QWebEnginePage::MoveToEndOfDocument); page->triggerAction(QWebEnginePage::MoveToNextWord); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 12); delete page; #endif } void tst_QWebEnginePage::textSelection() { QWebEngineView *view = new QWebEngineView; CursorTrackedPage *page = new CursorTrackedPage; QString content("

The quick brown fox

" \ "

jumps over the lazy dog

" \ "

May the source
be with you!

"); page->setView(view); QSignalSpy loadSpy(view, SIGNAL(loadFinished(bool))); page->setHtml(content); QTRY_COMPARE(loadSpy.count(), 1); // these actions must exist QVERIFY(page->action(QWebEnginePage::SelectAll) != 0); #if defined(QWEBENGINEPAGE_SELECTACTIONS) QVERIFY(page->action(QWebEnginePage::SelectNextChar) != 0); QVERIFY(page->action(QWebEnginePage::SelectPreviousChar) != 0); QVERIFY(page->action(QWebEnginePage::SelectNextWord) != 0); QVERIFY(page->action(QWebEnginePage::SelectPreviousWord) != 0); QVERIFY(page->action(QWebEnginePage::SelectNextLine) != 0); QVERIFY(page->action(QWebEnginePage::SelectPreviousLine) != 0); QVERIFY(page->action(QWebEnginePage::SelectStartOfLine) != 0); QVERIFY(page->action(QWebEnginePage::SelectEndOfLine) != 0); QVERIFY(page->action(QWebEnginePage::SelectStartOfBlock) != 0); QVERIFY(page->action(QWebEnginePage::SelectEndOfBlock) != 0); QVERIFY(page->action(QWebEnginePage::SelectStartOfDocument) != 0); QVERIFY(page->action(QWebEnginePage::SelectEndOfDocument) != 0); // right now they are disabled because contentEditable is false and // there isn't an existing selection to modify QCOMPARE(page->action(QWebEnginePage::SelectNextChar)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SelectPreviousChar)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SelectNextWord)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SelectPreviousWord)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SelectNextLine)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SelectPreviousLine)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SelectStartOfLine)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SelectEndOfLine)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SelectStartOfBlock)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SelectEndOfBlock)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SelectStartOfDocument)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SelectEndOfDocument)->isEnabled(), false); #endif // ..but SelectAll is awalys enabled QCOMPARE(page->action(QWebEnginePage::SelectAll)->isEnabled(), true); // Verify hasSelection returns false since there is no selection yet... QCOMPARE(page->hasSelection(), false); // this will select the first paragraph QString selectScript = "var range = document.createRange(); " \ "var node = document.getElementById(\"one\"); " \ "range.selectNode(node); " \ "getSelection().addRange(range);"; evaluateJavaScriptSync(page, selectScript); QCOMPARE(page->selectedText().trimmed(), QString::fromLatin1("The quick brown fox")); #if defined(QWEBENGINEPAGE_SELECTEDHTML) QRegExp regExp(" style=\".*\""); regExp.setMinimal(true); QCOMPARE(page->selectedHtml().trimmed().replace(regExp, ""), QString::fromLatin1("

The quick brown fox

")); #endif // Make sure hasSelection returns true, since there is selected text now... QCOMPARE(page->hasSelection(), true); #if defined(QWEBENGINEPAGE_SELECTACTIONS) // here the actions are enabled after a selection has been created QCOMPARE(page->action(QWebEnginePage::SelectNextChar)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectPreviousChar)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectNextWord)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectPreviousWord)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectNextLine)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectPreviousLine)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectStartOfLine)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectEndOfLine)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectStartOfBlock)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectEndOfBlock)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectStartOfDocument)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectEndOfDocument)->isEnabled(), true); // make it editable before navigating the cursor page->setContentEditable(true); // cursor will be before the word "The", this makes sure there is a charet page->triggerAction(QWebEnginePage::MoveToStartOfDocument); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 0); // here the actions are enabled after contentEditable is true QCOMPARE(page->action(QWebEnginePage::SelectNextChar)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectPreviousChar)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectNextWord)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectPreviousWord)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectNextLine)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectPreviousLine)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectStartOfLine)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectEndOfLine)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectStartOfBlock)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectEndOfBlock)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectStartOfDocument)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SelectEndOfDocument)->isEnabled(), true); #endif delete page; delete view; } void tst_QWebEnginePage::textEditing() { #if !defined(QWEBENGINEPAGE_EVALUATEJAVASCRIPT) QSKIP("QWEBENGINEPAGE_EVALUATEJAVASCRIPT"); #else CursorTrackedPage* page = new CursorTrackedPage; QString content("

The quick brown fox

" \ "

jumps over the lazy dog

" \ "

May the source
be with you!

"); page->setHtml(content); // these actions must exist QVERIFY(page->action(QWebEnginePage::Cut) != 0); QVERIFY(page->action(QWebEnginePage::Copy) != 0); QVERIFY(page->action(QWebEnginePage::Paste) != 0); QVERIFY(page->action(QWebEnginePage::DeleteStartOfWord) != 0); QVERIFY(page->action(QWebEnginePage::DeleteEndOfWord) != 0); QVERIFY(page->action(QWebEnginePage::SetTextDirectionDefault) != 0); QVERIFY(page->action(QWebEnginePage::SetTextDirectionLeftToRight) != 0); QVERIFY(page->action(QWebEnginePage::SetTextDirectionRightToLeft) != 0); QVERIFY(page->action(QWebEnginePage::ToggleBold) != 0); QVERIFY(page->action(QWebEnginePage::ToggleItalic) != 0); QVERIFY(page->action(QWebEnginePage::ToggleUnderline) != 0); QVERIFY(page->action(QWebEnginePage::InsertParagraphSeparator) != 0); QVERIFY(page->action(QWebEnginePage::InsertLineSeparator) != 0); QVERIFY(page->action(QWebEnginePage::PasteAndMatchStyle) != 0); QVERIFY(page->action(QWebEnginePage::RemoveFormat) != 0); QVERIFY(page->action(QWebEnginePage::ToggleStrikethrough) != 0); QVERIFY(page->action(QWebEnginePage::ToggleSubscript) != 0); QVERIFY(page->action(QWebEnginePage::ToggleSuperscript) != 0); QVERIFY(page->action(QWebEnginePage::InsertUnorderedList) != 0); QVERIFY(page->action(QWebEnginePage::InsertOrderedList) != 0); QVERIFY(page->action(QWebEnginePage::Indent) != 0); QVERIFY(page->action(QWebEnginePage::Outdent) != 0); QVERIFY(page->action(QWebEnginePage::AlignCenter) != 0); QVERIFY(page->action(QWebEnginePage::AlignJustified) != 0); QVERIFY(page->action(QWebEnginePage::AlignLeft) != 0); QVERIFY(page->action(QWebEnginePage::AlignRight) != 0); // right now they are disabled because contentEditable is false QCOMPARE(page->action(QWebEnginePage::Cut)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::Paste)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::DeleteStartOfWord)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::DeleteEndOfWord)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SetTextDirectionDefault)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SetTextDirectionLeftToRight)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::SetTextDirectionRightToLeft)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::ToggleBold)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::ToggleItalic)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::ToggleUnderline)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::InsertParagraphSeparator)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::InsertLineSeparator)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::PasteAndMatchStyle)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::RemoveFormat)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::ToggleStrikethrough)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::ToggleSubscript)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::ToggleSuperscript)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::InsertUnorderedList)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::InsertOrderedList)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::Indent)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::Outdent)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::AlignCenter)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::AlignJustified)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::AlignLeft)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::AlignRight)->isEnabled(), false); // Select everything page->triggerAction(QWebEnginePage::SelectAll); // make sure it is enabled since there is a selection QCOMPARE(page->action(QWebEnginePage::Copy)->isEnabled(), true); // make it editable before navigating the cursor page->setContentEditable(true); // clear the selection page->triggerAction(QWebEnginePage::MoveToStartOfDocument); QVERIFY(page->isSelectionCollapsed()); QCOMPARE(page->selectionStartOffset(), 0); // make sure it is disabled since there isn't a selection QCOMPARE(page->action(QWebEnginePage::Copy)->isEnabled(), false); // here the actions are enabled after contentEditable is true QCOMPARE(page->action(QWebEnginePage::Paste)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::DeleteStartOfWord)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::DeleteEndOfWord)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SetTextDirectionDefault)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SetTextDirectionLeftToRight)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::SetTextDirectionRightToLeft)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::ToggleBold)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::ToggleItalic)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::ToggleUnderline)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::InsertParagraphSeparator)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::InsertLineSeparator)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::PasteAndMatchStyle)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::ToggleStrikethrough)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::ToggleSubscript)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::ToggleSuperscript)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::InsertUnorderedList)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::InsertOrderedList)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::Indent)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::Outdent)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::AlignCenter)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::AlignJustified)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::AlignLeft)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::AlignRight)->isEnabled(), true); // make sure these are disabled since there isn't a selection QCOMPARE(page->action(QWebEnginePage::Cut)->isEnabled(), false); QCOMPARE(page->action(QWebEnginePage::RemoveFormat)->isEnabled(), false); // make sure everything is selected page->triggerAction(QWebEnginePage::SelectAll); // this is only true if there is an editable selection QCOMPARE(page->action(QWebEnginePage::Cut)->isEnabled(), true); QCOMPARE(page->action(QWebEnginePage::RemoveFormat)->isEnabled(), true); delete page; #endif } void tst_QWebEnginePage::backActionUpdate() { QWebEngineView view; QWebEnginePage *page = view.page(); QAction *action = page->action(QWebEnginePage::Back); QVERIFY(!action->isEnabled()); QSignalSpy loadSpy(page, SIGNAL(loadFinished(bool))); QUrl url = QUrl("qrc:///resources/framedindex.html"); page->load(url); QTRY_COMPARE(loadSpy.count(), 1); QVERIFY(!action->isEnabled()); QTest::mouseClick(&view, Qt::LeftButton, 0, QPoint(10, 10)); QEXPECT_FAIL("", "Behavior change: Load signals are emitted only for the main frame in QtWebEngine.", Continue); QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 2, 100); QEXPECT_FAIL("", "FIXME: Mouse events aren't passed from the QWebEngineView down to the RWHVQtDelegateWidget", Continue); QVERIFY(action->isEnabled()); } void tst_QWebEnginePage::inputMethods_data() { QTest::addColumn("viewType"); QTest::newRow("QWebEngineView") << "QWebEngineView"; QTest::newRow("QGraphicsWebView") << "QGraphicsWebView"; } #if defined(QWEBENGINEPAGE_INPUTMETHODQUERY) static Qt::InputMethodHints inputMethodHints(QObject* object) { if (QGraphicsObject* o = qobject_cast(object)) return o->inputMethodHints(); if (QWidget* w = qobject_cast(object)) return w->inputMethodHints(); return Qt::InputMethodHints(); } static bool inputMethodEnabled(QObject* object) { if (QGraphicsObject* o = qobject_cast(object)) return o->flags() & QGraphicsItem::ItemAcceptsInputMethod; if (QWidget* w = qobject_cast(object)) return w->testAttribute(Qt::WA_InputMethodEnabled); return false; } static void clickOnPage(QWebEnginePage* page, const QPoint& position) { QMouseEvent evpres(QEvent::MouseButtonPress, position, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); page->event(&evpres); QMouseEvent evrel(QEvent::MouseButtonRelease, position, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); page->event(&evrel); } #endif void tst_QWebEnginePage::inputMethods() { #if !defined(QWEBENGINEPAGE_INPUTMETHODQUERY) QSKIP("QWEBENGINEPAGE_INPUTMETHODQUERY"); #else QFETCH(QString, viewType); QWebEnginePage* page = new QWebEnginePage; QObject* view = 0; QObject* container = 0; if (viewType == "QWebEngineView") { QWebEngineView* wv = new QWebEngineView; wv->setPage(page); view = wv; container = view; } else if (viewType == "QGraphicsWebView") { QGraphicsWebView* wv = new QGraphicsWebView; wv->setPage(page); view = wv; QGraphicsView* gv = new QGraphicsView; QGraphicsScene* scene = new QGraphicsScene(gv); gv->setScene(scene); scene->addItem(wv); wv->setGeometry(QRect(0, 0, 500, 500)); container = gv; } else QVERIFY2(false, "Unknown view type"); page->settings()->setFontFamily(QWebEngineSettings::SerifFont, page->settings()->fontFamily(QWebEngineSettings::FixedFont)); page->setHtml("" \ "
" \ "" \ ""); page->mainFrame()->setFocus(); TestInputContext testContext; QWebEngineElementCollection inputs = page->mainFrame()->documentElement().findAll("input"); QPoint textInputCenter = inputs.at(0).geometry().center(); clickOnPage(page, textInputCenter); // This part of the test checks if the SIP (Software Input Panel) is triggered, // which normally happens on mobile platforms, when a user input form receives // a mouse click. int inputPanel = 0; if (viewType == "QWebEngineView") { if (QWebEngineView* wv = qobject_cast(view)) inputPanel = wv->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel); } else if (viewType == "QGraphicsWebView") { if (QGraphicsWebView* wv = qobject_cast(view)) inputPanel = wv->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel); } // For non-mobile platforms RequestSoftwareInputPanel event is not called // because there is no SIP (Software Input Panel) triggered. In the case of a // mobile platform, an input panel, e.g. virtual keyboard, is usually invoked // and the RequestSoftwareInputPanel event is called. For these two situations // this part of the test can verified as the checks below. if (inputPanel) QVERIFY(testContext.isInputPanelVisible()); else QVERIFY(!testContext.isInputPanelVisible()); testContext.hideInputPanel(); clickOnPage(page, textInputCenter); QVERIFY(testContext.isInputPanelVisible()); //ImMicroFocus QVariant variant = page->inputMethodQuery(Qt::ImMicroFocus); QVERIFY(inputs.at(0).geometry().contains(variant.toRect().topLeft())); // We assigned the serif font famility to be the same as the fixef font family. // Then test ImFont on a serif styled element, we should get our fixef font family. variant = page->inputMethodQuery(Qt::ImFont); QFont font = variant.value(); QCOMPARE(page->settings()->fontFamily(QWebEngineSettings::FixedFont), font.family()); QList inputAttributes; //Insert text. { QInputMethodEvent eventText("QtWebEngine", inputAttributes); QSignalSpy signalSpy(page, SIGNAL(microFocusChanged())); page->event(&eventText); QCOMPARE(signalSpy.count(), 0); } { QInputMethodEvent eventText("", inputAttributes); eventText.setCommitString(QString("QtWebEngine"), 0, 0); page->event(&eventText); } //ImMaximumTextLength variant = page->inputMethodQuery(Qt::ImMaximumTextLength); QCOMPARE(20, variant.toInt()); //Set selection inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 3, 2, QVariant()); QInputMethodEvent eventSelection("",inputAttributes); page->event(&eventSelection); //ImAnchorPosition variant = page->inputMethodQuery(Qt::ImAnchorPosition); int anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 3); //ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); int cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 5); //ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); QString selectionValue = variant.value(); QCOMPARE(selectionValue, QString("eb")); //Set selection with negative length inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 6, -5, QVariant()); QInputMethodEvent eventSelection3("",inputAttributes); page->event(&eventSelection3); //ImAnchorPosition variant = page->inputMethodQuery(Qt::ImAnchorPosition); anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 1); //ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 6); //ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); selectionValue = variant.value(); QCOMPARE(selectionValue, QString("tWebK")); //ImSurroundingText variant = page->inputMethodQuery(Qt::ImSurroundingText); QString value = variant.value(); QCOMPARE(value, QString("QtWebEngine")); { QList attributes; // Clear the selection, so the next test does not clear any contents. QInputMethodEvent::Attribute newSelection(QInputMethodEvent::Selection, 0, 0, QVariant()); attributes.append(newSelection); QInputMethodEvent event("composition", attributes); page->event(&event); } // A ongoing composition should not change the surrounding text before it is committed. variant = page->inputMethodQuery(Qt::ImSurroundingText); value = variant.value(); QCOMPARE(value, QString("QtWebEngine")); // Cancel current composition first inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 0, 0, QVariant()); QInputMethodEvent eventSelection4("", inputAttributes); page->event(&eventSelection4); // START - Tests for Selection when the Editor is NOT in Composition mode // LEFT to RIGHT selection // Deselect the selection by sending MouseButtonPress events // This moves the current cursor to the end of the text clickOnPage(page, textInputCenter); { QList attributes; QInputMethodEvent event(QString(), attributes); event.setCommitString("XXX", 0, 0); page->event(&event); event.setCommitString(QString(), -2, 2); // Erase two characters. page->event(&event); event.setCommitString(QString(), -1, 1); // Erase one character. page->event(&event); variant = page->inputMethodQuery(Qt::ImSurroundingText); value = variant.value(); QCOMPARE(value, QString("QtWebEngine")); } //Move to the start of the line page->triggerAction(QWebEnginePage::MoveToStartOfLine); QKeyEvent keyRightEventPress(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier); QKeyEvent keyRightEventRelease(QEvent::KeyRelease, Qt::Key_Right, Qt::NoModifier); //Move 2 characters RIGHT for (int j = 0; j < 2; ++j) { page->event(&keyRightEventPress); page->event(&keyRightEventRelease); } //Select to the end of the line page->triggerAction(QWebEnginePage::SelectEndOfLine); //ImAnchorPosition QtWebEngine variant = page->inputMethodQuery(Qt::ImAnchorPosition); anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 2); //ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 8); //ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); selectionValue = variant.value(); QCOMPARE(selectionValue, QString("WebKit")); //RIGHT to LEFT selection //Deselect the selection (this moves the current cursor to the end of the text) clickOnPage(page, textInputCenter); //ImAnchorPosition variant = page->inputMethodQuery(Qt::ImAnchorPosition); anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 8); //ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 8); //ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); selectionValue = variant.value(); QCOMPARE(selectionValue, QString("")); QKeyEvent keyLeftEventPress(QEvent::KeyPress, Qt::Key_Left, Qt::NoModifier); QKeyEvent keyLeftEventRelease(QEvent::KeyRelease, Qt::Key_Left, Qt::NoModifier); //Move 2 characters LEFT for (int i = 0; i < 2; ++i) { page->event(&keyLeftEventPress); page->event(&keyLeftEventRelease); } //Select to the start of the line page->triggerAction(QWebEnginePage::SelectStartOfLine); //ImAnchorPosition variant = page->inputMethodQuery(Qt::ImAnchorPosition); anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 6); //ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 0); //ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); selectionValue = variant.value(); QCOMPARE(selectionValue, QString("QtWebK")); //END - Tests for Selection when the Editor is not in Composition mode //ImhHiddenText QPoint passwordInputCenter = inputs.at(1).geometry().center(); clickOnPage(page, passwordInputCenter); QVERIFY(inputMethodEnabled(view)); QVERIFY(inputMethodHints(view) & Qt::ImhHiddenText); clickOnPage(page, textInputCenter); QVERIFY(!(inputMethodHints(view) & Qt::ImhHiddenText)); page->setHtml("

nothing to input here"); testContext.hideInputPanel(); QWebEngineElement para = page->mainFrame()->findFirstElement("p"); clickOnPage(page, para.geometry().center()); QVERIFY(!testContext.isInputPanelVisible()); //START - Test for sending empty QInputMethodEvent page->setHtml("" \ "" \ ""); evaluateJavaScriptSync(page, "var inputEle = document.getElementById('input3'); inputEle.focus(); inputEle.select();"); //Send empty QInputMethodEvent QInputMethodEvent emptyEvent; page->event(&emptyEvent); QString inputValue = evaluateJavaScriptSync(page, "document.getElementById('input3').value").toString(); QCOMPARE(inputValue, QString("QtWebEngine2")); //END - Test for sending empty QInputMethodEvent page->setHtml("" \ "" \ ""); evaluateJavaScriptSync(page, "var inputEle = document.getElementById('input4'); inputEle.focus(); inputEle.select();"); // Clear the selection, also cancel the ongoing composition if there is one. { QList attributes; QInputMethodEvent::Attribute newSelection(QInputMethodEvent::Selection, 0, 0, QVariant()); attributes.append(newSelection); QInputMethodEvent event("", attributes); page->event(&event); } // ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); selectionValue = variant.value(); QCOMPARE(selectionValue, QString("")); variant = page->inputMethodQuery(Qt::ImSurroundingText); QString surroundingValue = variant.value(); QCOMPARE(surroundingValue, QString("QtWebEngine inputMethod")); // ImAnchorPosition variant = page->inputMethodQuery(Qt::ImAnchorPosition); anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 0); // ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 0); // 1. Insert a character to the beginning of the line. // Send temporary text, which makes the editor has composition 'm'. { QList attributes; QInputMethodEvent event("m", attributes); page->event(&event); } // ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); selectionValue = variant.value(); QCOMPARE(selectionValue, QString("")); // ImSurroundingText variant = page->inputMethodQuery(Qt::ImSurroundingText); surroundingValue = variant.value(); QCOMPARE(surroundingValue, QString("QtWebEngine inputMethod")); // ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 0); // ImAnchorPosition variant = page->inputMethodQuery(Qt::ImAnchorPosition); anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 0); // Send temporary text, which makes the editor has composition 'n'. { QList attributes; QInputMethodEvent event("n", attributes); page->event(&event); } // ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); selectionValue = variant.value(); QCOMPARE(selectionValue, QString("")); // ImSurroundingText variant = page->inputMethodQuery(Qt::ImSurroundingText); surroundingValue = variant.value(); QCOMPARE(surroundingValue, QString("QtWebEngine inputMethod")); // ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 0); // ImAnchorPosition variant = page->inputMethodQuery(Qt::ImAnchorPosition); anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 0); // Send commit text, which makes the editor conforms composition. { QList attributes; QInputMethodEvent event("", attributes); event.setCommitString("o"); page->event(&event); } // ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); selectionValue = variant.value(); QCOMPARE(selectionValue, QString("")); // ImSurroundingText variant = page->inputMethodQuery(Qt::ImSurroundingText); surroundingValue = variant.value(); QCOMPARE(surroundingValue, QString("oQtWebEngine inputMethod")); // ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 1); // ImAnchorPosition variant = page->inputMethodQuery(Qt::ImAnchorPosition); anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 1); // 2. insert a character to the middle of the line. // Send temporary text, which makes the editor has composition 'd'. { QList attributes; QInputMethodEvent event("d", attributes); page->event(&event); } // ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); selectionValue = variant.value(); QCOMPARE(selectionValue, QString("")); // ImSurroundingText variant = page->inputMethodQuery(Qt::ImSurroundingText); surroundingValue = variant.value(); QCOMPARE(surroundingValue, QString("oQtWebEngine inputMethod")); // ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 1); // ImAnchorPosition variant = page->inputMethodQuery(Qt::ImAnchorPosition); anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 1); // Send commit text, which makes the editor conforms composition. { QList attributes; QInputMethodEvent event("", attributes); event.setCommitString("e"); page->event(&event); } // ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); selectionValue = variant.value(); QCOMPARE(selectionValue, QString("")); // ImSurroundingText variant = page->inputMethodQuery(Qt::ImSurroundingText); surroundingValue = variant.value(); QCOMPARE(surroundingValue, QString("oeQtWebEngine inputMethod")); // ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 2); // ImAnchorPosition variant = page->inputMethodQuery(Qt::ImAnchorPosition); anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 2); // 3. Insert a character to the end of the line. page->triggerAction(QWebEnginePage::MoveToEndOfLine); // Send temporary text, which makes the editor has composition 't'. { QList attributes; QInputMethodEvent event("t", attributes); page->event(&event); } // ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); selectionValue = variant.value(); QCOMPARE(selectionValue, QString("")); // ImSurroundingText variant = page->inputMethodQuery(Qt::ImSurroundingText); surroundingValue = variant.value(); QCOMPARE(surroundingValue, QString("oeQtWebEngine inputMethod")); // ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 22); // ImAnchorPosition variant = page->inputMethodQuery(Qt::ImAnchorPosition); anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 22); // Send commit text, which makes the editor conforms composition. { QList attributes; QInputMethodEvent event("", attributes); event.setCommitString("t"); page->event(&event); } // ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); selectionValue = variant.value(); QCOMPARE(selectionValue, QString("")); // ImSurroundingText variant = page->inputMethodQuery(Qt::ImSurroundingText); surroundingValue = variant.value(); QCOMPARE(surroundingValue, QString("oeQtWebEngine inputMethodt")); // ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 23); // ImAnchorPosition variant = page->inputMethodQuery(Qt::ImAnchorPosition); anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 23); // 4. Replace the selection. page->triggerAction(QWebEnginePage::SelectPreviousWord); // ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); selectionValue = variant.value(); QCOMPARE(selectionValue, QString("inputMethodt")); // ImSurroundingText variant = page->inputMethodQuery(Qt::ImSurroundingText); surroundingValue = variant.value(); QCOMPARE(surroundingValue, QString("oeQtWebEngine inputMethodt")); // ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 11); // ImAnchorPosition variant = page->inputMethodQuery(Qt::ImAnchorPosition); anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 23); // Send temporary text, which makes the editor has composition 'w'. { QList attributes; QInputMethodEvent event("w", attributes); page->event(&event); } // ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); selectionValue = variant.value(); QCOMPARE(selectionValue, QString("")); // ImSurroundingText variant = page->inputMethodQuery(Qt::ImSurroundingText); surroundingValue = variant.value(); QCOMPARE(surroundingValue, QString("oeQtWebEngine ")); // ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 11); // ImAnchorPosition variant = page->inputMethodQuery(Qt::ImAnchorPosition); anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 11); // Send commit text, which makes the editor conforms composition. { QList attributes; QInputMethodEvent event("", attributes); event.setCommitString("2"); page->event(&event); } // ImCurrentSelection variant = page->inputMethodQuery(Qt::ImCurrentSelection); selectionValue = variant.value(); QCOMPARE(selectionValue, QString("")); // ImSurroundingText variant = page->inputMethodQuery(Qt::ImSurroundingText); surroundingValue = variant.value(); QCOMPARE(surroundingValue, QString("oeQtWebEngine 2")); // ImCursorPosition variant = page->inputMethodQuery(Qt::ImCursorPosition); cursorPosition = variant.toInt(); QCOMPARE(cursorPosition, 12); // ImAnchorPosition variant = page->inputMethodQuery(Qt::ImAnchorPosition); anchorPosition = variant.toInt(); QCOMPARE(anchorPosition, 12); // Check sending RequestSoftwareInputPanel event page->setHtml("" \ "" \ "

abc
"\ ""); QWebEngineElement inputElement = page->mainFrame()->findFirstElement("div"); clickOnPage(page, inputElement.geometry().center()); QVERIFY(!testContext.isInputPanelVisible()); // START - Newline test for textarea qApp->processEvents(); page->setHtml("" \ "