diff options
author | Qt Forward Merge Bot <qt_forward_merge_bot@qt-project.org> | 2019-10-11 12:44:19 +0200 |
---|---|---|
committer | Qt Forward Merge Bot <qt_forward_merge_bot@qt-project.org> | 2019-10-11 12:44:19 +0200 |
commit | ab1bd15209abaf7effc51dbc2f272c5681af7223 (patch) | |
tree | 680bfbc4ab13514a9d2288609377bd8461f1d9f6 /tests/webkitwidgets/qwebpage/tst_qwebpage.cpp | |
parent | 5909e6d0d10de3e1370b3ea0bc596f580101e3b4 (diff) | |
parent | 2eac3aeb98fca0e6c13aaaff481861c5ef679e68 (diff) | |
download | qtwebkit-dev.tar.gz |
Change-Id: I2b773e6958cf1d3699ff7887f2807572f1dafa8d
Diffstat (limited to 'tests/webkitwidgets/qwebpage/tst_qwebpage.cpp')
-rw-r--r-- | tests/webkitwidgets/qwebpage/tst_qwebpage.cpp | 3518 |
1 files changed, 3518 insertions, 0 deletions
diff --git a/tests/webkitwidgets/qwebpage/tst_qwebpage.cpp b/tests/webkitwidgets/qwebpage/tst_qwebpage.cpp new file mode 100644 index 000000000..eb50384e7 --- /dev/null +++ b/tests/webkitwidgets/qwebpage/tst_qwebpage.cpp @@ -0,0 +1,3518 @@ +/* + Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + Copyright (C) 2009 Girish Ramakrishnan <girish@forwardbias.in> + 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 <QClipboard> +#include <QDir> +#include <QGraphicsWidget> +#include <QLineEdit> +#include <QMainWindow> +#include <QMenu> +#include <QMimeDatabase> +#include <QPushButton> +#include <QRegExp> +#include <QStateMachine> +#include <QStyle> +#include <QtTest/QtTest> +#include <QTextCharFormat> +#include <private/qinputmethod_p.h> +#include <qgraphicsscene.h> +#include <qgraphicsview.h> +#include <qgraphicswebview.h> +#include <qnetworkcookiejar.h> +#include <qnetworkreply.h> +#include <qnetworkrequest.h> +#include <qpa/qplatforminputcontext.h> +#include <qwebdatabase.h> +#include <qwebelement.h> +#include <qwebframe.h> +#include <qwebhistory.h> +#include <qwebpage.h> +#include <qwebsecurityorigin.h> +#include <qwebview.h> +#include <qimagewriter.h> + +#ifdef HAVE_QTTESTSUPPORT +#include "../WebCoreSupport/DumpRenderTreeSupportQt.h" +#endif + +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_QWebPage : public QObject +{ + Q_OBJECT + +public: + tst_QWebPage(); + virtual ~tst_QWebPage(); + +public Q_SLOTS: + void init(); + void cleanup(); + void cleanupFiles(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + +#ifdef HAVE_QTTESTSUPPORT + void thirdPartyCookiePolicy(); +#endif + + void contextMenuCopy(); + void contextMenuPopulatedOnce(); + void acceptNavigationRequest(); + void domainSpecificKeyEvent(); + void geolocationRequestJS(); + void loadFinished(); + void actionStates(); + void popupFormSubmission(); + void acceptNavigationRequestWithNewWindow(); + void userStyleSheet(); + void userStyleSheetFromLocalFileUrl(); + void userStyleSheetFromQrcUrl(); + void loadHtml5Video(); + void modified(); + void contextMenuCrash(); + void updatePositionDependentActionsCrash(); + void database(); + void createPluginWithPluginsEnabled(); + void createPluginWithPluginsDisabled(); + void destroyPlugin_data(); + void destroyPlugin(); + void createViewlessPlugin_data(); + void createViewlessPlugin(); + void graphicsWidgetPlugin(); + +#ifdef HAVE_QTTESTSUPPORT + void multiplePageGroupsAndLocalStorage(); +#endif + + void cursorMovements(); + void textSelection(); + void textEditing(); + void backActionUpdate(); + void frameAt(); + void requestCache(); + void loadCachedPage(); + +#ifdef HAVE_QTTESTSUPPORT + void protectBindingsRuntimeObjectsFromCollector(); +#endif + + 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 errorPageExtensionInIFrames(); + void errorPageExtensionInFrameset(); + void userAgentApplicationName(); + void userAgentNewlineStripping(); + void undoActionHaveCustomText(); + + void viewModes(); + + void crashTests_LazyInitializationOfMainFrame(); + + void screenshot_data(); + void screenshot(); + + void changeVisibilityState(); + +#if defined(ENABLE_WEBGL) && ENABLE_WEBGL + void acceleratedWebGLScreenshotWithoutView(); + void unacceleratedWebGLScreenshotWithoutView(); +#endif + + void originatingObjectInNetworkRequests(); + void networkReplyParentDidntChange(); + void destroyQNAMBeforeAbortDoesntCrash(); + void testJSPrompt(); + void showModalDialog(); + void testStopScheduledPageRefresh(); + void findText(); + void supportedContentType(); + // [Qt] tst_QWebPage::infiniteLoopJS() timeouts with DFG JIT + // https://bugs.webkit.org/show_bug.cgi?id=79040 + // void infiniteLoopJS(); + void navigatorCookieEnabled(); + void deleteQWebViewTwice(); + void renderOnRepaintRequestedShouldNotRecurse(); + void loadSignalsOrder_data(); + void loadSignalsOrder(); + void openWindowDefaultSize(); + void cssMediaTypeGlobalSetting(); + void cssMediaTypePageSetting(); + +#ifdef Q_OS_MACOS + void macCopyUnicodeToClipboard(); +#endif + +private: + QWebView* m_view { nullptr }; + QWebPage* m_page { nullptr }; + QString tmpDirPath() const + { + static QString tmpd = QDir::tempPath() + "/tst_qwebpage-" + + QDateTime::currentDateTime().toString(QLatin1String("yyyyMMddhhmmss")); + return tmpd; + } +}; + +tst_QWebPage::tst_QWebPage() +{ +} + +tst_QWebPage::~tst_QWebPage() +{ +} + +void tst_QWebPage::init() +{ + m_view = new QWebView(); + m_page = m_view->page(); +} + +void tst_QWebPage::cleanup() +{ + delete m_view; +} + +void tst_QWebPage::cleanupFiles() +{ + removeRecursive(tmpDirPath()); +} + +void tst_QWebPage::initTestCase() +{ + cleanupFiles(); // In case there are old files from previous runs +} + +void tst_QWebPage::cleanupTestCase() +{ + cleanupFiles(); // Be nice +} + +class NavigationRequestOverride : public QWebPage +{ +public: + NavigationRequestOverride(QWebView* parent, bool initialValue) : QWebPage(parent), m_acceptNavigationRequest(initialValue) {} + + bool m_acceptNavigationRequest; +protected: + virtual bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest &request, QWebPage::NavigationType type) { + Q_UNUSED(frame); + Q_UNUSED(request); + Q_UNUSED(type); + + return m_acceptNavigationRequest; + } +}; + +void tst_QWebPage::acceptNavigationRequest() +{ + QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool))); + + NavigationRequestOverride* newPage = new NavigationRequestOverride(m_view, false); + m_view->setPage(newPage); + + m_view->setHtml(QString("<html><body><form name='tstform' action='data:text/html,foo'method='get'>" + "<input type='text'><input type='submit'></form></body></html>"), QUrl()); + QTRY_COMPARE(loadSpy.count(), 1); + + m_view->page()->mainFrame()->evaluateJavaScript("tstform.submit();"); + + newPage->m_acceptNavigationRequest = true; + m_view->page()->mainFrame()->evaluateJavaScript("tstform.submit();"); + QTRY_COMPARE(loadSpy.count(), 2); + + QCOMPARE(m_view->page()->mainFrame()->toPlainText(), QString("foo?")); + + // Restore default page + m_view->setPage(0); +} + +void tst_QWebPage::domainSpecificKeyEvent() +{ + QWebView webView; + webView.show(); + QTest::qWaitForWindowExposed(&webView); + + webView.setHtml(QLatin1String("<html><head>" + "<script>" + "var receivedKeyArray = new Array();" + "function keyEvent(e) {" + "receivedKeyArray.push(e.type+':'+e.keyCode+':'+e.charCode);" + "};" + "window.onkeyup = keyEvent; window.onkeypress = keyEvent; window.onkeydown = keyEvent;" + "</script></head><body>test</body></html>")); + + // Enable settings to use nativeVirtualKey as DOM key value. + webView.page()->setProperty("_q_useNativeVirtualKeyAsDOMKey", true); + // Simulate domain specific keyevent to WebKit by passing it as nativeVirtualKey in QKeyEvent. + // Qt::Key_Pause --> 0x51 + QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_Pause, Qt::NoModifier, 0, 81, 0); + QApplication::sendEvent(&webView, &keyEvent); + keyEvent = QKeyEvent(QEvent::KeyRelease, Qt::Key_Pause, Qt::NoModifier, 0, 81, 0); + QApplication::sendEvent(&webView, &keyEvent); + const QLatin1String expectedReceivedKeyArray1("keydown:81:0,keyup:81:0"); + QString receivedKeyArray = webView.page()->mainFrame()->evaluateJavaScript(QLatin1String("receivedKeyArray")).toStringList().join(","); + QVERIFY(receivedKeyArray == expectedReceivedKeyArray1); + + // Normal PC keyboard key converstion flow shouldn't be affected when sending nativeVirtual key as 0. + // Qt::Key_Pause --> VK_PAUSE(0x13) + webView.page()->mainFrame()->evaluateJavaScript(QLatin1String("receivedKeyArray = new Array()")); // Reset + keyEvent = QKeyEvent(QEvent::KeyPress, Qt::Key_Pause, Qt::NoModifier, 0, 0, 0); + QApplication::sendEvent(&webView, &keyEvent); + keyEvent = QKeyEvent(QEvent::KeyRelease, Qt::Key_Pause, Qt::NoModifier, 0, 0, 0); + QApplication::sendEvent(&webView, &keyEvent); + const QLatin1String expectedReceivedKeyArray2("keydown:19:0,keyup:19:0"); + receivedKeyArray = webView.page()->mainFrame()->evaluateJavaScript(QLatin1String("receivedKeyArray")).toStringList().join(","); + QVERIFY(receivedKeyArray == expectedReceivedKeyArray2); + + // Negative case. + // Disable settings to use nativeVirtualKey as DOM key value. + webView.page()->setProperty("_q_useNativeVirtualKeyAsDOMKey", false); + // Qt::Key_Pause --> VK_PAUSE(0x13) + webView.page()->mainFrame()->evaluateJavaScript(QLatin1String("receivedKeyArray = new Array()")); // Reset + keyEvent = QKeyEvent(QEvent::KeyPress, Qt::Key_Pause, Qt::NoModifier, 0, 81, 0); + QApplication::sendEvent(&webView, &keyEvent); + keyEvent = QKeyEvent(QEvent::KeyRelease, Qt::Key_Pause, Qt::NoModifier, 0, 81, 0); + QApplication::sendEvent(&webView, &keyEvent); + const QLatin1String expectedReceivedKeyArray3("keydown:19:0,keyup:19:0"); + receivedKeyArray = webView.page()->mainFrame()->evaluateJavaScript(QLatin1String("receivedKeyArray")).toStringList().join(","); + QVERIFY(receivedKeyArray == expectedReceivedKeyArray3); +} + +class JSTestPage : public QWebPage +{ +Q_OBJECT +public: + JSTestPage(QObject* parent = 0) + : QWebPage(parent) {} + + virtual bool shouldInterruptJavaScript() + { + return true; + } +public Q_SLOTS: + void requestPermission(QWebFrame* frame, QWebPage::Feature feature) + { + if (m_allowGeolocation) + setFeaturePermission(frame, feature, PermissionGrantedByUser); + else + setFeaturePermission(frame, feature, PermissionDeniedByUser); + } + +public: + void setGeolocationPermission(bool allow) + { + m_allowGeolocation = allow; + } + +private: + bool m_allowGeolocation { false }; +}; + +// [Qt] tst_QWebPage::infiniteLoopJS() timeouts with DFG JIT +// https://bugs.webkit.org/show_bug.cgi?id=79040 +/* +void tst_QWebPage::infiniteLoopJS() +{ + JSTestPage* newPage = new JSTestPage(m_view); + m_view->setPage(newPage); + m_view->setHtml(QString("<html><body>test</body></html>"), QUrl()); + m_view->page()->mainFrame()->evaluateJavaScript("var run = true;var a = 1;while(run){a++;}"); + delete newPage; +} +*/ + +void tst_QWebPage::geolocationRequestJS() +{ + JSTestPage* newPage = new JSTestPage(m_view); + + if (newPage->mainFrame()->evaluateJavaScript(QLatin1String("!navigator.geolocation")).toBool()) { + delete newPage; + QSKIP("Geolocation is not supported.", SkipSingle); + } + + connect(newPage, SIGNAL(featurePermissionRequested(QWebFrame*, QWebPage::Feature)), + newPage, SLOT(requestPermission(QWebFrame*, QWebPage::Feature))); + + newPage->setGeolocationPermission(false); + m_view->setPage(newPage); + m_view->setHtml(QString("<html><body>test</body></html>"), QUrl()); + m_view->page()->mainFrame()->evaluateJavaScript("var errorCode = 0; function error(err) { errorCode = err.code; } function success(pos) { } navigator.geolocation.getCurrentPosition(success, error)"); + QTest::qWait(2000); + QVariant empty = m_view->page()->mainFrame()->evaluateJavaScript("errorCode"); + + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=102235", Continue); + QVERIFY(empty.type() == QVariant::Double && empty.toInt() != 0); + + newPage->setGeolocationPermission(true); + m_view->page()->mainFrame()->evaluateJavaScript("errorCode = 0; navigator.geolocation.getCurrentPosition(success, error);"); + empty = m_view->page()->mainFrame()->evaluateJavaScript("errorCode"); + + //http://dev.w3.org/geo/api/spec-source.html#position + //PositionError: const unsigned short PERMISSION_DENIED = 1; + QVERIFY(empty.type() == QVariant::Double && empty.toInt() != 1); + delete newPage; +} + +void tst_QWebPage::loadFinished() +{ + qRegisterMetaType<QWebFrame*>("QWebFrame*"); + qRegisterMetaType<QNetworkRequest*>("QNetworkRequest*"); + QSignalSpy spyLoadStarted(m_view, SIGNAL(loadStarted())); + QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool))); + + m_view->page()->mainFrame()->load(QUrl("data:text/html,<frameset cols=\"25%,75%\"><frame src=\"data:text/html," + "<head><meta http-equiv='refresh' content='1'></head>foo \">" + "<frame src=\"data:text/html,bar\"></frameset>")); + QTRY_COMPARE(spyLoadFinished.count(), 1); + + QTRY_VERIFY(spyLoadStarted.count() > 1); + QTRY_VERIFY(spyLoadFinished.count() > 1); + + spyLoadFinished.clear(); + + m_view->page()->mainFrame()->load(QUrl("data:text/html,<frameset cols=\"25%,75%\"><frame src=\"data:text/html," + "foo \"><frame src=\"data:text/html,bar\"></frameset>")); + QTRY_COMPARE(spyLoadFinished.count(), 1); + QCOMPARE(spyLoadFinished.count(), 1); +} + +void tst_QWebPage::actionStates() +{ + QWebPage* page = m_view->page(); + + page->mainFrame()->load(QUrl("qrc:///resources/script.html")); + + QAction* reloadAction = page->action(QWebPage::Reload); + QAction* stopAction = page->action(QWebPage::Stop); + + QTRY_VERIFY(reloadAction->isEnabled()); + QTRY_VERIFY(!stopAction->isEnabled()); +} + +class ConsolePage : public QWebPage +{ +public: + ConsolePage(QObject* parent = 0) : QWebPage(parent) {} + + virtual void javaScriptConsoleMessage(const QString& message, int lineNumber, const QString& sourceID) + { + messages.append(message); + lineNumbers.append(lineNumber); + sourceIDs.append(sourceID); + } + + QStringList messages; + QList<int> lineNumbers; + QStringList sourceIDs; +}; + +void tst_QWebPage::consoleOutput() +{ + ConsolePage page; + page.mainFrame()->evaluateJavaScript("this is not valid JavaScript"); + QCOMPARE(page.messages.count(), 1); + QCOMPARE(page.lineNumbers.at(0), 1); +} + +class TestPage : public QWebPage { + Q_OBJECT +public: + TestPage(QObject* parent = 0) : QWebPage(parent) + { + connect(this, SIGNAL(geometryChangeRequested(QRect)), this, SLOT(slotGeometryChangeRequested(QRect))); + } + + struct Navigation { + QPointer<QWebFrame> frame; + QNetworkRequest request; + NavigationType type; + }; + + QList<Navigation> navigations; + QList<TestPage*> createdWindows; + QRect requestedGeometry; + + virtual bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest &request, NavigationType type) { + Navigation n; + n.frame = frame; + n.request = request; + n.type = type; + navigations.append(n); + return true; + } + + virtual QWebPage* createWindow(WebWindowType) { + TestPage* page = new TestPage(this); + createdWindows.append(page); + return page; + } + +private Q_SLOTS: + void slotGeometryChangeRequested(const QRect& geom) { + requestedGeometry = geom; + } +}; + +void tst_QWebPage::popupFormSubmission() +{ + TestPage page; + page.settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, true); + page.mainFrame()->setHtml("<form name=form1 method=get action='' target=myNewWin>"\ + "<input type=hidden name=foo value='bar'>"\ + "</form>"); + page.mainFrame()->evaluateJavaScript("window.open('', 'myNewWin', 'width=500,height=300,toolbar=0')"); + page.mainFrame()->evaluateJavaScript("document.form1.submit();"); + + QTest::qWait(500); + // The number of popup created should be one. + QVERIFY(page.createdWindows.size() == 1); + + QString url = page.createdWindows.takeFirst()->mainFrame()->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")); +} + +void tst_QWebPage::acceptNavigationRequestWithNewWindow() +{ + TestPage* page = new TestPage(m_view); + page->settings()->setAttribute(QWebSettings::LinksIncludedInFocusChain, true); + m_page = page; + m_view->setPage(m_page); + + m_view->setUrl(QString("data:text/html,<a href=\"data:text/html,Reached\" target=\"_blank\">Click me</a>")); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + QFocusEvent fe(QEvent::FocusIn); + m_page->event(&fe); + + QVERIFY(m_page->focusNextPrevChild(/*next*/ true)); + + QKeyEvent keyEnter(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier); + m_page->event(&keyEnter); + + QCOMPARE(page->navigations.count(), 2); + + TestPage::Navigation n = page->navigations.at(1); + QVERIFY(n.frame.isNull()); + QCOMPARE(n.request.url().toString(), QString("data:text/html,Reached")); + QVERIFY(n.type == QWebPage::NavigationTypeLinkClicked); + + QCOMPARE(page->createdWindows.count(), 1); +} + +class TestNetworkManager : public QNetworkAccessManager +{ +public: + TestNetworkManager(QObject* parent) : QNetworkAccessManager(parent) {} + + QList<QUrl> requestedUrls; + QList<QNetworkRequest> 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_QWebPage::userStyleSheet() +{ + TestNetworkManager* networkManager = new TestNetworkManager(m_page); + m_page->setNetworkAccessManager(networkManager); + + m_page->settings()->setUserStyleSheetUrl(QUrl(QString::fromLatin1("data:text/css;charset=utf-8;base64," + + QByteArray("p { background-image: url('http://does.not/exist.png');}").toBase64()))); + m_view->setHtml("<p>hello world</p>"); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + QVERIFY(networkManager->requestedUrls.count() >= 1); + QCOMPARE(networkManager->requestedUrls.at(0), QUrl("http://does.not/exist.png")); +} + +void tst_QWebPage::userStyleSheetFromLocalFileUrl() +{ + TestNetworkManager* networkManager = new TestNetworkManager(m_page); + m_page->setNetworkAccessManager(networkManager); + + QUrl styleSheetUrl = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebpage/resources/user.css")); + m_page->settings()->setUserStyleSheetUrl(styleSheetUrl); + m_view->setHtml("<p>hello world</p>"); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + QVERIFY(networkManager->requestedUrls.count() >= 1); + QCOMPARE(networkManager->requestedUrls.at(0), QUrl("http://does.not/exist.png")); +} + +void tst_QWebPage::userStyleSheetFromQrcUrl() +{ + TestNetworkManager* networkManager = new TestNetworkManager(m_page); + m_page->setNetworkAccessManager(networkManager); + + m_page->settings()->setUserStyleSheetUrl(QUrl("qrc:///resources/user.css")); + m_view->setHtml("<p>hello world</p>"); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + QVERIFY(networkManager->requestedUrls.count() >= 1); + QCOMPARE(networkManager->requestedUrls.at(0), QUrl("http://does.not/exist.png")); +} + +void tst_QWebPage::loadHtml5Video() +{ +#if defined(USE_QT_MULTIMEDIA) && USE_QT_MULTIMEDIA + QByteArray url("http://does.not/exist?a=1%2Cb=2"); + m_view->setHtml("<p><video id ='video' src='" + url + "' autoplay/></p>"); + 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 + QSKIP("This test requires Qt Multimedia", SkipAll); +#endif +} + +void tst_QWebPage::viewModes() +{ + m_view->setHtml("<body></body>"); + m_page->setProperty("_q_viewMode", "minimized"); + + QVariant empty = m_page->mainFrame()->evaluateJavaScript("window.styleMedia.matchMedium(\"(-webkit-view-mode)\")"); + QVERIFY(empty.type() == QVariant::Bool && empty.toBool()); + + QVariant minimized = m_page->mainFrame()->evaluateJavaScript("window.styleMedia.matchMedium(\"(-webkit-view-mode: minimized)\")"); + QVERIFY(minimized.type() == QVariant::Bool && minimized.toBool()); + + QVariant maximized = m_page->mainFrame()->evaluateJavaScript("window.styleMedia.matchMedium(\"(-webkit-view-mode: maximized)\")"); + QVERIFY(maximized.type() == QVariant::Bool && !maximized.toBool()); +} + +void tst_QWebPage::modified() +{ + m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>blub")); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + m_page->mainFrame()->setUrl(QUrl("data:text/html,<body id=foo contenteditable>blah")); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + QVERIFY(!m_page->isModified()); + +// m_page->mainFrame()->evaluateJavaScript("alert(document.getElementById('foo'))"); + m_page->mainFrame()->evaluateJavaScript("document.getElementById('foo').focus()"); + m_page->mainFrame()->evaluateJavaScript("document.execCommand('InsertText', true, 'Test');"); + + QVERIFY(m_page->isModified()); + + m_page->mainFrame()->evaluateJavaScript("document.execCommand('Undo', true);"); + + QVERIFY(!m_page->isModified()); + + m_page->mainFrame()->evaluateJavaScript("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()); + + QVERIFY(m_page->history()->currentItemIndex() == 0); + + m_page->history()->setMaximumItemCount(3); + QVERIFY(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->mainFrame()->setUrl(QUrl("data:text/html,<body>This is second page")); + m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>This is third page")); + QVERIFY(m_page->history()->count() == 2); + m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>This is fourth page")); + QVERIFY(m_page->history()->count() == 2); + m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>This is fifth page")); + QVERIFY(::waitForSignal(m_page, SIGNAL(saveFrameStateRequested(QWebFrame*,QWebHistoryItem*)))); +} + +// https://bugs.webkit.org/show_bug.cgi?id=51331 +void tst_QWebPage::updatePositionDependentActionsCrash() +{ + QWebView view; + view.setHtml("<p>test"); + QPoint pos(0, 0); + view.page()->updatePositionDependentActions(pos); + QMenu* contextMenu = 0; + foreach (QObject* child, view.children()) { + contextMenu = qobject_cast<QMenu*>(child); + if (contextMenu) + break; + } + QVERIFY(!contextMenu); +} + +// https://bugs.webkit.org/show_bug.cgi?id=20357 +void tst_QWebPage::contextMenuCrash() +{ + QWebView view; + view.setHtml("<p>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<QMenu*>(child); + if (contextMenu) + break; + } + QVERIFY(contextMenu); + delete contextMenu; +} + +void tst_QWebPage::database() +{ + QString path = tmpDirPath(); + m_page->settings()->setOfflineStoragePath(path); + QVERIFY(m_page->settings()->offlineStoragePath() == path); + + QWebSettings::setOfflineStorageDefaultQuota(1024 * 1024); + QVERIFY(QWebSettings::offlineStorageDefaultQuota() == 1024 * 1024); + + m_page->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true); + m_page->settings()->setAttribute(QWebSettings::OfflineStorageDatabaseEnabled, true); + + QString dbFileName = path + "Databases.db"; + + if (QFile::exists(dbFileName)) + QFile::remove(dbFileName); + + qRegisterMetaType<QWebFrame*>("QWebFrame*"); + QSignalSpy spy(m_page, SIGNAL(databaseQuotaExceeded(QWebFrame*,QString))); + m_view->setHtml(QString("<html><head><script>var db; db=openDatabase('testdb', '1.0', 'test database API', 50000); </script></head><body><div></div></body></html>"), QUrl("http://www.myexample.com")); + QTRY_COMPARE(spy.count(), 1); + m_page->mainFrame()->evaluateJavaScript("var db2; db2=openDatabase('testdb', '1.0', 'test database API', 50000);"); + QTRY_COMPARE(spy.count(),1); + + m_page->mainFrame()->evaluateJavaScript("localStorage.test='This is a test for local storage';"); + m_view->setHtml(QString("<html><body id='b'>text</body></html>"), QUrl("http://www.myexample.com")); + + QVariant s1 = m_page->mainFrame()->evaluateJavaScript("localStorage.test"); + QCOMPARE(s1.toString(), QString("This is a test for local storage")); + + m_page->mainFrame()->evaluateJavaScript("sessionStorage.test='This is a test for session storage';"); + m_view->setHtml(QString("<html><body id='b'>text</body></html>"), QUrl("http://www.myexample.com")); + QVariant s2 = m_page->mainFrame()->evaluateJavaScript("sessionStorage.test"); + QCOMPARE(s2.toString(), QString("This is a test for session storage")); + + m_view->setHtml(QString("<html><head></head><body><div></div></body></html>"), QUrl("http://www.myexample.com")); + m_page->mainFrame()->evaluateJavaScript("var db3; db3=openDatabase('testdb', '1.0', 'test database API', 50000);db3.transaction(function(tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS Test (text TEXT)', []); }, function(tx, result) { }, function(tx, error) { });"); + QTest::qWait(200); + + // Remove all databases. + QWebSecurityOrigin origin = m_page->mainFrame()->securityOrigin(); + QList<QWebDatabase> dbs = origin.databases(); + for (int i = 0; i < dbs.count(); i++) { + QString fileName = dbs[i].fileName(); + QVERIFY(QFile::exists(fileName)); + QWebDatabase::removeDatabase(dbs[i]); + QVERIFY(!QFile::exists(fileName)); + } + QVERIFY(!origin.databases().size()); + // Remove removed test :-) + QWebDatabase::removeAllDatabases(); + QVERIFY(!origin.databases().size()); +} + +class PluginPage : public QWebPage +{ +public: + PluginPage(QObject *parent = 0) + : QWebPage(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<CallInfo> 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(QWebView *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("<html><body><object type='application/x-foobarbaz' classid='pushbutton' id='mybutton'/></body></html>")); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(newPage->calls.count(), 0); + + view->setHtml(QString("<html><body><object type='application/x-qt-plugin' classid='pushbutton' id='mybutton'/></body></html>")); + 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(newPage->mainFrame()->evaluateJavaScript("document.getElementById('mybutton').toString()").toString(), + QString::fromLatin1("[object HTMLObjectElement]")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mybutton.toString()").toString(), + QString::fromLatin1("[object HTMLObjectElement]")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("typeof mybutton.objectName").toString(), + QString::fromLatin1("string")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mybutton.objectName").toString(), + QString::fromLatin1("pushbutton")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("typeof mybutton.clicked").toString(), + QString::fromLatin1("function")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mybutton.clicked.toString()").toString(), + QString::fromLatin1("function clicked() {\n [native code]\n}")); + + view->setHtml(QString("<html><body><table>" + "<tr><object type='application/x-qt-plugin' classid='lineedit' id='myedit'/></tr>" + "<tr><object type='application/x-qt-plugin' classid='pushbutton' id='mybutton'/></tr>" + "</table></body></html>"), 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")); + } +} + +void tst_QWebPage::graphicsWidgetPlugin() +{ + m_view->settings()->setAttribute(QWebSettings::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("<html><body><object type='application/x-foobarbaz' classid='graphicswidget' id='mygraphicswidget'/></body></html>")); + QTRY_COMPARE(loadSpy.count(), 1); + QCOMPARE(newPage->calls.count(), 0); + + webView.setHtml(QString("<html><body><object type='application/x-qt-plugin' classid='graphicswidget' id='mygraphicswidget'/></body></html>")); + 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(newPage->mainFrame()->evaluateJavaScript("document.getElementById('mygraphicswidget').toString()").toString(), + QString::fromLatin1("[object HTMLObjectElement]")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mygraphicswidget.toString()").toString(), + QString::fromLatin1("[object HTMLObjectElement]")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("typeof mygraphicswidget.objectName").toString(), + QString::fromLatin1("string")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mygraphicswidget.objectName").toString(), + QString::fromLatin1("graphicswidget")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("typeof mygraphicswidget.geometryChanged").toString(), + QString::fromLatin1("function")); + QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mygraphicswidget.geometryChanged.toString()").toString(), + QString::fromLatin1("function geometryChanged() {\n [native code]\n}")); +} + +void tst_QWebPage::createPluginWithPluginsEnabled() +{ + m_view->settings()->setAttribute(QWebSettings::PluginsEnabled, true); + createPlugin(m_view); +} + +void tst_QWebPage::createPluginWithPluginsDisabled() +{ + // Qt Plugins should be loaded by QtWebKit 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(QWebSettings::PluginsEnabled, false); + createPlugin(m_view); +} + +// Standard base class for template PluginTracerPage. In tests it is used as interface. +class PluginCounterPage : public QWebPage { +public: + int m_count; + QPointer<QObject> m_widget; + QObject* m_pluginParent; + PluginCounterPage(QObject* parent = 0) + : QWebPage(parent) + , m_count(0) + , m_pluginParent(0) + { + settings()->setAttribute(QWebSettings::PluginsEnabled, true); + } + ~PluginCounterPage() + { + if (m_pluginParent) + m_pluginParent->deleteLater(); + } +}; + +template<class T> +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 necesary 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<T*>(m_widget.data())->setParent(static_cast<T*>(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<QWidget>(parent); + break; + case QGraphicsWidgetType: + result = new PluginTracerPage<QGraphicsWidget>(parent); + break; + default: {/*Oops*/}; + } + return result; + } + + static void prepareTestData() + { + QTest::addColumn<int>("type"); + QTest::newRow("QWidget") << (int)PluginFactory::QWidgetType; + QTest::newRow("QGraphicsWidget") << (int)PluginFactory::QGraphicsWidgetType; + } +}; + +void tst_QWebPage::destroyPlugin_data() +{ + PluginFactory::prepareTestData(); +} + +void tst_QWebPage::destroyPlugin() +{ + 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("<html><body><object type=\"application/x-qt-plugin\" classid=\"QProgressBar\"></object></body></html>"); + m_view->setHtml(content); + QVERIFY(page->m_widget); + QCOMPARE(page->m_count, 1); + + // navigate away, the plugin widget should be destructed + m_view->setHtml("<html><body>Hi</body></html>"); + QTestEventLoop::instance().enterLoop(1); + QVERIFY(!page->m_widget); +} + +void tst_QWebPage::createViewlessPlugin_data() +{ + PluginFactory::prepareTestData(); +} + +void tst_QWebPage::createViewlessPlugin() +{ + QFETCH(int, type); + PluginCounterPage* page = PluginFactory::create((PluginFactory::FactoredType)type); + QString content("<html><body><object type=\"application/x-qt-plugin\" classid=\"QProgressBar\"></object></body></html>"); + page->mainFrame()->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; + +} + +#ifdef HAVE_QTTESTSUPPORT +void tst_QWebPage::multiplePageGroupsAndLocalStorage() +{ + QDir dir(tmpDirPath()); + dir.mkdir("path1"); + dir.mkdir("path2"); + + QWebView view1; + QWebView view2; + + view1.page()->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true); + view1.page()->settings()->setLocalStoragePath(QDir::toNativeSeparators(tmpDirPath() + "/path1")); + DumpRenderTreeSupportQt::webPageSetGroupName(view1.page()->handle(), "group1"); + view2.page()->settings()->setAttribute(QWebSettings::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("<html><body> </body></html>"), QUrl("http://www.myexample.com")); + view2.setHtml(QString("<html><body> </body></html>"), QUrl("http://www.myexample.com")); + + view1.page()->mainFrame()->evaluateJavaScript("localStorage.test='value1';"); + view2.page()->mainFrame()->evaluateJavaScript("localStorage.test='value2';"); + + view1.setHtml(QString("<html><body> </body></html>"), QUrl("http://www.myexample.com")); + view2.setHtml(QString("<html><body> </body></html>"), QUrl("http://www.myexample.com")); + + QVariant s1 = view1.page()->mainFrame()->evaluateJavaScript("localStorage.test"); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(s1.toString(), QString("value1")); + + QVariant s2 = view2.page()->mainFrame()->evaluateJavaScript("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 QWebPage +{ +public: + + CursorTrackedPage(QWidget *parent = 0): QWebPage(parent) { + setViewportSize(QSize(1024, 768)); // big space + } + + QString selectedText() { + return mainFrame()->evaluateJavaScript("window.getSelection().toString()").toString(); + } + + int selectionStartOffset() { + return mainFrame()->evaluateJavaScript("window.getSelection().getRangeAt(0).startOffset").toInt(); + } + + int selectionEndOffset() { + return mainFrame()->evaluateJavaScript("window.getSelection().getRangeAt(0).endOffset").toInt(); + } + + // true if start offset == end offset, i.e. no selected text + int isSelectionCollapsed() { + return mainFrame()->evaluateJavaScript("window.getSelection().getRangeAt(0).collapsed").toBool(); + } +}; + +void tst_QWebPage::cursorMovements() +{ + CursorTrackedPage* page = new CursorTrackedPage; + QString content("<html><body><p id=one>The quick brown fox</p><p id=two>jumps over the lazy dog</p><p>May the source<br/>be with you!</p></body></html>"); + page->mainFrame()->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);"; + page->mainFrame()->evaluateJavaScript(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("<p id=\"one\">The quick brown fox</p>")); + + // these actions must exist + QVERIFY(page->action(QWebPage::MoveToNextChar) != 0); + QVERIFY(page->action(QWebPage::MoveToPreviousChar) != 0); + QVERIFY(page->action(QWebPage::MoveToNextWord) != 0); + QVERIFY(page->action(QWebPage::MoveToPreviousWord) != 0); + QVERIFY(page->action(QWebPage::MoveToNextLine) != 0); + QVERIFY(page->action(QWebPage::MoveToPreviousLine) != 0); + QVERIFY(page->action(QWebPage::MoveToStartOfLine) != 0); + QVERIFY(page->action(QWebPage::MoveToEndOfLine) != 0); + QVERIFY(page->action(QWebPage::MoveToStartOfBlock) != 0); + QVERIFY(page->action(QWebPage::MoveToEndOfBlock) != 0); + QVERIFY(page->action(QWebPage::MoveToStartOfDocument) != 0); + QVERIFY(page->action(QWebPage::MoveToEndOfDocument) != 0); + + // right now they are disabled because contentEditable is false + QCOMPARE(page->action(QWebPage::MoveToNextChar)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToPreviousChar)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToNextWord)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToPreviousWord)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToNextLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToPreviousLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToStartOfLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToEndOfLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToStartOfBlock)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToEndOfBlock)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::MoveToStartOfDocument)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::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(QWebPage::MoveToNextChar)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToPreviousChar)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToNextWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToPreviousWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToNextLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToPreviousLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToStartOfLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToEndOfLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToStartOfBlock)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToEndOfBlock)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToStartOfDocument)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::MoveToEndOfDocument)->isEnabled(), true); + + // cursor will be before the word "jump" + page->triggerAction(QWebPage::MoveToNextChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + + // cursor will be between 'j' and 'u' in the word "jump" + page->triggerAction(QWebPage::MoveToNextChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 1); + + // cursor will be between 'u' and 'm' in the word "jump" + page->triggerAction(QWebPage::MoveToNextChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 2); + + // cursor will be after the word "jump" + page->triggerAction(QWebPage::MoveToNextWord); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 5); + + // cursor will be after the word "lazy" + page->triggerAction(QWebPage::MoveToNextWord); + page->triggerAction(QWebPage::MoveToNextWord); + page->triggerAction(QWebPage::MoveToNextWord); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 19); + + // cursor will be between 'z' and 'y' in "lazy" + page->triggerAction(QWebPage::MoveToPreviousChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 18); + + // cursor will be between 'a' and 'z' in "lazy" + page->triggerAction(QWebPage::MoveToPreviousChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 17); + + // cursor will be before the word "lazy" + page->triggerAction(QWebPage::MoveToPreviousWord); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 15); + + // cursor will be before the word "quick" + page->triggerAction(QWebPage::MoveToPreviousWord); + page->triggerAction(QWebPage::MoveToPreviousWord); + page->triggerAction(QWebPage::MoveToPreviousWord); + page->triggerAction(QWebPage::MoveToPreviousWord); + page->triggerAction(QWebPage::MoveToPreviousWord); + page->triggerAction(QWebPage::MoveToPreviousWord); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 4); + + // cursor will be between 'p' and 's' in the word "jumps" + page->triggerAction(QWebPage::MoveToNextWord); + page->triggerAction(QWebPage::MoveToNextWord); + page->triggerAction(QWebPage::MoveToNextWord); + page->triggerAction(QWebPage::MoveToNextChar); + page->triggerAction(QWebPage::MoveToNextChar); + page->triggerAction(QWebPage::MoveToNextChar); + page->triggerAction(QWebPage::MoveToNextChar); + page->triggerAction(QWebPage::MoveToNextChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 4); + + // cursor will be before the word "jumps" + page->triggerAction(QWebPage::MoveToStartOfLine); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + + // cursor will be after the word "dog" + page->triggerAction(QWebPage::MoveToEndOfLine); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 23); + + // cursor will be between 'w' and 'n' in "brown" + page->triggerAction(QWebPage::MoveToStartOfLine); + page->triggerAction(QWebPage::MoveToPreviousWord); + page->triggerAction(QWebPage::MoveToPreviousWord); + page->triggerAction(QWebPage::MoveToNextChar); + page->triggerAction(QWebPage::MoveToNextChar); + page->triggerAction(QWebPage::MoveToNextChar); + page->triggerAction(QWebPage::MoveToNextChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 14); + + // cursor will be after the word "fox" + page->triggerAction(QWebPage::MoveToEndOfLine); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 19); + + // cursor will be before the word "The" + page->triggerAction(QWebPage::MoveToStartOfDocument); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + + // cursor will be after the word "you!" + page->triggerAction(QWebPage::MoveToEndOfDocument); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 12); + + // cursor will be before the word "be" + page->triggerAction(QWebPage::MoveToStartOfBlock); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + + // cursor will be after the word "you!" + page->triggerAction(QWebPage::MoveToEndOfBlock); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 12); + + // try to move before the document start + page->triggerAction(QWebPage::MoveToStartOfDocument); + page->triggerAction(QWebPage::MoveToPreviousChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + page->triggerAction(QWebPage::MoveToStartOfDocument); + page->triggerAction(QWebPage::MoveToPreviousWord); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + + // try to move past the document end + page->triggerAction(QWebPage::MoveToEndOfDocument); + page->triggerAction(QWebPage::MoveToNextChar); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 12); + page->triggerAction(QWebPage::MoveToEndOfDocument); + page->triggerAction(QWebPage::MoveToNextWord); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 12); + + delete page; +} + +void tst_QWebPage::textSelection() +{ + CursorTrackedPage* page = new CursorTrackedPage; + QString content("<html><body><p id=one>The quick brown fox</p>" \ + "<p id=two>jumps over the lazy dog</p>" \ + "<p>May the source<br/>be with you!</p></body></html>"); + page->mainFrame()->setHtml(content); + + // these actions must exist + QVERIFY(page->action(QWebPage::SelectAll) != 0); + QVERIFY(page->action(QWebPage::SelectNextChar) != 0); + QVERIFY(page->action(QWebPage::SelectPreviousChar) != 0); + QVERIFY(page->action(QWebPage::SelectNextWord) != 0); + QVERIFY(page->action(QWebPage::SelectPreviousWord) != 0); + QVERIFY(page->action(QWebPage::SelectNextLine) != 0); + QVERIFY(page->action(QWebPage::SelectPreviousLine) != 0); + QVERIFY(page->action(QWebPage::SelectStartOfLine) != 0); + QVERIFY(page->action(QWebPage::SelectEndOfLine) != 0); + QVERIFY(page->action(QWebPage::SelectStartOfBlock) != 0); + QVERIFY(page->action(QWebPage::SelectEndOfBlock) != 0); + QVERIFY(page->action(QWebPage::SelectStartOfDocument) != 0); + QVERIFY(page->action(QWebPage::SelectEndOfDocument) != 0); + + // right now they are disabled because contentEditable is false and + // there isn't an existing selection to modify + QCOMPARE(page->action(QWebPage::SelectNextChar)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectPreviousChar)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectNextWord)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectPreviousWord)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectNextLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectPreviousLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectStartOfLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectEndOfLine)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectStartOfBlock)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectEndOfBlock)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectStartOfDocument)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SelectEndOfDocument)->isEnabled(), false); + + // ..but SelectAll is awalys enabled + QCOMPARE(page->action(QWebPage::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);"; + page->mainFrame()->evaluateJavaScript(selectScript); + QCOMPARE(page->selectedText().trimmed(), QString::fromLatin1("The quick brown fox")); + QRegExp regExp(" style=\".*\""); + regExp.setMinimal(true); + QCOMPARE(page->selectedHtml().trimmed().replace(regExp, ""), QString::fromLatin1("<p id=\"one\">The quick brown fox</p>")); + + // Make sure hasSelection returns true, since there is selected text now... + QCOMPARE(page->hasSelection(), true); + + // here the actions are enabled after a selection has been created + QCOMPARE(page->action(QWebPage::SelectNextChar)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectPreviousChar)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectNextWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectPreviousWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectNextLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectPreviousLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectStartOfLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectEndOfLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectStartOfBlock)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectEndOfBlock)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectStartOfDocument)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::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(QWebPage::MoveToStartOfDocument); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + + // here the actions are enabled after contentEditable is true + QCOMPARE(page->action(QWebPage::SelectNextChar)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectPreviousChar)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectNextWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectPreviousWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectNextLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectPreviousLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectStartOfLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectEndOfLine)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectStartOfBlock)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectEndOfBlock)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectStartOfDocument)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SelectEndOfDocument)->isEnabled(), true); + + delete page; +} + +void tst_QWebPage::textEditing() +{ + CursorTrackedPage* page = new CursorTrackedPage; + QString content("<html><body><p id=one>The quick brown fox</p>" \ + "<p id=two>jumps over the lazy dog</p>" \ + "<p>May the source<br/>be with you!</p></body></html>"); + page->mainFrame()->setHtml(content); + + // these actions must exist + QVERIFY(page->action(QWebPage::Cut) != 0); + QVERIFY(page->action(QWebPage::Copy) != 0); + QVERIFY(page->action(QWebPage::Paste) != 0); + QVERIFY(page->action(QWebPage::DeleteStartOfWord) != 0); + QVERIFY(page->action(QWebPage::DeleteEndOfWord) != 0); + QVERIFY(page->action(QWebPage::SetTextDirectionDefault) != 0); + QVERIFY(page->action(QWebPage::SetTextDirectionLeftToRight) != 0); + QVERIFY(page->action(QWebPage::SetTextDirectionRightToLeft) != 0); + QVERIFY(page->action(QWebPage::ToggleBold) != 0); + QVERIFY(page->action(QWebPage::ToggleItalic) != 0); + QVERIFY(page->action(QWebPage::ToggleUnderline) != 0); + QVERIFY(page->action(QWebPage::InsertParagraphSeparator) != 0); + QVERIFY(page->action(QWebPage::InsertLineSeparator) != 0); + QVERIFY(page->action(QWebPage::PasteAndMatchStyle) != 0); + QVERIFY(page->action(QWebPage::RemoveFormat) != 0); + QVERIFY(page->action(QWebPage::ToggleStrikethrough) != 0); + QVERIFY(page->action(QWebPage::ToggleSubscript) != 0); + QVERIFY(page->action(QWebPage::ToggleSuperscript) != 0); + QVERIFY(page->action(QWebPage::InsertUnorderedList) != 0); + QVERIFY(page->action(QWebPage::InsertOrderedList) != 0); + QVERIFY(page->action(QWebPage::Indent) != 0); + QVERIFY(page->action(QWebPage::Outdent) != 0); + QVERIFY(page->action(QWebPage::AlignCenter) != 0); + QVERIFY(page->action(QWebPage::AlignJustified) != 0); + QVERIFY(page->action(QWebPage::AlignLeft) != 0); + QVERIFY(page->action(QWebPage::AlignRight) != 0); + + // right now they are disabled because contentEditable is false + QCOMPARE(page->action(QWebPage::Cut)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::Paste)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::DeleteStartOfWord)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::DeleteEndOfWord)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SetTextDirectionDefault)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SetTextDirectionLeftToRight)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::SetTextDirectionRightToLeft)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::ToggleBold)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::ToggleItalic)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::ToggleUnderline)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::InsertParagraphSeparator)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::InsertLineSeparator)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::PasteAndMatchStyle)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::RemoveFormat)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::ToggleStrikethrough)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::ToggleSubscript)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::ToggleSuperscript)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::InsertUnorderedList)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::InsertOrderedList)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::Indent)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::Outdent)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::AlignCenter)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::AlignJustified)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::AlignLeft)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::AlignRight)->isEnabled(), false); + + // Select everything + page->triggerAction(QWebPage::SelectAll); + + // make sure it is enabled since there is a selection + QCOMPARE(page->action(QWebPage::Copy)->isEnabled(), true); + + // make it editable before navigating the cursor + page->setContentEditable(true); + + // clear the selection + page->triggerAction(QWebPage::MoveToStartOfDocument); + QVERIFY(page->isSelectionCollapsed()); + QCOMPARE(page->selectionStartOffset(), 0); + + // make sure it is disabled since there isn't a selection + QCOMPARE(page->action(QWebPage::Copy)->isEnabled(), false); + + // here the actions are enabled after contentEditable is true + QCOMPARE(page->action(QWebPage::Paste)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::DeleteStartOfWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::DeleteEndOfWord)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SetTextDirectionDefault)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SetTextDirectionLeftToRight)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::SetTextDirectionRightToLeft)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::ToggleBold)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::ToggleItalic)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::ToggleUnderline)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::InsertParagraphSeparator)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::InsertLineSeparator)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::PasteAndMatchStyle)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::ToggleStrikethrough)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::ToggleSubscript)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::ToggleSuperscript)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::InsertUnorderedList)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::InsertOrderedList)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::Indent)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::Outdent)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::AlignCenter)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::AlignJustified)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::AlignLeft)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::AlignRight)->isEnabled(), true); + + // make sure these are disabled since there isn't a selection + QCOMPARE(page->action(QWebPage::Cut)->isEnabled(), false); + QCOMPARE(page->action(QWebPage::RemoveFormat)->isEnabled(), false); + + // make sure everything is selected + page->triggerAction(QWebPage::SelectAll); + + // this is only true if there is an editable selection + QCOMPARE(page->action(QWebPage::Cut)->isEnabled(), true); + QCOMPARE(page->action(QWebPage::RemoveFormat)->isEnabled(), true); + + delete page; +} + +void tst_QWebPage::requestCache() +{ + TestPage page; + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + + page.mainFrame()->setUrl(QString("data:text/html,<a href=\"data:text/html,Reached\" target=\"_blank\">Click me</a>")); + QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(page.navigations.count(), 1); + + page.mainFrame()->setUrl(QString("data:text/html,<a href=\"data:text/html,Reached\" target=\"_blank\">Click me2</a>")); + QTRY_COMPARE(loadSpy.count(), 2); + QTRY_COMPARE(page.navigations.count(), 2); + + page.triggerAction(QWebPage::Stop); + QVERIFY(page.history()->canGoBack()); + page.triggerAction(QWebPage::Back); + + QTRY_COMPARE(loadSpy.count(), 3); + QTRY_COMPARE(page.navigations.count(), 3); + QCOMPARE(page.navigations.at(0).request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(), + (int)QNetworkRequest::PreferNetwork); + QCOMPARE(page.navigations.at(1).request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(), + (int)QNetworkRequest::PreferNetwork); + QCOMPARE(page.navigations.at(2).request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(), + (int)QNetworkRequest::PreferCache); +} + +void tst_QWebPage::loadCachedPage() +{ + TestPage page; + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + page.settings()->setMaximumPagesInCache(3); + + page.mainFrame()->load(QUrl("data:text/html,This is first page")); + + QTRY_COMPARE(loadSpy.count(), 1); + QTRY_COMPARE(page.navigations.count(), 1); + + QUrl firstPageUrl = page.mainFrame()->url(); + page.mainFrame()->load(QUrl("data:text/html,This is second page")); + + QTRY_COMPARE(loadSpy.count(), 2); + QTRY_COMPARE(page.navigations.count(), 2); + + page.triggerAction(QWebPage::Stop); + QVERIFY(page.history()->canGoBack()); + + QSignalSpy urlSpy(page.mainFrame(), SIGNAL(urlChanged(QUrl))); + QVERIFY(urlSpy.isValid()); + + page.triggerAction(QWebPage::Back); + ::waitForSignal(page.mainFrame(), SIGNAL(urlChanged(QUrl))); + QCOMPARE(urlSpy.size(), 1); + + QList<QVariant> arguments1 = urlSpy.takeFirst(); + QCOMPARE(arguments1.at(0).toUrl(), firstPageUrl); + +} +void tst_QWebPage::backActionUpdate() +{ + QWebView view; + QWebPage *page = view.page(); + QAction *action = page->action(QWebPage::Back); + QVERIFY(!action->isEnabled()); + QSignalSpy loadSpy(page, SIGNAL(loadFinished(bool))); + QUrl url = QUrl("qrc:///resources/framedindex.html"); + page->mainFrame()->load(url); + QTRY_COMPARE(loadSpy.count(), 1); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QVERIFY(!action->isEnabled()); + QTest::mouseClick(&view, Qt::LeftButton, 0, QPoint(10, 10)); + QTRY_COMPARE(loadSpy.count(), 2); + + QVERIFY(action->isEnabled()); +} + +void frameAtHelper(QWebPage* webPage, QWebFrame* webFrame, QPoint framePosition) +{ + if (!webFrame) + return; + + framePosition += QPoint(webFrame->pos()); + QList<QWebFrame*> children = webFrame->childFrames(); + for (int i = 0; i < children.size(); ++i) { + if (children.at(i)->childFrames().size() > 0) + frameAtHelper(webPage, children.at(i), framePosition); + + QRect frameRect(children.at(i)->pos() + framePosition, children.at(i)->geometry().size()); + QVERIFY(children.at(i) == webPage->frameAt(frameRect.topLeft())); + } +} + +void tst_QWebPage::frameAt() +{ + QWebView webView; + QWebPage* webPage = webView.page(); + QSignalSpy loadSpy(webPage, SIGNAL(loadFinished(bool))); + QUrl url = QUrl("qrc:///resources/iframe.html"); + webPage->mainFrame()->load(url); + QTRY_COMPARE(loadSpy.count(), 1); + frameAtHelper(webPage, webPage->mainFrame(), webPage->mainFrame()->pos()); +} + +void tst_QWebPage::inputMethods_data() +{ + QTest::addColumn<QString>("viewType"); + QTest::newRow("QWebView") << "QWebView"; + QTest::newRow("QGraphicsWebView") << "QGraphicsWebView"; +} + +static Qt::InputMethodHints inputMethodHints(QObject* object) +{ + if (QGraphicsObject* o = qobject_cast<QGraphicsObject*>(object)) + return o->inputMethodHints(); + if (QWidget* w = qobject_cast<QWidget*>(object)) + return w->inputMethodHints(); + return Qt::InputMethodHints(); +} + +static bool inputMethodEnabled(QObject* object) +{ + if (QGraphicsObject* o = qobject_cast<QGraphicsObject*>(object)) + return o->flags() & QGraphicsItem::ItemAcceptsInputMethod; + if (QWidget* w = qobject_cast<QWidget*>(object)) + return w->testAttribute(Qt::WA_InputMethodEnabled); + return false; +} + +static void clickOnPage(QWebPage* 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); +} + +void tst_QWebPage::inputMethods() +{ + QFETCH(QString, viewType); + QWebPage* page = new QWebPage; + QObject* view = 0; + QObject* container = 0; + if (viewType == "QWebView") { + QWebView* wv = new QWebView; + 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(QWebSettings::SerifFont, page->settings()->fontFamily(QWebSettings::FixedFont)); + page->mainFrame()->setHtml("<html><body>" \ + "<input type='text' id='input1' style='font-family: serif' value='' maxlength='20'/><br>" \ + "<input type='password'/>" \ + "</body></html>"); + page->mainFrame()->setFocus(); + + TestInputContext testContext; + + QWebElementCollection 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 == "QWebView") { + if (QWebView* wv = qobject_cast<QWebView*>(view)) + inputPanel = wv->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel); + } else if (viewType == "QGraphicsWebView") { + if (QGraphicsWebView* wv = qobject_cast<QGraphicsWebView*>(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<QFont>(); + QCOMPARE(page->settings()->fontFamily(QWebSettings::FixedFont), font.family()); + + QList<QInputMethodEvent::Attribute> inputAttributes; + + //Insert text. + { + QInputMethodEvent eventText("QtWebKit", inputAttributes); + QSignalSpy signalSpy(page, SIGNAL(microFocusChanged())); + page->event(&eventText); + QCOMPARE(signalSpy.count(), 0); + } + + { + QInputMethodEvent eventText("", inputAttributes); + eventText.setCommitString(QString("QtWebKit"), 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<QString>(); + 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<QString>(); + QCOMPARE(selectionValue, QString("tWebK")); + + //ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + QString value = variant.value<QString>(); + QCOMPARE(value, QString("QtWebKit")); + + { + QList<QInputMethodEvent::Attribute> 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<QString>(); + QCOMPARE(value, QString("QtWebKit")); + + // 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<QInputMethodEvent::Attribute> 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<QString>(); + QCOMPARE(value, QString("QtWebKit")); + } + + //Move to the start of the line + page->triggerAction(QWebPage::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(QWebPage::SelectEndOfLine); + + //ImAnchorPosition QtWebKit + 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<QString>(); + 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<QString>(); + 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(QWebPage::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<QString>(); + 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->mainFrame()->setHtml("<html><body><p>nothing to input here"); + testContext.hideInputPanel(); + + QWebElement para = page->mainFrame()->findFirstElement("p"); + clickOnPage(page, para.geometry().center()); + + QVERIFY(!testContext.isInputPanelVisible()); + + //START - Test for sending empty QInputMethodEvent + page->mainFrame()->setHtml("<html><body>" \ + "<input type='text' id='input3' value='QtWebKit2'/>" \ + "</body></html>"); + page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input3'); inputEle.focus(); inputEle.select();"); + + //Send empty QInputMethodEvent + QInputMethodEvent emptyEvent; + page->event(&emptyEvent); + + QString inputValue = page->mainFrame()->evaluateJavaScript("document.getElementById('input3').value").toString(); + QCOMPARE(inputValue, QString("QtWebKit2")); + //END - Test for sending empty QInputMethodEvent + + page->mainFrame()->setHtml("<html><body>" \ + "<input type='text' id='input4' value='QtWebKit inputMethod'/>" \ + "</body></html>"); + page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input4'); inputEle.focus(); inputEle.select();"); + + // Clear the selection, also cancel the ongoing composition if there is one. + { + QList<QInputMethodEvent::Attribute> 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<QString>(); + QCOMPARE(selectionValue, QString("")); + + variant = page->inputMethodQuery(Qt::ImSurroundingText); + QString surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("QtWebKit 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 begining of the line. + // Send temporary text, which makes the editor has composition 'm'. + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("m", attributes); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("QtWebKit 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<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("n", attributes); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("QtWebKit 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<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("", attributes); + event.setCommitString("o"); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oQtWebKit 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<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("d", attributes); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oQtWebKit 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<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("", attributes); + event.setCommitString("e"); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oeQtWebKit 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(QWebPage::MoveToEndOfLine); + + // Send temporary text, which makes the editor has composition 't'. + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("t", attributes); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oeQtWebKit 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<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("", attributes); + event.setCommitString("t"); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oeQtWebKit 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(QWebPage::SelectPreviousWord); + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("inputMethodt")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oeQtWebKit 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<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("w", attributes); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oeQtWebKit ")); + + // 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<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("", attributes); + event.setCommitString("2"); + page->event(&event); + } + + // ImCurrentSelection + variant = page->inputMethodQuery(Qt::ImCurrentSelection); + selectionValue = variant.value<QString>(); + QCOMPARE(selectionValue, QString("")); + + // ImSurroundingText + variant = page->inputMethodQuery(Qt::ImSurroundingText); + surroundingValue = variant.value<QString>(); + QCOMPARE(surroundingValue, QString("oeQtWebKit 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->mainFrame()->setHtml("<html><body>" \ + "<input type='text' id='input5' value='QtWebKit inputMethod'/>" \ + "<div id='btnDiv' onclick='i=document.getElementById("input5"); i.focus();'>abc</div>"\ + "</body></html>"); + QWebElement inputElement = page->mainFrame()->findFirstElement("div"); + clickOnPage(page, inputElement.geometry().center()); + + QVERIFY(!testContext.isInputPanelVisible()); + + // START - Newline test for textarea + qApp->processEvents(); + page->mainFrame()->setHtml("<html><body>" \ + "<textarea rows='5' cols='1' id='input5' value=''/>" \ + "</body></html>"); + page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input5'); inputEle.focus(); inputEle.select();"); + + // Enter Key without key text + QKeyEvent keyEnter(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier); + page->event(&keyEnter); + QList<QInputMethodEvent::Attribute> attribs; + + QInputMethodEvent eventText(QString(), attribs); + eventText.setCommitString("\n"); + page->event(&eventText); + + QInputMethodEvent eventText2(QString(), attribs); + eventText2.setCommitString("third line"); + page->event(&eventText2); + qApp->processEvents(); + + QString inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("\n\nthird line")); + + // Enter Key with key text '\r' + page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input5'); inputEle.value = ''; inputEle.focus(); inputEle.select();"); + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("")); + + QKeyEvent keyEnterWithCarriageReturn(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier, "\r"); + page->event(&keyEnterWithCarriageReturn); + page->event(&eventText); + page->event(&eventText2); + qApp->processEvents(); + + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("\n\nthird line")); + + // Enter Key with key text '\n' + page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input5'); inputEle.value = ''; inputEle.focus(); inputEle.select();"); + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("")); + + QKeyEvent keyEnterWithLineFeed(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier, "\n"); + page->event(&keyEnterWithLineFeed); + page->event(&eventText); + page->event(&eventText2); + qApp->processEvents(); + + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("\n\nthird line")); + + // Enter Key with key text "\n\r" + page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input5'); inputEle.value = ''; inputEle.focus(); inputEle.select();"); + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("")); + + QKeyEvent keyEnterWithLFCR(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier, "\n\r"); + page->event(&keyEnterWithLFCR); + page->event(&eventText); + page->event(&eventText2); + qApp->processEvents(); + + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("\n\nthird line")); + + // Return Key without key text + page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input5'); inputEle.value = ''; inputEle.focus(); inputEle.select();"); + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("")); + + QKeyEvent keyReturn(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier); + page->event(&keyReturn); + page->event(&eventText); + page->event(&eventText2); + qApp->processEvents(); + + inputValue2 = page->mainFrame()->evaluateJavaScript("document.getElementById('input5').value").toString(); + QCOMPARE(inputValue2, QString("\n\nthird line")); + + // END - Newline test for textarea + + delete container; +} + +void tst_QWebPage::inputMethodsTextFormat_data() +{ + QTest::addColumn<QString>("string"); + QTest::addColumn<int>("start"); + QTest::addColumn<int>("length"); + + QTest::newRow("") << QString("") << 0 << 0; + QTest::newRow("Q") << QString("Q") << 0 << 1; + QTest::newRow("Qt") << QString("Qt") << 0 << 1; + QTest::newRow("Qt") << QString("Qt") << 0 << 2; + QTest::newRow("Qt") << QString("Qt") << 1 << 1; + QTest::newRow("Qt ") << QString("Qt ") << 0 << 1; + QTest::newRow("Qt ") << QString("Qt ") << 1 << 1; + QTest::newRow("Qt ") << QString("Qt ") << 2 << 1; + QTest::newRow("Qt ") << QString("Qt ") << 2 << -1; + QTest::newRow("Qt ") << QString("Qt ") << -2 << 3; + QTest::newRow("Qt ") << QString("Qt ") << 0 << 3; + QTest::newRow("Qt by") << QString("Qt by") << 0 << 1; + QTest::newRow("Qt by Nokia") << QString("Qt by Nokia") << 0 << 1; +} + + +void tst_QWebPage::inputMethodsTextFormat() +{ + QWebPage* page = new QWebPage; + QWebView* view = new QWebView; + view->setPage(page); + page->settings()->setFontFamily(QWebSettings::SerifFont, "FooSerifFont"); + page->mainFrame()->setHtml("<html><body>" \ + "<input type='text' id='input1' style='font-family: serif' value='' maxlength='20'/>"); + page->mainFrame()->evaluateJavaScript("document.getElementById('input1').focus()"); + page->mainFrame()->setFocus(); + view->show(); + + QFETCH(QString, string); + QFETCH(int, start); + QFETCH(int, length); + + QList<QInputMethodEvent::Attribute> attrs; + QTextCharFormat format; + format.setUnderlineStyle(QTextCharFormat::SingleUnderline); + format.setUnderlineColor(Qt::red); + attrs.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, start, length, format)); + QInputMethodEvent im(string, attrs); + page->event(&im); + + QTest::qWait(1000); + + delete view; +} + +#ifdef HAVE_QTTESTSUPPORT +void tst_QWebPage::protectBindingsRuntimeObjectsFromCollector() +{ + QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool))); + + PluginPage* newPage = new PluginPage(m_view); + m_view->setPage(newPage); + + m_view->settings()->setAttribute(QWebSettings::PluginsEnabled, true); + + m_view->setHtml(QString("<html><body><object type='application/x-qt-plugin' classid='lineedit' id='mylineedit'/></body></html>")); + QTRY_COMPARE(loadSpy.count(), 1); + + newPage->mainFrame()->evaluateJavaScript("function testme(text) { var lineedit = document.getElementById('mylineedit'); lineedit.setText(text); lineedit.selectAll(); }"); + + newPage->mainFrame()->evaluateJavaScript("testme('foo')"); + + DumpRenderTreeSupportQt::garbageCollectorCollect(); + + // don't crash! + newPage->mainFrame()->evaluateJavaScript("testme('bar')"); +} +#endif + +void tst_QWebPage::localURLSchemes() +{ + int i = QWebSecurityOrigin::localSchemes().size(); + + QWebSecurityOrigin::removeLocalScheme("file"); + QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i); + QWebSecurityOrigin::addLocalScheme("file"); + QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i); + + QWebSecurityOrigin::removeLocalScheme("qrc"); + QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i - 1); + QWebSecurityOrigin::addLocalScheme("qrc"); + QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i); + + QString myscheme = "myscheme"; + QWebSecurityOrigin::addLocalScheme(myscheme); + QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i + 1); + QVERIFY(QWebSecurityOrigin::localSchemes().contains(myscheme)); + QWebSecurityOrigin::removeLocalScheme(myscheme); + QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i); + QWebSecurityOrigin::removeLocalScheme(myscheme); + QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i); +} + +static inline bool testFlag(QWebPage& webPage, QWebSettings::WebAttribute settingAttribute, const QString& jsObjectName, bool settingValue) +{ + webPage.settings()->setAttribute(settingAttribute, settingValue); + return webPage.mainFrame()->evaluateJavaScript(QString("(window.%1 != undefined)").arg(jsObjectName)).toBool(); +} + +void tst_QWebPage::testOptionalJSObjects() +{ + // Once a feature is enabled and the JS object is accessed turning off the setting will not turn off + // the visibility of the JS object any more. For this reason this test uses two QWebPage instances. + // Part of the test is to make sure that the QWebPage instances do not interfere with each other so turning on + // a feature for one instance will not turn it on for another. + + QWebPage webPage1; + QWebPage webPage2; + + webPage1.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("http://www.example.com/")); + webPage2.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("http://www.example.com/")); + + QEXPECT_FAIL("","Feature enabled/disabled checking problem. Look at bugs.webkit.org/show_bug.cgi?id=29867", Continue); + QCOMPARE(testFlag(webPage1, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", false), false); + QCOMPARE(testFlag(webPage2, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", true), true); + QEXPECT_FAIL("","Feature enabled/disabled checking problem. Look at bugs.webkit.org/show_bug.cgi?id=29867", Continue); + QCOMPARE(testFlag(webPage1, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", false), false); + QCOMPARE(testFlag(webPage2, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", false), true); + + QCOMPARE(testFlag(webPage1, QWebSettings::LocalStorageEnabled, "localStorage", false), false); + QCOMPARE(testFlag(webPage2, QWebSettings::LocalStorageEnabled, "localStorage", true), true); + QCOMPARE(testFlag(webPage1, QWebSettings::LocalStorageEnabled, "localStorage", false), false); + QCOMPARE(testFlag(webPage2, QWebSettings::LocalStorageEnabled, "localStorage", false), true); +} + +static inline bool checkLocalStorageVisibility(QWebPage& webPage, bool localStorageEnabled) +{ + webPage.settings()->setAttribute(QWebSettings::LocalStorageEnabled, localStorageEnabled); + return webPage.mainFrame()->evaluateJavaScript(QString("(window.localStorage != undefined)")).toBool(); +} + +void tst_QWebPage::testLocalStorageVisibility() +{ + // Local storage's visibility depends on its security origin, which depends on base url. + // Initially, it will test it with base urls that get a globally unique origin, which may not + // be able to use local storage even if the feature is enabled. Then later the same test is + // done but with urls that would get a valid origin, so local storage could be used. + // Before every test case it checks if local storage is not already visible. + + QWebPage webPage; + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl()); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), false); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("invalid")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), false); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("://misparsed.com")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), false); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("http://")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), false); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("about:blank")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), false); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("data:text/html,test")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), false); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("file:///")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), true); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("http://www.example.com")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), true); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("https://www.example.com")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), true); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("ftp://files.example.com")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), true); + + webPage.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl("file:///path/to/index.html")); + + QCOMPARE(checkLocalStorageVisibility(webPage, false), false); + QCOMPARE(checkLocalStorageVisibility(webPage, true), true); +} + +void tst_QWebPage::testEnablePersistentStorage() +{ + QWebPage webPage; + + // By default all persistent options should be disabled + QCOMPARE(webPage.settings()->testAttribute(QWebSettings::LocalStorageEnabled), false); + QCOMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineStorageDatabaseEnabled), false); + QCOMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineWebApplicationCacheEnabled), false); + QVERIFY(webPage.settings()->iconDatabasePath().isEmpty()); + + QWebSettings::enablePersistentStorage(); + + + QTRY_COMPARE(webPage.settings()->testAttribute(QWebSettings::LocalStorageEnabled), true); + QTRY_COMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineStorageDatabaseEnabled), true); + QTRY_COMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineWebApplicationCacheEnabled), true); + + QTRY_VERIFY(!webPage.settings()->offlineStoragePath().isEmpty()); + QTRY_VERIFY(!webPage.settings()->offlineWebApplicationCachePath().isEmpty()); + QTRY_VERIFY(!webPage.settings()->iconDatabasePath().isEmpty()); +} + +void tst_QWebPage::defaultTextEncoding() +{ + QWebFrame* mainFrame = m_page->mainFrame(); + + QString defaultCharset = mainFrame->evaluateJavaScript("document.defaultCharset").toString(); + QVERIFY(!defaultCharset.isEmpty()); + QCOMPARE(QWebSettings::globalSettings()->defaultTextEncoding(), defaultCharset); + + m_page->settings()->setDefaultTextEncoding(QString("utf-8")); + QString charset = mainFrame->evaluateJavaScript("document.defaultCharset").toString(); + QCOMPARE(charset, QString("utf-8")); + QCOMPARE(m_page->settings()->defaultTextEncoding(), charset); + + m_page->settings()->setDefaultTextEncoding(QString()); + charset = mainFrame->evaluateJavaScript("document.defaultCharset").toString(); + QVERIFY(!charset.isEmpty()); + QCOMPARE(charset, defaultCharset); + + QWebSettings::globalSettings()->setDefaultTextEncoding(QString("utf-8")); + charset = mainFrame->evaluateJavaScript("document.defaultCharset").toString(); + QCOMPARE(charset, QString("utf-8")); + QCOMPARE(QWebSettings::globalSettings()->defaultTextEncoding(), charset); +} + +class ErrorPage : public QWebPage +{ +public: + + ErrorPage(QWidget* parent = 0): QWebPage(parent) + { + } + + virtual bool supportsExtension(Extension extension) const + { + return extension == ErrorPageExtension; + } + + virtual bool extension(Extension, const ExtensionOption* option, ExtensionReturn* output) + { + ErrorPageExtensionReturn* errorPage = static_cast<ErrorPageExtensionReturn*>(output); + + errorPage->contentType = "text/html"; + errorPage->content = "error"; + return true; + } +}; + +void tst_QWebPage::errorPageExtension() +{ + ErrorPage page; + m_view->setPage(&page); + + QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool))); + + m_view->setUrl(QUrl("data:text/html,foo")); + QTRY_COMPARE(spyLoadFinished.count(), 1); + + page.mainFrame()->setUrl(QUrl("http://non.existent/url")); + QTRY_COMPARE(spyLoadFinished.count(), 2); + QCOMPARE(page.mainFrame()->toPlainText(), QString("error")); + QCOMPARE(page.history()->count(), 2); + QCOMPARE(page.history()->currentItem().url(), QUrl("http://non.existent/url")); + QCOMPARE(page.history()->canGoBack(), true); + QCOMPARE(page.history()->canGoForward(), false); + + page.triggerAction(QWebPage::Back); + QTRY_COMPARE(page.history()->canGoBack(), false); + QTRY_COMPARE(page.history()->canGoForward(), true); + + page.triggerAction(QWebPage::Forward); + QTRY_COMPARE(page.history()->canGoBack(), true); + QTRY_COMPARE(page.history()->canGoForward(), false); + + page.triggerAction(QWebPage::Back); + QTRY_COMPARE(page.history()->canGoBack(), false); + QTRY_COMPARE(page.history()->canGoForward(), true); + QTRY_COMPARE(page.history()->currentItem().url(), QUrl("data:text/html,foo")); + + m_view->setPage(0); +} + +void tst_QWebPage::errorPageExtensionInIFrames() +{ + ErrorPage page; + m_view->setPage(&page); + + m_view->page()->mainFrame()->load(QUrl( + "data:text/html," + "<h1>h1</h1>" + "<iframe src='data:text/html,<p/>p'></iframe>" + "<iframe src='http://non.existent/url'></iframe>")); + QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool))); + QTRY_COMPARE(spyLoadFinished.count(), 1); + + QCOMPARE(page.mainFrame()->childFrames()[1]->toPlainText(), QString("error")); + + m_view->setPage(0); +} + +void tst_QWebPage::errorPageExtensionInFrameset() +{ + ErrorPage page; + m_view->setPage(&page); + + m_view->load(QUrl("qrc:///resources/index.html")); + + QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool))); + QTRY_COMPARE(spyLoadFinished.count(), 1); + QCOMPARE(page.mainFrame()->childFrames().count(), 2); + QCOMPARE(page.mainFrame()->childFrames()[1]->toPlainText(), QString("error")); + + m_view->setPage(0); +} + +class FriendlyWebPage : public QWebPage +{ +public: + friend class tst_QWebPage; +}; + +void tst_QWebPage::userAgentApplicationName() +{ + const QString oldApplicationName = QCoreApplication::applicationName(); + FriendlyWebPage page; + + const QString applicationNameMarker = QString::fromUtf8("StrangeName\342\210\236"); + QCoreApplication::setApplicationName(applicationNameMarker); + QVERIFY(page.userAgentForUrl(QUrl()).contains(applicationNameMarker)); + + QCoreApplication::setApplicationName(oldApplicationName); +} + +class CustomUserAgentWebPage : public QWebPage +{ +public: + static const QLatin1String filteredUserAgent; +protected: + virtual QString userAgentForUrl(const QUrl& url) const + { + return QString("My User Agent\nX-New-Http-Header: Oh Noes!"); + } +}; +const QLatin1String CustomUserAgentWebPage::filteredUserAgent("My User AgentX-New-Http-Header: Oh Noes!"); + +void tst_QWebPage::userAgentNewlineStripping() +{ + CustomUserAgentWebPage page; + QWebFrame* mainFrame = page.mainFrame(); + mainFrame->setHtml("<html><body></body></html>"); + QCOMPARE(mainFrame->evaluateJavaScript("navigator.userAgent").toString(), CustomUserAgentWebPage::filteredUserAgent); +} + +void tst_QWebPage::crashTests_LazyInitializationOfMainFrame() +{ + { + QWebPage webPage; + } + { + QWebPage webPage; + webPage.selectedText(); + } + { + QWebPage webPage; + webPage.selectedHtml(); + } + { + QWebPage webPage; + webPage.triggerAction(QWebPage::Back, true); + } + { + QWebPage webPage; + QPoint pos(10,10); + webPage.updatePositionDependentActions(pos); + } +} + +static void takeScreenshot(QWebPage* page) +{ + QWebFrame* mainFrame = page->mainFrame(); + page->setViewportSize(mainFrame->contentsSize()); + QImage image(page->viewportSize(), QImage::Format_ARGB32); + QPainter painter(&image); + mainFrame->render(&painter); + painter.end(); +} + +void tst_QWebPage::screenshot_data() +{ + QTest::addColumn<QString>("html"); + QTest::newRow("WithoutPlugin") << "<html><body id='b'>text</body></html>"; + QTest::newRow("WindowedPlugin") << QString("<html><body id='b'>text<embed src='resources/test.swf'></embed></body></html>"); + QTest::newRow("WindowlessPlugin") << QString("<html><body id='b'>text<embed src='resources/test.swf' wmode='transparent'></embed></body></html>"); +} + +void tst_QWebPage::screenshot() +{ + if (!QDir(TESTS_SOURCE_DIR).exists()) + QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + + QDir::setCurrent(TESTS_SOURCE_DIR); + + QFETCH(QString, html); + QWebPage* page = new QWebPage; + page->settings()->setAttribute(QWebSettings::PluginsEnabled, true); + QWebFrame* mainFrame = page->mainFrame(); + mainFrame->setHtml(html, QUrl::fromLocalFile(TESTS_SOURCE_DIR)); + ::waitForSignal(mainFrame, SIGNAL(loadFinished(bool)), 2000); + + // take screenshot without a view + takeScreenshot(page); + + QWebView* view = new QWebView; + view->setPage(page); + + // take screenshot when attached to a view + takeScreenshot(page); + + delete page; + delete view; + + QDir::setCurrent(QApplication::applicationDirPath()); +} + +#if defined(ENABLE_WEBGL) && ENABLE_WEBGL +// https://bugs.webkit.org/show_bug.cgi?id=54138 +static void webGLScreenshotWithoutView(bool accelerated) +{ + QWebPage page; + page.settings()->setAttribute(QWebSettings::WebGLEnabled, true); + page.settings()->setAttribute(QWebSettings::AcceleratedCompositingEnabled, accelerated); + QWebFrame* mainFrame = page.mainFrame(); + mainFrame->setHtml("<html><body>" + "<canvas id='webgl' width='300' height='300'></canvas>" + "<script>document.getElementById('webgl').getContext('experimental-webgl')</script>" + "</body></html>"); + + takeScreenshot(&page); +} + +void tst_QWebPage::acceleratedWebGLScreenshotWithoutView() +{ + webGLScreenshotWithoutView(true); +} + +void tst_QWebPage::unacceleratedWebGLScreenshotWithoutView() +{ + webGLScreenshotWithoutView(false); +} +#endif + +void tst_QWebPage::originatingObjectInNetworkRequests() +{ + TestNetworkManager* networkManager = new TestNetworkManager(m_page); + m_page->setNetworkAccessManager(networkManager); + networkManager->requests.clear(); + + m_view->setHtml(QString("<frameset cols=\"25%,75%\"><frame src=\"qrc:///frame_c.html\">" + "<frame src=\"qrc:///frame_b.html\"></frameset>"), QUrl()); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + QCOMPARE(networkManager->requests.count(), 2); + + QList<QWebFrame*> childFrames = m_page->mainFrame()->childFrames(); + QCOMPARE(childFrames.count(), 2); + + for (int i = 0; i < 2; ++i) + QVERIFY(qobject_cast<QWebFrame*>(networkManager->requests.at(i).originatingObject()) == childFrames.at(i)); +} + +void tst_QWebPage::networkReplyParentDidntChange() +{ + TestNetworkManager* networkManager = new TestNetworkManager(m_page); + m_page->setNetworkAccessManager(networkManager); + networkManager->requests.clear(); + + // Trigger a load and check that pending QNetworkReplies haven't been reparented before returning to the event loop. + m_view->load(QUrl("qrc:///resources/content.html")); + + QVERIFY(networkManager->requests.count() > 0); + QVERIFY(networkManager->findChildren<QNetworkReply*>().size() > 0); +} + +void tst_QWebPage::destroyQNAMBeforeAbortDoesntCrash() +{ + QNetworkAccessManager* networkManager = new QNetworkAccessManager; + m_page->setNetworkAccessManager(networkManager); + + m_view->load(QUrl("qrc:///resources/content.html")); + delete networkManager; + // This simulates what PingLoader does with its QNetworkReply when it times out. + // PingLoader isn't attached to a QWebPage and can be kept alive + // for 60000 seconds (~16.7 hours) to then cancel its ResourceHandle. + m_view->stop(); +} + +/** + * Test fixups for https://bugs.webkit.org/show_bug.cgi?id=30914 + * + * From JS we test the following conditions. + * + * OK + QString() => SUCCESS, empty string (but not null) + * OK + "text" => SUCCESS, "text" + * CANCEL + QString() => CANCEL, null string + * CANCEL + "text" => CANCEL, null string + */ +class JSPromptPage : public QWebPage { + Q_OBJECT +public: + JSPromptPage() + {} + + bool javaScriptPrompt(QWebFrame* frame, const QString& msg, const QString& defaultValue, QString* result) + { + if (msg == QLatin1String("test1")) { + *result = QString(); + return true; + } else if (msg == QLatin1String("test2")) { + *result = QLatin1String("text"); + return true; + } else if (msg == QLatin1String("test3")) { + *result = QString(); + return false; + } else if (msg == QLatin1String("test4")) { + *result = QLatin1String("text"); + return false; + } + + qFatal("Unknown msg."); + return QWebPage::javaScriptPrompt(frame, msg, defaultValue, result); + } +}; + +void tst_QWebPage::testJSPrompt() +{ + JSPromptPage page; + bool res; + + // OK + QString() + res = page.mainFrame()->evaluateJavaScript( + "var retval = prompt('test1');" + "retval=='' && retval.length == 0;").toBool(); + QVERIFY(res); + + // OK + "text" + res = page.mainFrame()->evaluateJavaScript( + "var retval = prompt('test2');" + "retval=='text' && retval.length == 4;").toBool(); + QVERIFY(res); + + // Cancel + QString() + res = page.mainFrame()->evaluateJavaScript( + "var retval = prompt('test3');" + "retval===null;").toBool(); + QVERIFY(res); + + // Cancel + "text" + res = page.mainFrame()->evaluateJavaScript( + "var retval = prompt('test4');" + "retval===null;").toBool(); + QVERIFY(res); +} + +class TestModalPage : public QWebPage +{ + Q_OBJECT +public: + TestModalPage(QObject* parent = 0) : QWebPage(parent) { + } + virtual QWebPage* createWindow(WebWindowType) { + QWebPage* page = new TestModalPage(); + connect(page, SIGNAL(windowCloseRequested()), page, SLOT(deleteLater())); + return page; + } +}; + +void tst_QWebPage::showModalDialog() +{ + TestModalPage page; + page.settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, true); + page.mainFrame()->setHtml(QString("<html></html>")); + QString res = page.mainFrame()->evaluateJavaScript("window.showModalDialog('javascript:window.returnValue=dialogArguments; window.close();', 'This is a test');").toString(); + QCOMPARE(res, QString("This is a test")); +} + +void tst_QWebPage::testStopScheduledPageRefresh() +{ + // Without QWebPage::StopScheduledPageRefresh + QWebPage page1; + page1.setNetworkAccessManager(new TestNetworkManager(&page1)); + page1.mainFrame()->setHtml("<html><head>" + "<meta http-equiv=\"refresh\"content=\"0;URL=qrc:///resources/index.html\">" + "</head><body><h1>Page redirects immediately...</h1>" + "</body></html>"); + QVERIFY(::waitForSignal(&page1, SIGNAL(loadFinished(bool)))); + QTest::qWait(500); + QCOMPARE(page1.mainFrame()->url(), QUrl(QLatin1String("qrc:///resources/index.html"))); + + // With QWebPage::StopScheduledPageRefresh + QWebPage page2; + page2.setNetworkAccessManager(new TestNetworkManager(&page2)); + page2.mainFrame()->setHtml("<html><head>" + "<meta http-equiv=\"refresh\"content=\"1;URL=qrc:///resources/index.html\">" + "</head><body><h1>Page redirect test with 1 sec timeout...</h1>" + "</body></html>"); + page2.triggerAction(QWebPage::StopScheduledPageRefresh); + QTest::qWait(1500); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118673", Continue); + QCOMPARE(page2.mainFrame()->url().toString(), QLatin1String("about:blank")); +} + +void tst_QWebPage::findText() +{ + m_view->setHtml(QString("<html><head></head><body><div>foo bar</div></body></html>")); + m_page->triggerAction(QWebPage::SelectAll); + QVERIFY(!m_page->selectedText().isEmpty()); + QVERIFY(!m_page->selectedHtml().isEmpty()); + m_page->findText(""); + QVERIFY(m_page->selectedText().isEmpty()); + QVERIFY(m_page->selectedHtml().isEmpty()); + QStringList words = (QStringList() << "foo" << "bar"); + foreach (QString subString, words) { + m_page->findText(subString, QWebPage::FindWrapsAroundDocument); + QCOMPARE(m_page->selectedText(), subString); + QVERIFY(m_page->selectedHtml().contains(subString)); + m_page->findText(""); + QVERIFY(m_page->selectedText().isEmpty()); + QVERIFY(m_page->selectedHtml().isEmpty()); + } +} + +void tst_QWebPage::supportedContentType() +{ + QStringList contentTypes; + + // Add supported non image types... + contentTypes << "text/html" << "text/xml" << "text/xsl" << "text/plain" << "text/" + << "application/xml" << "application/xhtml+xml" << "application/vnd.wap.xhtml+xml" + << "application/rss+xml" << "application/atom+xml" << "application/json" + // Add JPEG MIME type + << "image/jpeg"; + +#if ENABLE_MHTML + contentTypes << "application/x-mimearchive"; +#endif + + // Get the mime types supported by webkit... + const QStringList supportedContentTypes = m_page->supportedContentTypes(); + + Q_FOREACH(const QString& mimeType, contentTypes) + QVERIFY2(supportedContentTypes.contains(mimeType), QString("'%1' is not a supported content type!").arg(mimeType).toLatin1()); + + Q_FOREACH(const QString& mimeType, contentTypes) + QVERIFY2(m_page->supportsContentType(mimeType), QString("Cannot handle content types '%1'!").arg(mimeType).toLatin1()); +} + + +void tst_QWebPage::navigatorCookieEnabled() +{ + m_page->networkAccessManager()->setCookieJar(0); + QVERIFY(!m_page->networkAccessManager()->cookieJar()); + QVERIFY(!m_page->mainFrame()->evaluateJavaScript("navigator.cookieEnabled").toBool()); + + m_page->networkAccessManager()->setCookieJar(new QNetworkCookieJar()); + QVERIFY(m_page->networkAccessManager()->cookieJar()); + QVERIFY(m_page->mainFrame()->evaluateJavaScript("navigator.cookieEnabled").toBool()); +} + +#ifdef HAVE_QTTESTSUPPORT +void tst_QWebPage::thirdPartyCookiePolicy() +{ + QWebSettings::globalSettings()->setThirdPartyCookiePolicy(QWebSettings::AlwaysBlockThirdPartyCookies); + m_page->networkAccessManager()->setCookieJar(new QNetworkCookieJar()); + QVERIFY(m_page->networkAccessManager()->cookieJar()); + + // These are all first-party cookies, so should pass. + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://www.example.com"), QUrl("http://example.com"))); + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://www.example.com"), QUrl("http://doc.example.com"))); + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://aaa.www.example.com"), QUrl("http://doc.example.com"))); + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://example.com"), QUrl("http://www.example.com"))); + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://www.example.co.uk"), QUrl("http://example.co.uk"))); + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://www.example.co.uk"), QUrl("http://doc.example.co.uk"))); + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://aaa.www.example.co.uk"), QUrl("http://doc.example.co.uk"))); + QVERIFY(DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://example.co.uk"), QUrl("http://www.example.co.uk"))); + + // These are all third-party cookies, so should fail. + QVERIFY(!DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://www.example.com"), QUrl("http://slashdot.org"))); + QVERIFY(!DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://example.com"), QUrl("http://anotherexample.com"))); + QVERIFY(!DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://anotherexample.com"), QUrl("http://example.com"))); + QVERIFY(!DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://www.example.co.uk"), QUrl("http://slashdot.co.uk"))); + QVERIFY(!DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://example.co.uk"), QUrl("http://anotherexample.co.uk"))); + QVERIFY(!DumpRenderTreeSupportQt::thirdPartyCookiePolicyAllows(m_page->handle(), + QUrl("http://anotherexample.co.uk"), QUrl("http://example.co.uk"))); +} +#endif + +#ifdef Q_OS_MACOS +void tst_QWebPage::macCopyUnicodeToClipboard() +{ + QString unicodeText = QString::fromUtf8("αβγδεζηθικλμπ"); + m_page->mainFrame()->setHtml(QString("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /></head><body>%1</body></html>").arg(unicodeText)); + m_page->triggerAction(QWebPage::SelectAll); + m_page->triggerAction(QWebPage::Copy); + + QString clipboardData = QString::fromUtf8(QApplication::clipboard()->mimeData()->data(QLatin1String("text/html"))); + + QVERIFY(clipboardData.contains(QLatin1String("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"))); + QVERIFY(clipboardData.contains(unicodeText)); +} +#endif + +void tst_QWebPage::changeVisibilityState() +{ + QVariant stateBool, stateString, cpt; + + m_page->mainFrame()->setHtml("<html><body></body></html>"); + m_page->mainFrame()->evaluateJavaScript("var stateBool = undefined, stateString = undefined, cpt = 0; var visibilityCallBack = function () {stateBool = document['hidden']; stateString = document['visibilityState']; cpt++;};document.addEventListener('visibilitychange', visibilityCallBack, false);"); + + // The visibility state should be initialised to visible. + QCOMPARE(m_page->visibilityState(), QWebPage::VisibilityStateVisible); + stateBool = m_page->mainFrame()->evaluateJavaScript("document['hidden']"); + QVERIFY(stateBool.type() == QVariant::Bool && !stateBool.toBool()); + stateString = m_page->mainFrame()->evaluateJavaScript("document['visibilityState']"); + QVERIFY(stateString.type() == QVariant::String && stateString.toString() == QString("visible")); + + // Try to change with the same value. + m_page->setVisibilityState(QWebPage::VisibilityStateVisible); + QVERIFY(m_page->visibilityState() == QWebPage::VisibilityStateVisible); + // We check that there isn't any JS event that has been fired and + // visibility properties are still equals to visible. + stateBool = m_page->mainFrame()->evaluateJavaScript("document['hidden']"); + QVERIFY(stateBool.type() == QVariant::Bool && !stateBool.toBool()); + stateString = m_page->mainFrame()->evaluateJavaScript("document['visibilityState']"); + QVERIFY(stateString.type() == QVariant::String && stateString.toString() == QString("visible")); + cpt = m_page->mainFrame()->evaluateJavaScript("cpt"); + QVERIFY(cpt.type() == QVariant::Double && !cpt.toDouble()); + + // Try to change to with different values then check if a JS event has been fired + // and visibility properties have been updated correctly. + m_page->setVisibilityState(QWebPage::VisibilityStatePrerender); + QCOMPARE(m_page->visibilityState(), QWebPage::VisibilityStatePrerender); + stateBool = m_page->mainFrame()->evaluateJavaScript("stateBool"); + QVERIFY(stateBool.type() == QVariant::Bool && stateBool.toBool()); + stateString = m_page->mainFrame()->evaluateJavaScript("stateString"); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QVERIFY(stateString.type() == QVariant::String && stateString.toString() == QString("prerender")); + cpt = m_page->mainFrame()->evaluateJavaScript("cpt"); + QVERIFY(cpt.type() == QVariant::Double && cpt.toDouble() == 1); + + m_page->setVisibilityState(QWebPage::VisibilityStateUnloaded); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(m_page->visibilityState(), QWebPage::VisibilityStateUnloaded); + stateBool = m_page->mainFrame()->evaluateJavaScript("stateBool"); + QVERIFY(stateBool.type() == QVariant::Bool && stateBool.toBool()); + stateString = m_page->mainFrame()->evaluateJavaScript("stateString"); + QCOMPARE(stateString.type(), QVariant::String); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(stateString.toString(), QString("unloaded")); + cpt = m_page->mainFrame()->evaluateJavaScript("cpt"); + QCOMPARE(cpt.type(), QVariant::Double); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(cpt.toDouble(), 2.0); + + m_page->setVisibilityState(QWebPage::VisibilityStateVisible); + QCOMPARE(m_page->visibilityState(), QWebPage::VisibilityStateVisible); + stateBool = m_page->mainFrame()->evaluateJavaScript("stateBool"); + QVERIFY(stateBool.type() == QVariant::Bool && !stateBool.toBool()); + stateString = m_page->mainFrame()->evaluateJavaScript("stateString"); + QVERIFY(stateString.type() == QVariant::String && stateString.toString() == QString("visible")); + cpt = m_page->mainFrame()->evaluateJavaScript("cpt"); + QCOMPARE(cpt.type(), QVariant::Double); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(cpt.toDouble(), 3.0); + + m_page->setVisibilityState(QWebPage::VisibilityStateHidden); + QCOMPARE(m_page->visibilityState(), QWebPage::VisibilityStateHidden); + stateBool = m_page->mainFrame()->evaluateJavaScript("stateBool"); + QVERIFY(stateBool.type() == QVariant::Bool && stateBool.toBool()); + stateString = m_page->mainFrame()->evaluateJavaScript("stateString"); + QVERIFY(stateString.type() == QVariant::String && stateString.toString() == QString("hidden")); + cpt = m_page->mainFrame()->evaluateJavaScript("cpt"); + QCOMPARE(cpt.type(), QVariant::Double); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(cpt.toDouble(), 4.0); + + m_page->setVisibilityState(QWebPage::VisibilityStateVisible); + QCOMPARE(m_page->visibilityState(), QWebPage::VisibilityStateVisible); + stateBool = m_page->mainFrame()->evaluateJavaScript("stateBool"); + QVERIFY(stateBool.type() == QVariant::Bool && !stateBool.toBool()); + stateString = m_page->mainFrame()->evaluateJavaScript("stateString"); + QVERIFY(stateString.type() == QVariant::String && stateString.toString() == QString("visible")); + cpt = m_page->mainFrame()->evaluateJavaScript("cpt"); + QCOMPARE(cpt.type(), QVariant::Double); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(cpt.toDouble(), 5.0); +} + +void tst_QWebPage::contextMenuCopy() +{ + QWebView view; + + view.setHtml("<a href=\"http://www.google.com\">You cant miss this</a>"); + + view.page()->triggerAction(QWebPage::SelectAll); + QVERIFY(!view.page()->selectedText().isEmpty()); + + QWebElement link = view.page()->mainFrame()->findFirstElement("a"); + QPoint pos(link.geometry().center()); + QContextMenuEvent event(QContextMenuEvent::Mouse, pos); + view.page()->swallowContextMenuEvent(&event); + view.page()->updatePositionDependentActions(pos); + + QList<QMenu*> contextMenus = view.findChildren<QMenu*>(); + QVERIFY(!contextMenus.isEmpty()); + QMenu* contextMenu = contextMenus.first(); + QVERIFY(contextMenu); + + QList<QAction *> list = contextMenu->actions(); + int index = list.indexOf(view.page()->action(QWebPage::Copy)); + QVERIFY(index != -1); +} + +// https://bugs.webkit.org/show_bug.cgi?id=62139 +void tst_QWebPage::contextMenuPopulatedOnce() +{ + QWebView view; + + view.setHtml("<input type=\"text\">"); + + QWebElement link = view.page()->mainFrame()->findFirstElement("input"); + QPoint pos(link.geometry().center()); + QContextMenuEvent event(QContextMenuEvent::Mouse, pos); + view.page()->swallowContextMenuEvent(&event); + view.page()->updatePositionDependentActions(pos); + + QList<QMenu*> contextMenus = view.findChildren<QMenu*>(); + QVERIFY(!contextMenus.isEmpty()); + QMenu* contextMenu = contextMenus.first(); + QVERIFY(contextMenu); + + QList<QAction *> list = contextMenu->actions(); + QStringList entries; + while (!list.isEmpty()) { + const QAction* action = list.takeFirst(); + if (!action->isSeparator()) { + QString entry = action->text(); + QVERIFY(!entries.contains(entry)); + entries << entry; + } + } +} + +void tst_QWebPage::deleteQWebViewTwice() +{ + for (int i = 0; i < 2; ++i) { + QMainWindow mainWindow; + QWebView* webView = new QWebView(&mainWindow); + mainWindow.setCentralWidget(webView); + webView->load(QUrl("qrc:///resources/frame_a.html")); + mainWindow.show(); + QVERIFY(::waitForSignal(webView, SIGNAL(loadFinished(bool)))); + } +} + +class RepaintRequestedRenderer : public QObject { + Q_OBJECT +public: + RepaintRequestedRenderer(QWebPage* page, QPainter* painter) + : m_page(page) + , m_painter(painter) + , m_recursionCount(0) + { + connect(m_page, SIGNAL(repaintRequested(QRect)), this, SLOT(onRepaintRequested(QRect))); + } + +Q_SIGNALS: + void finished(); + +private Q_SLOTS: + void onRepaintRequested(const QRect& rect) + { + QCOMPARE(m_recursionCount, 0); + + m_recursionCount++; + m_page->mainFrame()->render(m_painter, rect); + m_recursionCount--; + + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); + } + +private: + QWebPage* m_page; + QPainter* m_painter; + int m_recursionCount; +}; + +void tst_QWebPage::renderOnRepaintRequestedShouldNotRecurse() +{ + QSize viewportSize(720, 576); + QWebPage page; + + QImage image(viewportSize, QImage::Format_ARGB32); + QPainter painter(&image); + + page.setPreferredContentsSize(viewportSize); + page.setViewportSize(viewportSize); + RepaintRequestedRenderer r(&page, &painter); + + page.mainFrame()->setHtml("zalan loves trunk", QUrl()); + + QVERIFY(::waitForSignal(&r, SIGNAL(finished()))); +} + +class SpyForLoadSignalsOrder : public QStateMachine { + Q_OBJECT +public: + SpyForLoadSignalsOrder(QWebPage* page, QObject* parent = 0) + : QStateMachine(parent) + { + connect(page, SIGNAL(loadProgress(int)), SLOT(onLoadProgress(int))); + + QState* waitingForLoadStarted = new QState(this); + QState* waitingForLastLoadProgress = new QState(this); + QState* waitingForLoadFinished = new QState(this); + QFinalState* final = new QFinalState(this); + + waitingForLoadStarted->addTransition(page, SIGNAL(loadStarted()), waitingForLastLoadProgress); + waitingForLastLoadProgress->addTransition(this, SIGNAL(lastLoadProgress()), waitingForLoadFinished); + waitingForLoadFinished->addTransition(page, SIGNAL(loadFinished(bool)), final); + + setInitialState(waitingForLoadStarted); + start(); + } + bool isFinished() const + { + return !isRunning(); + } +public Q_SLOTS: + void onLoadProgress(int progress) + { + if (progress == 100) + emit lastLoadProgress(); + } +Q_SIGNALS: + void lastLoadProgress(); +}; + +void tst_QWebPage::loadSignalsOrder_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::newRow("inline data") << QUrl("data:text/html,This is first page"); + QTest::newRow("simple page") << QUrl("qrc:///resources/content.html"); + QTest::newRow("frameset page") << QUrl("qrc:///resources/index.html"); +} + +void tst_QWebPage::loadSignalsOrder() +{ + QFETCH(QUrl, url); + QWebPage page; + SpyForLoadSignalsOrder loadSpy(&page); + waitForSignal(&loadSpy, SIGNAL(started())); + page.mainFrame()->load(url); + QTRY_VERIFY(loadSpy.isFinished()); +} + +void tst_QWebPage::undoActionHaveCustomText() +{ + m_page->mainFrame()->setHtml("<div id=test contenteditable></div>"); + m_page->mainFrame()->evaluateJavaScript("document.getElementById('test').focus()"); + + m_page->mainFrame()->evaluateJavaScript("document.execCommand('insertText', true, 'Test');"); + QString typingActionText = m_page->action(QWebPage::Undo)->text(); + + m_page->mainFrame()->evaluateJavaScript("document.execCommand('indent', true);"); + QString alignActionText = m_page->action(QWebPage::Undo)->text(); + + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QVERIFY(typingActionText != alignActionText); +} + +void tst_QWebPage::openWindowDefaultSize() +{ + TestPage page; + page.settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, true); + // Open a default window. + page.mainFrame()->evaluateJavaScript("window.open()"); + // Open a too small window. + page.mainFrame()->evaluateJavaScript("window.open('', '', 'width=10,height=10')"); + + QTest::qWait(500); + // The number of popups created should be two. + QVERIFY(page.createdWindows.size() == 2); + + QRect requestedGeometry = page.createdWindows[0]->requestedGeometry; + // Check default size has been requested. + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(requestedGeometry.width(), 0); + QEXPECT_FAIL("", "https://github.com/qtwebkit/qtwebkit/issues/913", Continue); + QCOMPARE(requestedGeometry.height(), 0); + + requestedGeometry = page.createdWindows[1]->requestedGeometry; + // Check minimum size has been requested. + QCOMPARE(requestedGeometry.width(), 100); + QCOMPARE(requestedGeometry.height(), 100); +} + +void tst_QWebPage::cssMediaTypeGlobalSetting() +{ + QString testHtml("<style>@media tv {body{background-color:red;}}@media handheld {body{background-color:green;}}@media screen {body{background-color:blue;}}</style>"); + QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool))); + + QWebSettings::globalSettings()->setCSSMediaType("tv"); + // Clear page specific setting to read from global setting + m_view->page()->settings()->setCSSMediaType(QString()); + m_view->setHtml(testHtml); + QTRY_COMPARE(loadSpy.count(), 1); + QVERIFY(m_view->page()->mainFrame()->evaluateJavaScript("window.matchMedia('tv').matches == true").toBool()); + QVERIFY(QWebSettings::globalSettings()->cssMediaType() == "tv"); + + QWebSettings::globalSettings()->setCSSMediaType("handheld"); + // Clear page specific setting to read from global setting + m_view->page()->settings()->setCSSMediaType(QString()); + m_view->setHtml(testHtml); + QTRY_COMPARE(loadSpy.count(), 2); + QVERIFY(m_view->page()->mainFrame()->evaluateJavaScript("window.matchMedia('handheld').matches == true").toBool()); + QVERIFY(QWebSettings::globalSettings()->cssMediaType() == "handheld"); + + QWebSettings::globalSettings()->setCSSMediaType("screen"); + // Clear page specific setting to read from global setting + m_view->page()->settings()->setCSSMediaType(QString()); + m_view->setHtml(testHtml); + QTRY_COMPARE(loadSpy.count(), 3); + QVERIFY(m_view->page()->mainFrame()->evaluateJavaScript("window.matchMedia('screen').matches == true").toBool()); + QVERIFY(QWebSettings::globalSettings()->cssMediaType() == "screen"); +} + +void tst_QWebPage::cssMediaTypePageSetting() +{ + QString testHtml("<style>@media tv {body{background-color:red;}}@media handheld {body{background-color:green;}}@media screen {body{background-color:blue;}}</style>"); + QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool))); + + m_view->page()->settings()->setCSSMediaType("tv"); + m_view->setHtml(testHtml); + QTRY_COMPARE(loadSpy.count(), 1); + QVERIFY(m_view->page()->mainFrame()->evaluateJavaScript("window.matchMedia('tv').matches == true").toBool()); + QVERIFY(m_view->page()->settings()->cssMediaType() == "tv"); + + m_view->page()->settings()->setCSSMediaType("handheld"); + m_view->setHtml(testHtml); + QTRY_COMPARE(loadSpy.count(), 2); + QVERIFY(m_view->page()->mainFrame()->evaluateJavaScript("window.matchMedia('handheld').matches == true").toBool()); + QVERIFY(m_view->page()->settings()->cssMediaType() == "handheld"); + + m_view->page()->settings()->setCSSMediaType("screen"); + m_view->setHtml(testHtml); + QTRY_COMPARE(loadSpy.count(), 3); + QVERIFY(m_view->page()->mainFrame()->evaluateJavaScript("window.matchMedia('screen').matches == true").toBool()); + QVERIFY(m_view->page()->settings()->cssMediaType() == "screen"); +} + +QTEST_MAIN(tst_QWebPage) +#include "tst_qwebpage.moc" |