diff options
46 files changed, 6487 insertions, 0 deletions
diff --git a/tests/widgets/qwebengineframe/qwebengineframe.pro b/tests/widgets/qwebengineframe/qwebengineframe.pro new file mode 100644 index 000000000..ff6c49628 --- /dev/null +++ b/tests/widgets/qwebengineframe/qwebengineframe.pro @@ -0,0 +1,2 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc diff --git a/tests/widgets/qwebengineframe/resources/image.png b/tests/widgets/qwebengineframe/resources/image.png Binary files differnew file mode 100644 index 000000000..8d703640c --- /dev/null +++ b/tests/widgets/qwebengineframe/resources/image.png diff --git a/tests/widgets/qwebengineframe/resources/style.css b/tests/widgets/qwebengineframe/resources/style.css new file mode 100644 index 000000000..c05b747f1 --- /dev/null +++ b/tests/widgets/qwebengineframe/resources/style.css @@ -0,0 +1 @@ +#idP {color: red !important} diff --git a/tests/widgets/qwebengineframe/resources/test1.html b/tests/widgets/qwebengineframe/resources/test1.html new file mode 100644 index 000000000..b323f966e --- /dev/null +++ b/tests/widgets/qwebengineframe/resources/test1.html @@ -0,0 +1 @@ +<html><body><p>Some text 1</p></body></html> diff --git a/tests/widgets/qwebengineframe/resources/test2.html b/tests/widgets/qwebengineframe/resources/test2.html new file mode 100644 index 000000000..63ac1f6ec --- /dev/null +++ b/tests/widgets/qwebengineframe/resources/test2.html @@ -0,0 +1 @@ +<html><body> <p>Some text 2</p></body></html> diff --git a/tests/widgets/qwebengineframe/resources/testiframe.html b/tests/widgets/qwebengineframe/resources/testiframe.html new file mode 100644 index 000000000..4b0e30ca5 --- /dev/null +++ b/tests/widgets/qwebengineframe/resources/testiframe.html @@ -0,0 +1,53 @@ +<html> +<head> +<title></title> +<style type="text/css"> +<!-- +#header { + background: #0f0; + position: absolute; + top: 0px; + left: 0px; + width: 800px; + height: 100px; +} +#content1 { + background: #ff0; + position: absolute; + top: 101px; + left: 0px; + width: 400px; + height: 400px; + overflow: scroll; +} +#content2 { + background: #ff7; + position: absolute; + top: 101px; + left: 401px; + width: 400px; + height: 400px; +} +#footer { + background: #0f0; + position: absolute; + top: 502px; + left: 0px; + width: 800px; + height: 200px; +} +--> +</style> +</head> +<body> +<div id="header"></div> +<div id="content1">You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible. +You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible. +You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible. +You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible. +You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible. +You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible.</div> +<iframe id="content2" name="control" src="testiframe2.html"> </iframe> +<div id="footer"></div> +</body> +</html>
\ No newline at end of file diff --git a/tests/widgets/qwebengineframe/resources/testiframe2.html b/tests/widgets/qwebengineframe/resources/testiframe2.html new file mode 100644 index 000000000..8957a5d8a --- /dev/null +++ b/tests/widgets/qwebengineframe/resources/testiframe2.html @@ -0,0 +1,20 @@ +<html> +<head> +<title></title> +<style type="text/css"> +<!-- +#content { + background: #fff; + position: absolute; + top: 0px; + left: 0px; + width: 800px; + height: 800px; +} +--> +</style> +</head> +<body> +<div id="content"> </div> +</body> +</html>
\ No newline at end of file diff --git a/tests/widgets/qwebengineframe/tst_qwebengineframe.cpp b/tests/widgets/qwebengineframe/tst_qwebengineframe.cpp new file mode 100644 index 000000000..59f620b1b --- /dev/null +++ b/tests/widgets/qwebengineframe/tst_qwebengineframe.cpp @@ -0,0 +1,1569 @@ +/* + Copyright (C) 2008,2009 Nokia Corporation and/or its subsidiary(-ies) + + 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 <QtTest/QtTest> + +#include <qwebpage.h> +#include <qwebelement.h> +#include <qwebview.h> +#include <qwebframe.h> +#include <qwebhistory.h> +#include <QAbstractItemView> +#include <QApplication> +#include <QComboBox> +#include <QPaintEngine> +#include <QPicture> +#include <QRegExp> +#include <QNetworkRequest> +#include <QNetworkReply> +#include <QTextCodec> +#ifndef QT_NO_OPENSSL +#include <qsslerror.h> +#endif +#include "../util.h" + +class tst_QWebFrame : public QObject +{ + Q_OBJECT + +public: + bool eventFilter(QObject* watched, QEvent* event); + +public Q_SLOTS: + void init(); + void cleanup(); + +private Q_SLOTS: + void horizontalScrollAfterBack(); + void symmetricUrl(); + void progressSignal(); + void urlChange(); + void requestedUrl(); + void requestedUrlAfterSetAndLoadFailures(); + void javaScriptWindowObjectCleared_data(); + void javaScriptWindowObjectCleared(); + void javaScriptWindowObjectClearedOnEvaluate(); + void setHtml(); + void setHtmlWithImageResource(); + void setHtmlWithStylesheetResource(); + void setHtmlWithBaseURL(); + void setHtmlWithJSAlert(); + void ipv6HostEncoding(); + void metaData(); +#if !defined(QT_NO_COMBOBOX) + void popupFocus(); +#endif + void inputFieldFocus(); + void hitTestContent(); + void baseUrl_data(); + void baseUrl(); + void hasSetFocus(); + void renderGeometry(); + void renderHints(); + void scrollPosition(); + void scrollToAnchor(); + void scrollbarsOff(); + void evaluateWillCauseRepaint(); + void setContent_data(); + void setContent(); + void setCacheLoadControlAttribute(); + void setUrlWithPendingLoads(); + void setUrlWithFragment_data(); + void setUrlWithFragment(); + void setUrlToEmpty(); + void setUrlToInvalid(); + void setUrlHistory(); + void setUrlUsingStateObject(); + void setUrlSameUrl(); + void setUrlThenLoads_data(); + void setUrlThenLoads(); + void loadFinishedAfterNotFoundError(); + void loadInSignalHandlers_data(); + void loadInSignalHandlers(); + +private: + QWebView* m_view; + QWebPage* m_page; + QWebView* m_inputFieldsTestView; + int m_inputFieldTestPaintCount; +}; + +bool tst_QWebFrame::eventFilter(QObject* watched, QEvent* event) +{ + // used on the inputFieldFocus test + if (watched == m_inputFieldsTestView) { + if (event->type() == QEvent::Paint) + m_inputFieldTestPaintCount++; + } + return QObject::eventFilter(watched, event); +} + +void tst_QWebFrame::init() +{ + m_view = new QWebView(); + m_page = m_view->page(); +} + +void tst_QWebFrame::cleanup() +{ + delete m_view; +} + +void tst_QWebFrame::symmetricUrl() +{ + QVERIFY(m_view->url().isEmpty()); + + QCOMPARE(m_view->history()->count(), 0); + + QUrl dataUrl("data:text/html,<h1>Test"); + + m_view->setUrl(dataUrl); + QCOMPARE(m_view->url(), dataUrl); + QCOMPARE(m_view->history()->count(), 0); + + // loading is _not_ immediate, so the text isn't set just yet. + QVERIFY(m_view->page()->mainFrame()->toPlainText().isEmpty()); + + ::waitForSignal(m_view, SIGNAL(loadFinished(bool))); + + QCOMPARE(m_view->history()->count(), 1); + QCOMPARE(m_view->page()->mainFrame()->toPlainText(), QString("Test")); + + QUrl dataUrl2("data:text/html,<h1>Test2"); + QUrl dataUrl3("data:text/html,<h1>Test3"); + + m_view->setUrl(dataUrl2); + m_view->setUrl(dataUrl3); + + QCOMPARE(m_view->url(), dataUrl3); + + ::waitForSignal(m_view, SIGNAL(loadFinished(bool))); + + QCOMPARE(m_view->history()->count(), 2); + + QCOMPARE(m_view->page()->mainFrame()->toPlainText(), QString("Test3")); +} + +void tst_QWebFrame::progressSignal() +{ + QSignalSpy progressSpy(m_view, SIGNAL(loadProgress(int))); + + QUrl dataUrl("data:text/html,<h1>Test"); + m_view->setUrl(dataUrl); + + ::waitForSignal(m_view, SIGNAL(loadFinished(bool))); + + QVERIFY(progressSpy.size() >= 2); + + // WebKit defines initialProgressValue as 10%, not 0% + QCOMPARE(progressSpy.first().first().toInt(), 10); + + // But we always end at 100% + QCOMPARE(progressSpy.last().first().toInt(), 100); +} + +void tst_QWebFrame::urlChange() +{ + QSignalSpy urlSpy(m_page->mainFrame(), SIGNAL(urlChanged(QUrl))); + + QUrl dataUrl("data:text/html,<h1>Test"); + m_view->setUrl(dataUrl); + + ::waitForSignal(m_page->mainFrame(), SIGNAL(urlChanged(QUrl))); + + QCOMPARE(urlSpy.size(), 1); + + QUrl dataUrl2("data:text/html,<html><head><title>title</title></head><body><h1>Test</body></html>"); + m_view->setUrl(dataUrl2); + + ::waitForSignal(m_page->mainFrame(), SIGNAL(urlChanged(QUrl))); + + QCOMPARE(urlSpy.size(), 2); +} + +class FakeReply : public QNetworkReply { + Q_OBJECT + +public: + static const QUrl urlFor404ErrorWithoutContents; + + FakeReply(const QNetworkRequest& request, QObject* parent = 0) + : QNetworkReply(parent) + { + setOperation(QNetworkAccessManager::GetOperation); + setRequest(request); + setUrl(request.url()); + if (request.url() == QUrl("qrc:/test1.html")) { + setHeader(QNetworkRequest::LocationHeader, QString("qrc:/test2.html")); + setAttribute(QNetworkRequest::RedirectionTargetAttribute, QUrl("qrc:/test2.html")); + QTimer::singleShot(0, this, SLOT(continueRedirect())); + } +#ifndef QT_NO_OPENSSL + else if (request.url() == QUrl("qrc:/fake-ssl-error.html")) { + setError(QNetworkReply::SslHandshakeFailedError, tr("Fake error!")); + QTimer::singleShot(0, this, SLOT(continueError())); + } +#endif + else if (request.url().host() == QLatin1String("abcdef.abcdef")) { + setError(QNetworkReply::HostNotFoundError, tr("Invalid URL")); + QTimer::singleShot(0, this, SLOT(continueError())); + } else if (request.url() == FakeReply::urlFor404ErrorWithoutContents) { + setError(QNetworkReply::ContentNotFoundError, "Not found"); + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 404); + QTimer::singleShot(0, this, SLOT(continueError())); + } + + open(QIODevice::ReadOnly); + } + ~FakeReply() + { + close(); + } + virtual void abort() {} + virtual void close() {} + +protected: + qint64 readData(char*, qint64) + { + return 0; + } + +private Q_SLOTS: + void continueRedirect() + { + emit metaDataChanged(); + emit finished(); + } + + void continueError() + { + emit error(this->error()); + emit finished(); + } +}; + +const QUrl FakeReply::urlFor404ErrorWithoutContents = QUrl("http://this.will/return-http-404-error-without-contents.html"); + +class FakeNetworkManager : public QNetworkAccessManager { + Q_OBJECT + +public: + FakeNetworkManager(QObject* parent) : QNetworkAccessManager(parent) { } + +protected: + virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, QIODevice* outgoingData) + { + QString url = request.url().toString(); + if (op == QNetworkAccessManager::GetOperation) { +#ifndef QT_NO_OPENSSL + if (url == "qrc:/fake-ssl-error.html") { + FakeReply* reply = new FakeReply(request, this); + QList<QSslError> errors; + emit sslErrors(reply, errors << QSslError(QSslError::UnspecifiedError)); + return reply; + } +#endif + if (url == "qrc:/test1.html" || url == "http://abcdef.abcdef/" || request.url() == FakeReply::urlFor404ErrorWithoutContents) + return new FakeReply(request, this); + } + + return QNetworkAccessManager::createRequest(op, request, outgoingData); + } +}; + +void tst_QWebFrame::requestedUrl() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + // in few seconds, the image should be completely loaded + QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); + FakeNetworkManager* networkManager = new FakeNetworkManager(&page); + page.setNetworkAccessManager(networkManager); + + frame->setUrl(QUrl("qrc:/test1.html")); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy.count(), 1); + QCOMPARE(frame->requestedUrl(), QUrl("qrc:/test1.html")); + QCOMPARE(frame->url(), QUrl("qrc:/test2.html")); + + frame->setUrl(QUrl("qrc:/non-existent.html")); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy.count(), 2); + QCOMPARE(frame->requestedUrl(), QUrl("qrc:/non-existent.html")); + QCOMPARE(frame->url(), QUrl("qrc:/non-existent.html")); + + frame->setUrl(QUrl("http://abcdef.abcdef")); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy.count(), 3); + QCOMPARE(frame->requestedUrl(), QUrl("http://abcdef.abcdef/")); + QCOMPARE(frame->url(), QUrl("http://abcdef.abcdef/")); + +#ifndef QT_NO_OPENSSL + qRegisterMetaType<QList<QSslError> >("QList<QSslError>"); + qRegisterMetaType<QNetworkReply* >("QNetworkReply*"); + + QSignalSpy spy2(page.networkAccessManager(), SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>))); + frame->setUrl(QUrl("qrc:/fake-ssl-error.html")); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy2.count(), 1); + QCOMPARE(frame->requestedUrl(), QUrl("qrc:/fake-ssl-error.html")); + QCOMPARE(frame->url(), QUrl("qrc:/fake-ssl-error.html")); +#endif +} + +void tst_QWebFrame::requestedUrlAfterSetAndLoadFailures() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + QSignalSpy spy(frame, SIGNAL(loadFinished(bool))); + + const QUrl first("http://abcdef.abcdef/"); + frame->setUrl(first); + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), first); + QCOMPARE(frame->requestedUrl(), first); + QVERIFY(!spy.at(0).first().toBool()); + + const QUrl second("http://abcdef.abcdef/another_page.html"); + QVERIFY(first != second); + + frame->load(second); + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), first); + QCOMPARE(frame->requestedUrl(), second); + QVERIFY(!spy.at(1).first().toBool()); +} + +void tst_QWebFrame::javaScriptWindowObjectCleared_data() +{ + QTest::addColumn<QString>("html"); + QTest::addColumn<int>("signalCount"); + QTest::newRow("with <script>") << "<html><body><script>i=0</script><p>hello world</p></body></html>" << 1; + // NOTE: Empty scripts no longer cause this signal to be emitted. + QTest::newRow("with empty <script>") << "<html><body><script></script><p>hello world</p></body></html>" << 0; + QTest::newRow("without <script>") << "<html><body><p>hello world</p></body></html>" << 0; +} + +void tst_QWebFrame::javaScriptWindowObjectCleared() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + QSignalSpy spy(frame, SIGNAL(javaScriptWindowObjectCleared())); + QFETCH(QString, html); + frame->setHtml(html); + + QFETCH(int, signalCount); + QCOMPARE(spy.count(), signalCount); +} + +void tst_QWebFrame::javaScriptWindowObjectClearedOnEvaluate() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + QSignalSpy spy(frame, SIGNAL(javaScriptWindowObjectCleared())); + frame->setHtml("<html></html>"); + QCOMPARE(spy.count(), 0); + frame->evaluateJavaScript("var a = 'a';"); + QCOMPARE(spy.count(), 1); + // no new clear for a new script: + frame->evaluateJavaScript("var a = 1;"); + QCOMPARE(spy.count(), 1); +} + +void tst_QWebFrame::setHtml() +{ + QString html("<html><head></head><body><p>hello world</p></body></html>"); + QSignalSpy spy(m_view->page(), SIGNAL(loadFinished(bool))); + m_view->page()->mainFrame()->setHtml(html); + QCOMPARE(m_view->page()->mainFrame()->toHtml(), html); + QCOMPARE(spy.count(), 1); +} + +void tst_QWebFrame::setHtmlWithImageResource() +{ + // By default, only security origins of local files can load local resources. + // So we should specify baseUrl to be a local file in order to get a proper origin and load the local image. + + QLatin1String html("<html><body><p>hello world</p><img src='qrc:/image.png'/></body></html>"); + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + frame->setHtml(html, QUrl(QLatin1String("file:///path/to/file"))); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + + QCOMPARE(frame->evaluateJavaScript("document.images.length").toInt(), 1); + QCOMPARE(frame->evaluateJavaScript("document.images[0].width").toInt(), 128); + QCOMPARE(frame->evaluateJavaScript("document.images[0].height").toInt(), 128); + + // Now we test the opposite: without a baseUrl as a local file, we cannot request local resources. + + frame->setHtml(html); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(frame->evaluateJavaScript("document.images.length").toInt(), 1); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118659", Continue); + QCOMPARE(frame->evaluateJavaScript("document.images[0].width").toInt(), 0); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118659", Continue); + QCOMPARE(frame->evaluateJavaScript("document.images[0].height").toInt(), 0); +} + +void tst_QWebFrame::setHtmlWithStylesheetResource() +{ + // By default, only security origins of local files can load local resources. + // So we should specify baseUrl to be a local file in order to be able to download the local stylesheet. + + const char* htmlData = + "<html>" + "<head>" + "<link rel='stylesheet' href='qrc:/style.css' type='text/css' />" + "</head>" + "<body>" + "<p id='idP'>some text</p>" + "</body>" + "</html>"; + QLatin1String html(htmlData); + QWebPage page; + QWebFrame* frame = page.mainFrame(); + QWebElement webElement; + + frame->setHtml(html, QUrl(QLatin1String("qrc:///file"))); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + webElement = frame->documentElement().findFirst("p"); + QCOMPARE(webElement.styleProperty("color", QWebElement::CascadedStyle), QLatin1String("red")); + + // Now we test the opposite: without a baseUrl as a local file, we cannot request local resources. + + frame->setHtml(html, QUrl(QLatin1String("http://www.example.com/"))); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + webElement = frame->documentElement().findFirst("p"); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118659", Continue); + QCOMPARE(webElement.styleProperty("color", QWebElement::CascadedStyle), QString()); +} + +void tst_QWebFrame::setHtmlWithBaseURL() +{ + // This tests if baseUrl is indeed affecting the relative paths from resources. + // As we are using a local file as baseUrl, its security origin should be able to load local resources. + + if (!QDir(TESTS_SOURCE_DIR).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + + QDir::setCurrent(TESTS_SOURCE_DIR); + + QString html("<html><body><p>hello world</p><img src='resources/image2.png'/></body></html>"); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + // in few seconds, the image should be completey loaded + QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); + + frame->setHtml(html, QUrl::fromLocalFile(TESTS_SOURCE_DIR)); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy.count(), 1); + + QCOMPARE(frame->evaluateJavaScript("document.images.length").toInt(), 1); + QCOMPARE(frame->evaluateJavaScript("document.images[0].width").toInt(), 128); + QCOMPARE(frame->evaluateJavaScript("document.images[0].height").toInt(), 128); + + // no history item has to be added. + QCOMPARE(m_view->page()->history()->count(), 0); +} + +class MyPage : public QWebPage +{ +public: + MyPage() : QWebPage(), alerts(0) {} + int alerts; + +protected: + virtual void javaScriptAlert(QWebFrame*, const QString& msg) + { + alerts++; + QCOMPARE(msg, QString("foo")); + // Should not be enough to trigger deferred loading, since we've upped the HTML + // tokenizer delay in the Qt frameloader. See HTMLTokenizer::continueProcessing() + QTest::qWait(1000); + } +}; + +void tst_QWebFrame::setHtmlWithJSAlert() +{ + QString html("<html><head></head><body><script>alert('foo');</script><p>hello world</p></body></html>"); + MyPage page; + m_view->setPage(&page); + page.mainFrame()->setHtml(html); + QCOMPARE(page.alerts, 1); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118663", Continue); + QCOMPARE(m_view->page()->mainFrame()->toHtml(), html); +} + +class TestNetworkManager : public QNetworkAccessManager +{ +public: + TestNetworkManager(QObject* parent) : QNetworkAccessManager(parent) {} + + QList<QUrl> requestedUrls; + +protected: + virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest &request, QIODevice* outgoingData) { + requestedUrls.append(request.url()); + QNetworkRequest redirectedRequest = request; + redirectedRequest.setUrl(QUrl("data:text/html,<p>hello")); + return QNetworkAccessManager::createRequest(op, redirectedRequest, outgoingData); + } +}; + +void tst_QWebFrame::ipv6HostEncoding() +{ + TestNetworkManager* networkManager = new TestNetworkManager(m_page); + m_page->setNetworkAccessManager(networkManager); + networkManager->requestedUrls.clear(); + + QUrl baseUrl = QUrl::fromEncoded("http://[::1]/index.html"); + m_view->setHtml("<p>Hi", baseUrl); + m_view->page()->mainFrame()->evaluateJavaScript("var r = new XMLHttpRequest();" + "r.open('GET', 'http://[::1]/test.xml', false);" + "r.send(null);" + ); + QCOMPARE(networkManager->requestedUrls.count(), 1); + QCOMPARE(networkManager->requestedUrls.at(0), QUrl::fromEncoded("http://[::1]/test.xml")); +} + +void tst_QWebFrame::metaData() +{ + m_view->setHtml("<html>" + " <head>" + " <meta name=\"description\" content=\"Test description\">" + " <meta name=\"keywords\" content=\"HTML, JavaScript, Css\">" + " </head>" + "</html>"); + + QMultiMap<QString, QString> metaData = m_view->page()->mainFrame()->metaData(); + + QCOMPARE(metaData.count(), 2); + + QCOMPARE(metaData.value("description"), QString("Test description")); + QCOMPARE(metaData.value("keywords"), QString("HTML, JavaScript, Css")); + QCOMPARE(metaData.value("nonexistent"), QString()); + + m_view->setHtml("<html>" + " <head>" + " <meta name=\"samekey\" content=\"FirstValue\">" + " <meta name=\"samekey\" content=\"SecondValue\">" + " </head>" + "</html>"); + + metaData = m_view->page()->mainFrame()->metaData(); + + QCOMPARE(metaData.count(), 2); + + QStringList values = metaData.values("samekey"); + QCOMPARE(values.count(), 2); + + QVERIFY(values.contains("FirstValue")); + QVERIFY(values.contains("SecondValue")); + + QCOMPARE(metaData.value("nonexistent"), QString()); +} + +#if !defined(QT_NO_COMBOBOX) +void tst_QWebFrame::popupFocus() +{ + QWebView view; + view.setHtml("<html>" + " <body>" + " <select name=\"select\">" + " <option>1</option>" + " <option>2</option>" + " </select>" + " <input type=\"text\"> </input>" + " <textarea name=\"text_area\" rows=\"3\" cols=\"40\">" + "This test checks whether showing and hiding a popup" + "takes the focus away from the webpage." + " </textarea>" + " </body>" + "</html>"); + view.resize(400, 100); + // Call setFocus before show to work around http://bugreports.qt.nokia.com/browse/QTBUG-14762 + view.setFocus(); + view.show(); + QTest::qWaitForWindowExposed(&view); + view.activateWindow(); + QTRY_VERIFY(view.hasFocus()); + + // open the popup by clicking. check if focus is on the popup + const QWebElement webCombo = view.page()->mainFrame()->documentElement().findFirst(QLatin1String("select[name=select]")); + QTest::mouseClick(&view, Qt::LeftButton, 0, webCombo.geometry().center()); + + QComboBox* combo = view.findChild<QComboBox*>(); + QVERIFY(combo != 0); + QTRY_VERIFY(!view.hasFocus() && combo->view()->hasFocus()); // Focus should be on the popup + + // hide the popup and check if focus is on the page + combo->hidePopup(); + QTRY_VERIFY(view.hasFocus()); // Focus should be back on the WebView +} +#endif + +void tst_QWebFrame::inputFieldFocus() +{ + QWebView view; + view.setHtml("<html><body><input type=\"text\"></input></body></html>"); + view.resize(400, 100); + view.show(); + QTest::qWaitForWindowExposed(&view); + view.activateWindow(); + view.setFocus(); + QTRY_VERIFY(view.hasFocus()); + + // double the flashing time, should at least blink once already + int delay = qApp->cursorFlashTime() * 2; + + // focus the lineedit and check if it blinks + bool autoSipEnabled = qApp->autoSipEnabled(); + qApp->setAutoSipEnabled(false); + const QWebElement inputElement = view.page()->mainFrame()->documentElement().findFirst(QLatin1String("input[type=text]")); + QTest::mouseClick(&view, Qt::LeftButton, 0, inputElement.geometry().center()); + m_inputFieldsTestView = &view; + view.installEventFilter( this ); + QTest::qWait(delay); + QVERIFY2(m_inputFieldTestPaintCount >= 3, + "The input field should have a blinking caret"); + qApp->setAutoSipEnabled(autoSipEnabled); +} + +void tst_QWebFrame::hitTestContent() +{ + QString html("<html><body><p>A paragraph</p><br/><br/><br/><a href=\"about:blank\" target=\"_foo\" id=\"link\">link text</a></body></html>"); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + frame->setHtml(html); + page.setViewportSize(QSize(200, 0)); //no height so link is not visible + const QWebElement linkElement = frame->documentElement().findFirst(QLatin1String("a#link")); + QWebHitTestResult result = frame->hitTestContent(linkElement.geometry().center()); + QCOMPARE(result.linkText(), QString("link text")); + QWebElement link = result.linkElement(); + QCOMPARE(link.attribute("target"), QString("_foo")); +} + +void tst_QWebFrame::baseUrl_data() +{ + QTest::addColumn<QString>("html"); + QTest::addColumn<QUrl>("loadUrl"); + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QUrl>("baseUrl"); + + QTest::newRow("null") << QString() << QUrl() + << QUrl("about:blank") << QUrl("about:blank"); + + QTest::newRow("foo") << QString() << QUrl("http://foobar.baz/") + << QUrl("http://foobar.baz/") << QUrl("http://foobar.baz/"); + + QString html = "<html>" + "<head>" + "<base href=\"http://foobaz.bar/\" />" + "</head>" + "</html>"; + QTest::newRow("customBaseUrl") << html << QUrl("http://foobar.baz/") + << QUrl("http://foobar.baz/") << QUrl("http://foobaz.bar/"); +} + +void tst_QWebFrame::baseUrl() +{ + QFETCH(QString, html); + QFETCH(QUrl, loadUrl); + QFETCH(QUrl, url); + QFETCH(QUrl, baseUrl); + + m_page->mainFrame()->setHtml(html, loadUrl); + QCOMPARE(m_page->mainFrame()->url(), url); + QCOMPARE(m_page->mainFrame()->baseUrl(), baseUrl); +} + +void tst_QWebFrame::hasSetFocus() +{ + QString html("<html><body><p>top</p>" \ + "<iframe width='80%' height='30%'/>" \ + "</body></html>"); + + QSignalSpy loadSpy(m_page, SIGNAL(loadFinished(bool))); + m_page->mainFrame()->setHtml(html); + + waitForSignal(m_page->mainFrame(), SIGNAL(loadFinished(bool)), 200); + QCOMPARE(loadSpy.size(), 1); + + QList<QWebFrame*> children = m_page->mainFrame()->childFrames(); + QWebFrame* frame = children.at(0); + QString innerHtml("<html><body><p>another iframe</p>" \ + "<iframe width='80%' height='30%'/>" \ + "</body></html>"); + frame->setHtml(innerHtml); + + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(loadSpy.size(), 2); + + m_page->mainFrame()->setFocus(); + QTRY_VERIFY(m_page->mainFrame()->hasFocus()); + + for (int i = 0; i < children.size(); ++i) { + children.at(i)->setFocus(); + QTRY_VERIFY(children.at(i)->hasFocus()); + QVERIFY(!m_page->mainFrame()->hasFocus()); + } + + m_page->mainFrame()->setFocus(); + QTRY_VERIFY(m_page->mainFrame()->hasFocus()); +} + +void tst_QWebFrame::renderGeometry() +{ + QString html("<html>" \ + "<head><style>" \ + "body, iframe { margin: 0px; border: none; }" \ + "</style></head>" \ + "<body><iframe width='100px' height='100px'/></body>" \ + "</html>"); + + QWebPage page; + page.mainFrame()->setHtml(html); + + QList<QWebFrame*> frames = page.mainFrame()->childFrames(); + QWebFrame *frame = frames.at(0); + QString innerHtml("<body style='margin: 0px;'><img src='qrc:/image.png'/></body>"); + + // By default, only security origins of local files can load local resources. + // So we should specify baseUrl to be a local file in order to get a proper origin. + frame->setHtml(innerHtml, QUrl("file:///path/to/file")); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + + QPicture picture; + + QSize size = page.mainFrame()->contentsSize(); + page.setViewportSize(size); + + // render contents layer only (the iframe is smaller than the image, so it will have scrollbars) + QPainter painter1(&picture); + frame->render(&painter1, QWebFrame::ContentsLayer); + painter1.end(); + + QCOMPARE(size.width(), picture.boundingRect().width() + frame->scrollBarGeometry(Qt::Vertical).width()); + QCOMPARE(size.height(), picture.boundingRect().height() + frame->scrollBarGeometry(Qt::Horizontal).height()); + + // render everything, should be the size of the iframe + QPainter painter2(&picture); + frame->render(&painter2, QWebFrame::AllLayers); + painter2.end(); + + QCOMPARE(size.width(), picture.boundingRect().width()); // width: 100px + QCOMPARE(size.height(), picture.boundingRect().height()); // height: 100px +} + + +class DummyPaintEngine: public QPaintEngine { +public: + + DummyPaintEngine() + : QPaintEngine(QPaintEngine::AllFeatures) + , renderHints(0) + { + } + + bool begin(QPaintDevice*) + { + setActive(true); + return true; + } + + bool end() + { + setActive(false); + return false; + } + + void updateState(const QPaintEngineState& state) + { + renderHints = state.renderHints(); + } + + void drawPath(const QPainterPath&) { } + void drawPixmap(const QRectF&, const QPixmap&, const QRectF&) { } + + QPaintEngine::Type type() const + { + return static_cast<QPaintEngine::Type>(QPaintEngine::User + 2); + } + + QPainter::RenderHints renderHints; +}; + +class DummyPaintDevice: public QPaintDevice { +public: + DummyPaintDevice() + : QPaintDevice() + , m_engine(new DummyPaintEngine) + { + } + + ~DummyPaintDevice() + { + delete m_engine; + } + + QPaintEngine* paintEngine() const + { + return m_engine; + } + + QPainter::RenderHints renderHints() const + { + return m_engine->renderHints; + } + +protected: + int metric(PaintDeviceMetric metric) const; + +private: + DummyPaintEngine* m_engine; + friend class DummyPaintEngine; +}; + + +int DummyPaintDevice::metric(PaintDeviceMetric metric) const +{ + switch (metric) { + case PdmWidth: + return 400; + break; + + case PdmHeight: + return 200; + break; + + case PdmNumColors: + return INT_MAX; + break; + + case PdmDepth: + return 32; + break; + + default: + break; + } + return 0; +} + +void tst_QWebFrame::renderHints() +{ + QString html("<html><body><p>Hello, world!</p></body></html>"); + + QWebPage page; + page.mainFrame()->setHtml(html); + page.setViewportSize(page.mainFrame()->contentsSize()); + + // We will call frame->render and trap the paint engine state changes + // to ensure that GraphicsContext does not clobber the render hints. + DummyPaintDevice buffer; + QPainter painter(&buffer); + + painter.setRenderHint(QPainter::TextAntialiasing, false); + page.mainFrame()->render(&painter); + QVERIFY(!(buffer.renderHints() & QPainter::TextAntialiasing)); + QVERIFY(!(buffer.renderHints() & QPainter::SmoothPixmapTransform)); + QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing)); + + painter.setRenderHint(QPainter::TextAntialiasing, true); + page.mainFrame()->render(&painter); + QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing); + QVERIFY(!(buffer.renderHints() & QPainter::SmoothPixmapTransform)); + QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing)); + + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + page.mainFrame()->render(&painter); + QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing); + QVERIFY(buffer.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing)); + + painter.setRenderHint(QPainter::HighQualityAntialiasing, true); + page.mainFrame()->render(&painter); + QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing); + QVERIFY(buffer.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(buffer.renderHints() & QPainter::HighQualityAntialiasing); +} + +void tst_QWebFrame::scrollPosition() +{ + // enlarged image in a small viewport, to provoke the scrollbars to appear + QString html("<html><body><img src='qrc:/image.png' height=500 width=500/></body></html>"); + + QWebPage page; + page.setViewportSize(QSize(200, 200)); + + QWebFrame* frame = page.mainFrame(); + frame->setHtml(html); + frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); + frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); + + // try to set the scroll offset programmatically + frame->setScrollPosition(QPoint(23, 29)); + QCOMPARE(frame->scrollPosition().x(), 23); + QCOMPARE(frame->scrollPosition().y(), 29); + + int x = frame->evaluateJavaScript("window.scrollX").toInt(); + int y = frame->evaluateJavaScript("window.scrollY").toInt(); + QCOMPARE(x, 23); + QCOMPARE(y, 29); +} + +void tst_QWebFrame::scrollToAnchor() +{ + QWebPage page; + page.setViewportSize(QSize(480, 800)); + QWebFrame* frame = page.mainFrame(); + + QString html("<html><body><p style=\"margin-bottom: 1500px;\">Hello.</p>" + "<p><a id=\"foo\">This</a> is an anchor</p>" + "<p style=\"margin-bottom: 1500px;\"><a id=\"bar\">This</a> is another anchor</p>" + "</body></html>"); + frame->setHtml(html); + frame->setScrollPosition(QPoint(0, 0)); + QCOMPARE(frame->scrollPosition().x(), 0); + QCOMPARE(frame->scrollPosition().y(), 0); + + QWebElement fooAnchor = frame->findFirstElement("a[id=foo]"); + + frame->scrollToAnchor("foo"); + QCOMPARE(frame->scrollPosition().y(), fooAnchor.geometry().top()); + + frame->scrollToAnchor("bar"); + frame->scrollToAnchor("foo"); + QCOMPARE(frame->scrollPosition().y(), fooAnchor.geometry().top()); + + frame->scrollToAnchor("top"); + QCOMPARE(frame->scrollPosition().y(), 0); + + frame->scrollToAnchor("bar"); + frame->scrollToAnchor("notexist"); + QVERIFY(frame->scrollPosition().y() != 0); +} + + +void tst_QWebFrame::scrollbarsOff() +{ + QWebView view; + QWebFrame* mainFrame = view.page()->mainFrame(); + + mainFrame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); + mainFrame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); + + QString html("<script>" \ + " function checkScrollbar() {" \ + " if (innerWidth === document.documentElement.offsetWidth)" \ + " document.getElementById('span1').innerText = 'SUCCESS';" \ + " else" \ + " document.getElementById('span1').innerText = 'FAIL';" \ + " }" \ + "</script>" \ + "<body>" \ + " <div style='margin-top:1000px ; margin-left:1000px'>" \ + " <a id='offscreen' href='a'>End</a>" \ + " </div>" \ + "<span id='span1'></span>" \ + "</body>"); + + + QSignalSpy loadSpy(&view, SIGNAL(loadFinished(bool))); + view.setHtml(html); + ::waitForSignal(&view, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(loadSpy.count(), 1); + + mainFrame->evaluateJavaScript("checkScrollbar();"); + QCOMPARE(mainFrame->documentElement().findAll("span").at(0).toPlainText(), QString("SUCCESS")); +} + +void tst_QWebFrame::horizontalScrollAfterBack() +{ + QWebView view; + QWebFrame* frame = view.page()->mainFrame(); + QSignalSpy loadSpy(view.page(), SIGNAL(loadFinished(bool))); + + view.page()->settings()->setMaximumPagesInCache(2); + frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAsNeeded); + frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAsNeeded); + + view.load(QUrl("qrc:/testiframe2.html")); + view.resize(200, 200); + QTRY_COMPARE(loadSpy.count(), 1); + QTRY_VERIFY((frame->scrollBarGeometry(Qt::Horizontal)).height()); + + view.load(QUrl("qrc:/testiframe.html")); + QTRY_COMPARE(loadSpy.count(), 2); + + view.page()->triggerAction(QWebPage::Back); + QTRY_COMPARE(loadSpy.count(), 3); + QTRY_VERIFY((frame->scrollBarGeometry(Qt::Horizontal)).height()); +} + +void tst_QWebFrame::evaluateWillCauseRepaint() +{ + QWebView view; + QString html("<html><body>top<div id=\"junk\" style=\"display: block;\">" + "junk</div>bottom</body></html>"); + view.setHtml(html); + view.show(); + + QTest::qWaitForWindowExposed(&view); + view.page()->mainFrame()->evaluateJavaScript( + "document.getElementById('junk').style.display = 'none';"); + + ::waitForSignal(view.page(), SIGNAL(repaintRequested(QRect))); +} + +void tst_QWebFrame::setContent_data() +{ + QTest::addColumn<QString>("mimeType"); + QTest::addColumn<QByteArray>("testContents"); + QTest::addColumn<QString>("expected"); + + QString str = QString::fromUtf8("ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει"); + QTest::newRow("UTF-8 plain text") << "text/plain; charset=utf-8" << str.toUtf8() << str; + + QTextCodec *utf16 = QTextCodec::codecForName("UTF-16"); + if (utf16) + QTest::newRow("UTF-16 plain text") << "text/plain; charset=utf-16" << utf16->fromUnicode(str) << str; + + str = QString::fromUtf8("Une chaîne de caractères à sa façon."); + QTest::newRow("latin-1 plain text") << "text/plain; charset=iso-8859-1" << str.toLatin1() << str; + + +} + +void tst_QWebFrame::setContent() +{ + QFETCH(QString, mimeType); + QFETCH(QByteArray, testContents); + QFETCH(QString, expected); + m_view->setContent(testContents, mimeType); + QWebFrame* mainFrame = m_view->page()->mainFrame(); + QCOMPARE(expected , mainFrame->toPlainText()); +} + +class CacheNetworkAccessManager : public QNetworkAccessManager { +public: + CacheNetworkAccessManager(QObject* parent = 0) + : QNetworkAccessManager(parent) + , m_lastCacheLoad(QNetworkRequest::PreferNetwork) + { + } + + virtual QNetworkReply* createRequest(Operation, const QNetworkRequest& request, QIODevice*) + { + QVariant cacheLoad = request.attribute(QNetworkRequest::CacheLoadControlAttribute); + if (cacheLoad.isValid()) + m_lastCacheLoad = static_cast<QNetworkRequest::CacheLoadControl>(cacheLoad.toUInt()); + else + m_lastCacheLoad = QNetworkRequest::PreferNetwork; // default value + return new FakeReply(request, this); + } + + QNetworkRequest::CacheLoadControl lastCacheLoad() const + { + return m_lastCacheLoad; + } + +private: + QNetworkRequest::CacheLoadControl m_lastCacheLoad; +}; + +void tst_QWebFrame::setCacheLoadControlAttribute() +{ + QWebPage page; + CacheNetworkAccessManager* manager = new CacheNetworkAccessManager(&page); + page.setNetworkAccessManager(manager); + QWebFrame* frame = page.mainFrame(); + + QNetworkRequest request(QUrl("http://abcdef.abcdef/")); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache); + frame->load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::AlwaysCache); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + frame->load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::PreferCache); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); + frame->load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::AlwaysNetwork); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork); + frame->load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::PreferNetwork); +} + +void tst_QWebFrame::setUrlWithPendingLoads() +{ + QWebPage page; + page.mainFrame()->setHtml("<img src='dummy:'/>"); + page.mainFrame()->setUrl(QUrl("about:blank")); +} + +void tst_QWebFrame::setUrlWithFragment_data() +{ + QTest::addColumn<QUrl>("previousUrl"); + QTest::newRow("empty") << QUrl(); + QTest::newRow("same URL no fragment") << QUrl("qrc:/test1.html"); + // See comments in setUrlSameUrl about using setUrl() with the same url(). + QTest::newRow("same URL with same fragment") << QUrl("qrc:/test1.html#"); + QTest::newRow("same URL with different fragment") << QUrl("qrc:/test1.html#anotherFragment"); + QTest::newRow("another URL") << QUrl("qrc:/test2.html"); +} + +// Based on bug report https://bugs.webkit.org/show_bug.cgi?id=32723 +void tst_QWebFrame::setUrlWithFragment() +{ + QFETCH(QUrl, previousUrl); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + if (!previousUrl.isEmpty()) { + frame->load(previousUrl); + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), previousUrl); + } + + QSignalSpy spy(frame, SIGNAL(loadFinished(bool))); + const QUrl url("qrc:/test1.html#"); + QVERIFY(!url.fragment().isNull()); + + frame->setUrl(url); + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + + QCOMPARE(spy.count(), 1); + QVERIFY(!frame->toPlainText().isEmpty()); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(frame->url(), url); +} + +void tst_QWebFrame::setUrlToEmpty() +{ + int expectedLoadFinishedCount = 0; + const QUrl aboutBlank("about:blank"); + const QUrl url("qrc:/test2.html"); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + QCOMPARE(frame->url(), QUrl()); + QCOMPARE(frame->requestedUrl(), QUrl()); + QCOMPARE(frame->baseUrl(), QUrl()); + + QSignalSpy spy(frame, SIGNAL(loadFinished(bool))); + + // Set existing url + frame->setUrl(url); + expectedLoadFinishedCount++; + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(frame->baseUrl(), url); + + // Set empty url + frame->setUrl(QUrl()); + expectedLoadFinishedCount++; + + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), aboutBlank); + QCOMPARE(frame->requestedUrl(), QUrl()); + QCOMPARE(frame->baseUrl(), aboutBlank); + + // Set existing url + frame->setUrl(url); + expectedLoadFinishedCount++; + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(frame->baseUrl(), url); + + // Load empty url + frame->load(QUrl()); + expectedLoadFinishedCount++; + + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), aboutBlank); + QCOMPARE(frame->requestedUrl(), QUrl()); + QCOMPARE(frame->baseUrl(), aboutBlank); +} + +void tst_QWebFrame::setUrlToInvalid() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + const QUrl invalidUrl("http:/example.com"); + QVERIFY(!invalidUrl.isEmpty()); + QVERIFY(invalidUrl != QUrl()); + + // QWebFrame will do its best to accept the URL, possible converting it to a valid equivalent URL. + const QUrl validUrl("http://example.com/"); + frame->setUrl(invalidUrl); + QCOMPARE(frame->url(), validUrl); + QCOMPARE(frame->requestedUrl(), validUrl); + QCOMPARE(frame->baseUrl(), validUrl); + + // QUrls equivalent to QUrl() will be treated as such. + const QUrl aboutBlank("about:blank"); + const QUrl anotherInvalidUrl("1http://bugs.webkit.org"); + QVERIFY(!anotherInvalidUrl.isEmpty()); // and they are not necessarily empty. + QVERIFY(!anotherInvalidUrl.isValid()); + QCOMPARE(anotherInvalidUrl.toEncoded(), QUrl().toEncoded()); + + frame->setUrl(anotherInvalidUrl); + QCOMPARE(frame->url(), aboutBlank); + QCOMPARE(frame->requestedUrl().toEncoded(), anotherInvalidUrl.toEncoded()); + QCOMPARE(frame->baseUrl(), aboutBlank); +} + +void tst_QWebFrame::setUrlHistory() +{ + const QUrl aboutBlank("about:blank"); + QUrl url; + int expectedLoadFinishedCount = 0; + QWebFrame* frame = m_page->mainFrame(); + QSignalSpy spy(frame, SIGNAL(loadFinished(bool))); + + QCOMPARE(m_page->history()->count(), 0); + + frame->setUrl(QUrl()); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), aboutBlank); + QCOMPARE(frame->requestedUrl(), QUrl()); + QCOMPARE(m_page->history()->count(), 0); + + url = QUrl("http://non.existent/"); + frame->setUrl(url); + ::waitForSignal(m_page, SIGNAL(loadFinished(bool))); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(m_page->history()->count(), 0); + + url = QUrl("qrc:/test1.html"); + frame->setUrl(url); + ::waitForSignal(m_page, SIGNAL(loadFinished(bool))); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(m_page->history()->count(), 1); + + frame->setUrl(QUrl()); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), aboutBlank); + QCOMPARE(frame->requestedUrl(), QUrl()); + QCOMPARE(m_page->history()->count(), 1); + + // Loading same page as current in history, so history count doesn't change. + url = QUrl("qrc:/test1.html"); + frame->setUrl(url); + ::waitForSignal(m_page, SIGNAL(loadFinished(bool))); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(m_page->history()->count(), 1); + + url = QUrl("qrc:/test2.html"); + frame->setUrl(url); + ::waitForSignal(m_page, SIGNAL(loadFinished(bool))); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(m_page->history()->count(), 2); +} + +void tst_QWebFrame::setUrlUsingStateObject() +{ + const QUrl aboutBlank("about:blank"); + QUrl url; + QWebFrame* frame = m_page->mainFrame(); + QSignalSpy urlChangedSpy(frame, SIGNAL(urlChanged(QUrl))); + int expectedUrlChangeCount = 0; + + QCOMPARE(m_page->history()->count(), 0); + + url = QUrl("qrc:/test1.html"); + frame->setUrl(url); + waitForSignal(m_page, SIGNAL(loadFinished(bool))); + expectedUrlChangeCount++; + QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QCOMPARE(frame->url(), url); + QCOMPARE(m_page->history()->count(), 1); + + frame->evaluateJavaScript("window.history.pushState(null,'push', 'navigate/to/here')"); + expectedUrlChangeCount++; + QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QCOMPARE(frame->url(), QUrl("qrc:/navigate/to/here")); + QCOMPARE(m_page->history()->count(), 2); + QVERIFY(m_page->history()->canGoBack()); + + frame->evaluateJavaScript("window.history.replaceState(null,'replace', 'another/location')"); + expectedUrlChangeCount++; + QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QCOMPARE(frame->url(), QUrl("qrc:/navigate/to/another/location")); + QCOMPARE(m_page->history()->count(), 2); + QVERIFY(!m_page->history()->canGoForward()); + QVERIFY(m_page->history()->canGoBack()); + + frame->evaluateJavaScript("window.history.back()"); + QTest::qWait(100); + expectedUrlChangeCount++; + QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount); + QCOMPARE(frame->url(), QUrl("qrc:/test1.html")); + QVERIFY(m_page->history()->canGoForward()); + QVERIFY(!m_page->history()->canGoBack()); +} + +void tst_QWebFrame::setUrlSameUrl() +{ + const QUrl url1("qrc:/test1.html"); + const QUrl url2("qrc:/test2.html"); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + FakeNetworkManager* networkManager = new FakeNetworkManager(&page); + page.setNetworkAccessManager(networkManager); + + QSignalSpy spy(frame, SIGNAL(loadFinished(bool))); + + frame->setUrl(url1); + waitForSignal(frame, SIGNAL(loadFinished(bool))); + QVERIFY(frame->url() != url1); // Nota bene: our QNAM redirects url1 to url2 + QCOMPARE(frame->url(), url2); + QCOMPARE(spy.count(), 1); + + frame->setUrl(url1); + waitForSignal(frame, SIGNAL(loadFinished(bool))); + QVERIFY(frame->url() != url1); + QCOMPARE(frame->url(), url2); + QCOMPARE(spy.count(), 2); + + // Now a case without redirect. The existing behavior we have for setUrl() + // is more like a "clear(); load()", so the page will be loaded again, even + // if urlToBeLoaded == url(). This test should be changed if we want to + // make setUrl() early return in this case. + frame->setUrl(url2); + waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), url2); + QCOMPARE(spy.count(), 3); + + frame->setUrl(url1); + waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), url2); + QCOMPARE(spy.count(), 4); +} + +static inline QUrl extractBaseUrl(const QUrl& url) +{ + return url.resolved(QUrl()); +} + +void tst_QWebFrame::setUrlThenLoads_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QUrl>("baseUrl"); + + QTest::newRow("resource file") << QUrl("qrc:/test1.html") << extractBaseUrl(QUrl("qrc:/test1.html")); + QTest::newRow("base specified in HTML") << QUrl("data:text/html,<head><base href=\"http://different.base/\"></head>") << QUrl("http://different.base/"); +} + +void tst_QWebFrame::setUrlThenLoads() +{ + QFETCH(QUrl, url); + QFETCH(QUrl, baseUrl); + QWebFrame* frame = m_page->mainFrame(); + QSignalSpy urlChangedSpy(frame, SIGNAL(urlChanged(QUrl))); + QSignalSpy startedSpy(frame, SIGNAL(loadStarted())); + QSignalSpy finishedSpy(frame, SIGNAL(loadFinished(bool))); + + frame->setUrl(url); + QCOMPARE(startedSpy.count(), 1); + ::waitForSignal(frame, SIGNAL(urlChanged(QUrl))); + QCOMPARE(urlChangedSpy.count(), 1); + QVERIFY(finishedSpy.at(0).first().toBool()); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(frame->baseUrl(), baseUrl); + + const QUrl urlToLoad1("qrc:/test2.html"); + const QUrl urlToLoad2("qrc:/test1.html"); + + // Just after first load. URL didn't changed yet. + frame->load(urlToLoad1); + QCOMPARE(startedSpy.count(), 2); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), urlToLoad1); + QCOMPARE(frame->baseUrl(), baseUrl); + + // After first URL changed. + ::waitForSignal(frame, SIGNAL(urlChanged(QUrl))); + QCOMPARE(urlChangedSpy.count(), 2); + QVERIFY(finishedSpy.at(1).first().toBool()); + QCOMPARE(frame->url(), urlToLoad1); + QCOMPARE(frame->requestedUrl(), urlToLoad1); + QCOMPARE(frame->baseUrl(), extractBaseUrl(urlToLoad1)); + + // Just after second load. URL didn't changed yet. + frame->load(urlToLoad2); + QCOMPARE(startedSpy.count(), 3); + QCOMPARE(frame->url(), urlToLoad1); + QCOMPARE(frame->requestedUrl(), urlToLoad2); + QCOMPARE(frame->baseUrl(), extractBaseUrl(urlToLoad1)); + + // After second URL changed. + ::waitForSignal(frame, SIGNAL(urlChanged(QUrl))); + QCOMPARE(urlChangedSpy.count(), 3); + QVERIFY(finishedSpy.at(2).first().toBool()); + QCOMPARE(frame->url(), urlToLoad2); + QCOMPARE(frame->requestedUrl(), urlToLoad2); + QCOMPARE(frame->baseUrl(), extractBaseUrl(urlToLoad2)); +} + +void tst_QWebFrame::loadFinishedAfterNotFoundError() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); + FakeNetworkManager* networkManager = new FakeNetworkManager(&page); + page.setNetworkAccessManager(networkManager); + + frame->setUrl(FakeReply::urlFor404ErrorWithoutContents); + QTRY_COMPARE(spy.count(), 1); + const bool wasLoadOk = spy.at(0).at(0).toBool(); + QVERIFY(!wasLoadOk); +} + +class URLSetter : public QObject { + Q_OBJECT + +public: + enum Signal { + LoadStarted, + LoadFinished, + ProvisionalLoad + }; + + enum Type { + UseLoad, + UseSetUrl + }; + + URLSetter(QWebFrame*, Signal, Type, const QUrl&); + +public Q_SLOTS: + void execute(); + +Q_SIGNALS: + void finished(); + +private: + QWebFrame* m_frame; + QUrl m_url; + Type m_type; +}; + +Q_DECLARE_METATYPE(URLSetter::Signal) +Q_DECLARE_METATYPE(URLSetter::Type) + +URLSetter::URLSetter(QWebFrame* frame, Signal signal, URLSetter::Type type, const QUrl& url) + : m_frame(frame), m_url(url), m_type(type) +{ + if (signal == LoadStarted) + connect(m_frame, SIGNAL(loadStarted()), SLOT(execute())); + else if (signal == LoadFinished) + connect(m_frame, SIGNAL(loadFinished(bool)), SLOT(execute())); + else + connect(m_frame, SIGNAL(provisionalLoad()), SLOT(execute())); +} + +void URLSetter::execute() +{ + // We track only the first emission. + m_frame->disconnect(this); + if (m_type == URLSetter::UseLoad) + m_frame->load(m_url); + else + m_frame->setUrl(m_url); + connect(m_frame, SIGNAL(loadFinished(bool)), SIGNAL(finished())); +} + +void tst_QWebFrame::loadInSignalHandlers_data() +{ + QTest::addColumn<URLSetter::Type>("type"); + QTest::addColumn<URLSetter::Signal>("signal"); + QTest::addColumn<QUrl>("url"); + + const QUrl validUrl("qrc:/test2.html"); + const QUrl invalidUrl("qrc:/invalid"); + + QTest::newRow("call load() in loadStarted() after valid url") << URLSetter::UseLoad << URLSetter::LoadStarted << validUrl; + QTest::newRow("call load() in loadStarted() after invalid url") << URLSetter::UseLoad << URLSetter::LoadStarted << invalidUrl; + QTest::newRow("call load() in loadFinished() after valid url") << URLSetter::UseLoad << URLSetter::LoadFinished << validUrl; + QTest::newRow("call load() in loadFinished() after invalid url") << URLSetter::UseLoad << URLSetter::LoadFinished << invalidUrl; + QTest::newRow("call load() in provisionalLoad() after valid url") << URLSetter::UseLoad << URLSetter::ProvisionalLoad << validUrl; + QTest::newRow("call load() in provisionalLoad() after invalid url") << URLSetter::UseLoad << URLSetter::ProvisionalLoad << invalidUrl; + + QTest::newRow("call setUrl() in loadStarted() after valid url") << URLSetter::UseSetUrl << URLSetter::LoadStarted << validUrl; + QTest::newRow("call setUrl() in loadStarted() after invalid url") << URLSetter::UseSetUrl << URLSetter::LoadStarted << invalidUrl; + QTest::newRow("call setUrl() in loadFinished() after valid url") << URLSetter::UseSetUrl << URLSetter::LoadFinished << validUrl; + QTest::newRow("call setUrl() in loadFinished() after invalid url") << URLSetter::UseSetUrl << URLSetter::LoadFinished << invalidUrl; + QTest::newRow("call setUrl() in provisionalLoad() after valid url") << URLSetter::UseSetUrl << URLSetter::ProvisionalLoad << validUrl; + QTest::newRow("call setUrl() in provisionalLoad() after invalid url") << URLSetter::UseSetUrl << URLSetter::ProvisionalLoad << invalidUrl; +} + +void tst_QWebFrame::loadInSignalHandlers() +{ + QFETCH(URLSetter::Type, type); + QFETCH(URLSetter::Signal, signal); + QFETCH(QUrl, url); + + QWebFrame* frame = m_page->mainFrame(); + const QUrl urlForSetter("qrc:/test1.html"); + URLSetter setter(frame, signal, type, urlForSetter); + + frame->load(url); + waitForSignal(&setter, SIGNAL(finished()), 200); + QCOMPARE(frame->url(), urlForSetter); +} + +QTEST_MAIN(tst_QWebFrame) +#include "tst_qwebframe.moc" diff --git a/tests/widgets/qwebengineframe/tst_qwebengineframe.qrc b/tests/widgets/qwebengineframe/tst_qwebengineframe.qrc new file mode 100644 index 000000000..2a7d0b9c2 --- /dev/null +++ b/tests/widgets/qwebengineframe/tst_qwebengineframe.qrc @@ -0,0 +1,10 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/"> +<file alias="image.png">resources/image.png</file> +<file alias="style.css">resources/style.css</file> +<file alias="test1.html">resources/test1.html</file> +<file alias="test2.html">resources/test2.html</file> +<file alias="testiframe.html">resources/testiframe.html</file> +<file alias="testiframe2.html">resources/testiframe2.html</file> +</qresource> +</RCC> diff --git a/tests/widgets/qwebenginehistory/qwebenginehistory.pro b/tests/widgets/qwebenginehistory/qwebenginehistory.pro new file mode 100644 index 000000000..ff6c49628 --- /dev/null +++ b/tests/widgets/qwebenginehistory/qwebenginehistory.pro @@ -0,0 +1,2 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc diff --git a/tests/widgets/qwebenginehistory/resources/page1.html b/tests/widgets/qwebenginehistory/resources/page1.html new file mode 100644 index 000000000..82fa4aff1 --- /dev/null +++ b/tests/widgets/qwebenginehistory/resources/page1.html @@ -0,0 +1 @@ +<title>page1</title><body><h1>page1</h1></body> diff --git a/tests/widgets/qwebenginehistory/resources/page2.html b/tests/widgets/qwebenginehistory/resources/page2.html new file mode 100644 index 000000000..5307bdcfd --- /dev/null +++ b/tests/widgets/qwebenginehistory/resources/page2.html @@ -0,0 +1 @@ +<title>page2</title><body><h1>page2</h1></body> diff --git a/tests/widgets/qwebenginehistory/resources/page3.html b/tests/widgets/qwebenginehistory/resources/page3.html new file mode 100644 index 000000000..4e5547c7e --- /dev/null +++ b/tests/widgets/qwebenginehistory/resources/page3.html @@ -0,0 +1 @@ +<title>page3</title><body><h1>page3</h1></body> diff --git a/tests/widgets/qwebenginehistory/resources/page4.html b/tests/widgets/qwebenginehistory/resources/page4.html new file mode 100644 index 000000000..3c57aeddc --- /dev/null +++ b/tests/widgets/qwebenginehistory/resources/page4.html @@ -0,0 +1 @@ +<title>page4</title><body><h1>page4</h1></body> diff --git a/tests/widgets/qwebenginehistory/resources/page5.html b/tests/widgets/qwebenginehistory/resources/page5.html new file mode 100644 index 000000000..859355279 --- /dev/null +++ b/tests/widgets/qwebenginehistory/resources/page5.html @@ -0,0 +1 @@ +<title>page5</title><body><h1>page5</h1></body> diff --git a/tests/widgets/qwebenginehistory/resources/page6.html b/tests/widgets/qwebenginehistory/resources/page6.html new file mode 100644 index 000000000..c5bbc6f79 --- /dev/null +++ b/tests/widgets/qwebenginehistory/resources/page6.html @@ -0,0 +1 @@ +<title>page6</title><body><h1>page6</h1></body> diff --git a/tests/widgets/qwebenginehistory/tst_qwebenginehistory.cpp b/tests/widgets/qwebenginehistory/tst_qwebenginehistory.cpp new file mode 100644 index 000000000..43474ae66 --- /dev/null +++ b/tests/widgets/qwebenginehistory/tst_qwebenginehistory.cpp @@ -0,0 +1,528 @@ +/* + Copyright (C) 2008 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 <QtTest/QtTest> +#include <QAction> + +#include "../util.h" +#include "qwebpage.h" +#include "qwebview.h" +#include "qwebframe.h" +#include "qwebhistory.h" +#include "qdebug.h" + +class tst_QWebHistory : public QObject +{ + Q_OBJECT + +public: + tst_QWebHistory(); + virtual ~tst_QWebHistory(); + +protected : + void loadPage(int nr) + { + frame->load(QUrl("qrc:/resources/page" + QString::number(nr) + ".html")); + loadFinishedBarrier->ensureSignalEmitted(); + } + +public Q_SLOTS: + void init(); + void cleanup(); + +private Q_SLOTS: + void title(); + void count(); + void back(); + void forward(); + void itemAt(); + void goToItem(); + void items(); + void serialize_1(); //QWebHistory countity + void serialize_2(); //QWebHistory index + void serialize_3(); //QWebHistoryItem + // Those tests shouldn't crash + void saveAndRestore_crash_1(); + void saveAndRestore_crash_2(); + void saveAndRestore_crash_3(); + void saveAndRestore_crash_4(); + + void popPushState_data(); + void popPushState(); + void clear(); + void restoreIncompatibleVersion1(); + + +private: + QWebPage* page; + QWebFrame* frame; + QWebHistory* hist; + QScopedPointer<SignalBarrier> loadFinishedBarrier; + int histsize; +}; + +tst_QWebHistory::tst_QWebHistory() +{ +} + +tst_QWebHistory::~tst_QWebHistory() +{ +} + +void tst_QWebHistory::init() +{ + page = new QWebPage(this); + frame = page->mainFrame(); + loadFinishedBarrier.reset(new SignalBarrier(frame, SIGNAL(loadFinished(bool)))); + + for (int i = 1;i < 6;i++) { + loadPage(i); + } + hist = page->history(); + histsize = 5; +} + +void tst_QWebHistory::cleanup() +{ + loadFinishedBarrier.reset(); + delete page; +} + +/** + * Check QWebHistoryItem::title() method + */ +void tst_QWebHistory::title() +{ + QCOMPARE(hist->currentItem().title(), QString("page5")); +} + +/** + * Check QWebHistory::count() method + */ +void tst_QWebHistory::count() +{ + QCOMPARE(hist->count(), histsize); +} + +/** + * Check QWebHistory::back() method + */ +void tst_QWebHistory::back() +{ + for (int i = histsize;i > 1;i--) { + QCOMPARE(page->mainFrame()->toPlainText(), QString("page") + QString::number(i)); + hist->back(); + loadFinishedBarrier->ensureSignalEmitted(); + } + //try one more time (too many). crash test + hist->back(); + QCOMPARE(page->mainFrame()->toPlainText(), QString("page1")); +} + +/** + * Check QWebHistory::forward() method + */ +void tst_QWebHistory::forward() +{ + //rewind history :-) + while (hist->canGoBack()) { + hist->back(); + loadFinishedBarrier->ensureSignalEmitted(); + } + + for (int i = 1;i < histsize;i++) { + QCOMPARE(page->mainFrame()->toPlainText(), QString("page") + QString::number(i)); + hist->forward(); + loadFinishedBarrier->ensureSignalEmitted(); + } + //try one more time (too many). crash test + hist->forward(); + QCOMPARE(page->mainFrame()->toPlainText(), QString("page") + QString::number(histsize)); +} + +/** + * Check QWebHistory::itemAt() method + */ +void tst_QWebHistory::itemAt() +{ + for (int i = 1;i < histsize;i++) { + QCOMPARE(hist->itemAt(i - 1).title(), QString("page") + QString::number(i)); + QVERIFY(hist->itemAt(i - 1).isValid()); + } + //check out of range values + QVERIFY(!hist->itemAt(-1).isValid()); + QVERIFY(!hist->itemAt(histsize).isValid()); +} + +/** + * Check QWebHistory::goToItem() method + */ +void tst_QWebHistory::goToItem() +{ + QWebHistoryItem current = hist->currentItem(); + hist->back(); + loadFinishedBarrier->ensureSignalEmitted(); + hist->back(); + loadFinishedBarrier->ensureSignalEmitted(); + QVERIFY(hist->currentItem().title() != current.title()); + hist->goToItem(current); + loadFinishedBarrier->ensureSignalEmitted(); + QCOMPARE(hist->currentItem().title(), current.title()); +} + +/** + * Check QWebHistory::items() method + */ +void tst_QWebHistory::items() +{ + QList<QWebHistoryItem> items = hist->items(); + //check count + QCOMPARE(histsize, items.count()); + + //check order + for (int i = 1;i <= histsize;i++) { + QCOMPARE(items.at(i - 1).title(), QString("page") + QString::number(i)); + } +} + +/** + * Check history state after serialization (pickle, persistent..) method + * Checks history size, history order + */ +void tst_QWebHistory::serialize_1() +{ + QByteArray tmp; //buffer + QDataStream save(&tmp, QIODevice::WriteOnly); //here data will be saved + QDataStream load(&tmp, QIODevice::ReadOnly); //from here data will be loaded + + save << *hist; + QVERIFY(save.status() == QDataStream::Ok); + QCOMPARE(hist->count(), histsize); + + //check size of history + //load next page to find differences + loadPage(6); + QCOMPARE(hist->count(), histsize + 1); + load >> *hist; + QVERIFY(load.status() == QDataStream::Ok); + QCOMPARE(hist->count(), histsize); + + //check order of historyItems + QList<QWebHistoryItem> items = hist->items(); + for (int i = 1;i <= histsize;i++) { + QCOMPARE(items.at(i - 1).title(), QString("page") + QString::number(i)); + } +} + +/** + * Check history state after serialization (pickle, persistent..) method + * Checks history currentIndex value + */ +void tst_QWebHistory::serialize_2() +{ + QByteArray tmp; //buffer + QDataStream save(&tmp, QIODevice::WriteOnly); //here data will be saved + QDataStream load(&tmp, QIODevice::ReadOnly); //from here data will be loaded + + // Force a "same document" navigation. + frame->load(frame->url().toString() + QLatin1String("#dummyAnchor")); + + int initialCurrentIndex = hist->currentItemIndex(); + + hist->back(); + loadFinishedBarrier->ensureSignalEmitted(); + hist->back(); + loadFinishedBarrier->ensureSignalEmitted(); + hist->back(); + loadFinishedBarrier->ensureSignalEmitted(); + //check if current index was changed (make sure that it is not last item) + QVERIFY(hist->currentItemIndex() != initialCurrentIndex); + //save current index + int oldCurrentIndex = hist->currentItemIndex(); + + save << *hist; + QVERIFY(save.status() == QDataStream::Ok); + load >> *hist; + QVERIFY(load.status() == QDataStream::Ok); + + //check current index + QCOMPARE(hist->currentItemIndex(), oldCurrentIndex); + + hist->forward(); + loadFinishedBarrier->ensureSignalEmitted(); + hist->forward(); + loadFinishedBarrier->ensureSignalEmitted(); + hist->forward(); + loadFinishedBarrier->ensureSignalEmitted(); + QCOMPARE(hist->currentItemIndex(), initialCurrentIndex); +} + +/** + * Check history state after serialization (pickle, persistent..) method + * Checks QWebHistoryItem public property after serialization + */ +void tst_QWebHistory::serialize_3() +{ + QByteArray tmp; //buffer + QDataStream save(&tmp, QIODevice::WriteOnly); //here data will be saved + QDataStream load(&tmp, QIODevice::ReadOnly); //from here data will be loaded + + //prepare two different history items + QWebHistoryItem a = hist->currentItem(); + a.setUserData("A - user data"); + + //check properties BEFORE serialization + QString title(a.title()); + QDateTime lastVisited(a.lastVisited()); + QUrl originalUrl(a.originalUrl()); + QUrl url(a.url()); + QVariant userData(a.userData()); + + save << *hist; + QVERIFY(save.status() == QDataStream::Ok); + QVERIFY(!load.atEnd()); + hist->clear(); + QVERIFY(hist->count() == 1); + load >> *hist; + QVERIFY(load.status() == QDataStream::Ok); + QWebHistoryItem b = hist->currentItem(); + + //check properties AFTER serialization + QCOMPARE(b.title(), title); + QCOMPARE(b.lastVisited(), lastVisited); + QCOMPARE(b.originalUrl(), originalUrl); + QCOMPARE(b.url(), url); + QCOMPARE(b.userData(), userData); + + //Check if all data was read + QVERIFY(load.atEnd()); +} + +static void saveHistory(QWebHistory* history, QByteArray* in) +{ + in->clear(); + QDataStream save(in, QIODevice::WriteOnly); + save << *history; +} + +static void restoreHistory(QWebHistory* history, QByteArray* out) +{ + QDataStream load(out, QIODevice::ReadOnly); + load >> *history; +} + +void tst_QWebHistory::saveAndRestore_crash_1() +{ + QByteArray buffer; + saveHistory(hist, &buffer); + for (unsigned i = 0; i < 5; i++) { + restoreHistory(hist, &buffer); + saveHistory(hist, &buffer); + } +} + +void tst_QWebHistory::saveAndRestore_crash_2() +{ + QByteArray buffer; + saveHistory(hist, &buffer); + QWebPage* page2 = new QWebPage(this); + QWebHistory* hist2 = page2->history(); + for (unsigned i = 0; i < 5; i++) { + restoreHistory(hist2, &buffer); + saveHistory(hist2, &buffer); + } + delete page2; +} + +void tst_QWebHistory::saveAndRestore_crash_3() +{ + QByteArray buffer; + saveHistory(hist, &buffer); + QWebPage* page2 = new QWebPage(this); + QWebHistory* hist1 = hist; + QWebHistory* hist2 = page2->history(); + for (unsigned i = 0; i < 5; i++) { + restoreHistory(hist1, &buffer); + restoreHistory(hist2, &buffer); + QVERIFY(hist1->count() == hist2->count()); + QVERIFY(hist1->count() == histsize); + hist2->back(); + saveHistory(hist2, &buffer); + hist2->clear(); + } + delete page2; +} + +void tst_QWebHistory::saveAndRestore_crash_4() +{ + QByteArray buffer; + saveHistory(hist, &buffer); + + QWebPage* page2 = new QWebPage(this); + // The initial crash was in PageCache. + page2->settings()->setMaximumPagesInCache(3); + + // Load the history in a new page, waiting for the load to finish. + QEventLoop waitForLoadFinished; + QObject::connect(page2, SIGNAL(loadFinished(bool)), &waitForLoadFinished, SLOT(quit()), Qt::QueuedConnection); + QDataStream load(&buffer, QIODevice::ReadOnly); + load >> *page2->history(); + waitForLoadFinished.exec(); + + delete page2; + // Give some time for the PageCache cleanup 0-timer to fire. + QTest::qWait(50); +} + +void tst_QWebHistory::popPushState_data() +{ + QTest::addColumn<QString>("script"); + QTest::newRow("pushState") << "history.pushState(123, \"foo\");"; + QTest::newRow("replaceState") << "history.replaceState(\"a\", \"b\");"; + QTest::newRow("back") << "history.back();"; + QTest::newRow("forward") << "history.forward();"; + QTest::newRow("clearState") << "history.clearState();"; +} + +/** Crash test, WebKit bug 38840 (https://bugs.webkit.org/show_bug.cgi?id=38840) */ +void tst_QWebHistory::popPushState() +{ + QFETCH(QString, script); + QWebPage page; + page.mainFrame()->setHtml("<html><body>long live Qt!</body></html>"); + page.mainFrame()->evaluateJavaScript(script); +} + +/** ::clear */ +void tst_QWebHistory::clear() +{ + QByteArray buffer; + + QAction* actionBack = page->action(QWebPage::Back); + QVERIFY(actionBack->isEnabled()); + saveHistory(hist, &buffer); + QVERIFY(hist->count() > 1); + hist->clear(); + QVERIFY(hist->count() == 1); // Leave current item. + QVERIFY(!actionBack->isEnabled()); + + QWebPage* page2 = new QWebPage(this); + QWebHistory* hist2 = page2->history(); + QVERIFY(hist2->count() == 0); + hist2->clear(); + QVERIFY(hist2->count() == 0); // Do not change anything. + delete page2; +} + +// static void dumpCurrentVersion(QWebHistory* history) +// { +// QByteArray buffer; +// saveHistory(history, &buffer); +// printf(" static const char version1Dump[] = {"); +// for (int i = 0; i < buffer.size(); ++i) { +// bool newLine = !(i % 15); +// bool last = i == buffer.size() - 1; +// printf("%s0x%.2x%s", newLine ? "\n " : "", (unsigned char)buffer[i], last ? "" : ", "); +// } +// printf("};\n"); +// } + +void tst_QWebHistory::restoreIncompatibleVersion1() +{ + // Uncomment this code to generate a dump similar to the one below with the current stream version. + // dumpCurrentVersion(hist); + static const unsigned char version1Dump[] = { + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x32, 0x00, 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x72, 0x00, 0x65, + 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00, + 0x2f, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x68, + 0x00, 0x74, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x61, 0x00, + 0x67, 0x00, 0x65, 0x00, 0x31, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, + 0x2f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, + 0x00, 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, + 0x31, 0x00, 0x2e, 0x00, 0x68, 0x00, 0x74, 0x00, 0x6d, 0x00, 0x6c, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, + 0x00, 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x72, 0x00, 0x65, 0x00, + 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00, 0x2f, + 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x68, 0x00, + 0x74, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, + 0x00, 0x65, 0x00, 0x32, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, + 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, + 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x32, + 0x00, 0x2e, 0x00, 0x68, 0x00, 0x74, 0x00, 0x6d, 0x00, 0x6c, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, + 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, + 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, + 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x33, 0x00, 0x2e, 0x00, 0x68, 0x00, 0x74, + 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, + 0x65, 0x00, 0x33, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x32, 0x00, 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, + 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, + 0x00, 0x73, 0x00, 0x2f, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x33, 0x00, + 0x2e, 0x00, 0x68, 0x00, 0x74, 0x00, 0x6d, 0x00, 0x6c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, + 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x71, + 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, + 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x70, + 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x68, 0x00, 0x74, 0x00, + 0x6d, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, + 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x32, 0x00, 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x72, + 0x00, 0x65, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, + 0x73, 0x00, 0x2f, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x34, 0x00, 0x2e, + 0x00, 0x68, 0x00, 0x74, 0x00, 0x6d, 0x00, 0x6c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x71, 0x00, + 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6f, + 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x70, 0x00, + 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x35, 0x00, 0x2e, 0x00, 0x68, 0x00, 0x74, 0x00, 0x6d, + 0x00, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, + 0x35, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x32, 0x00, 0x71, 0x00, 0x72, 0x00, 0x63, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x72, 0x00, + 0x65, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, + 0x00, 0x2f, 0x00, 0x70, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x35, 0x00, 0x2e, 0x00, + 0x68, 0x00, 0x74, 0x00, 0x6d, 0x00, 0x6c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + QByteArray version1(reinterpret_cast<const char*>(version1Dump), sizeof(version1Dump)); + QDataStream stream(&version1, QIODevice::ReadOnly); + + // This should fail to load, the history should be cleared and the stream should be broken. + stream >> *hist; + QVERIFY(!hist->canGoBack()); + QVERIFY(!hist->canGoForward()); + QVERIFY(stream.status() == QDataStream::ReadCorruptData); +} + +QTEST_MAIN(tst_QWebHistory) +#include "tst_qwebhistory.moc" diff --git a/tests/widgets/qwebenginehistory/tst_qwebenginehistory.qrc b/tests/widgets/qwebenginehistory/tst_qwebenginehistory.qrc new file mode 100644 index 000000000..cdfe575a0 --- /dev/null +++ b/tests/widgets/qwebenginehistory/tst_qwebenginehistory.qrc @@ -0,0 +1,10 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file>resources/page1.html</file> + <file>resources/page2.html</file> + <file>resources/page3.html</file> + <file>resources/page4.html</file> + <file>resources/page5.html</file> + <file>resources/page6.html</file> +</qresource> +</RCC> diff --git a/tests/widgets/qwebenginehistoryinterface/qwebenginehistoryinterface.pro b/tests/widgets/qwebenginehistoryinterface/qwebenginehistoryinterface.pro new file mode 100644 index 000000000..ff6c49628 --- /dev/null +++ b/tests/widgets/qwebenginehistoryinterface/qwebenginehistoryinterface.pro @@ -0,0 +1,2 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc diff --git a/tests/widgets/qwebenginehistoryinterface/tst_qwebenginehistoryinterface.cpp b/tests/widgets/qwebenginehistoryinterface/tst_qwebenginehistoryinterface.cpp new file mode 100644 index 000000000..f4571cf74 --- /dev/null +++ b/tests/widgets/qwebenginehistoryinterface/tst_qwebenginehistoryinterface.cpp @@ -0,0 +1,96 @@ +/* + Copyright (C) 2008 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 <QtTest/QtTest> + +#include <qwebpage.h> +#include <qwebview.h> +#include <qwebframe.h> +#include <qwebelement.h> +#include <qwebhistoryinterface.h> +#include <QDebug> + +class tst_QWebHistoryInterface : public QObject +{ + Q_OBJECT + +public: + tst_QWebHistoryInterface(); + virtual ~tst_QWebHistoryInterface(); + +public Q_SLOTS: + void init(); + void cleanup(); + +private Q_SLOTS: + void visitedLinks(); + +private: + + +private: + QWebView* m_view; + QWebPage* m_page; +}; + +tst_QWebHistoryInterface::tst_QWebHistoryInterface() +{ +} + +tst_QWebHistoryInterface::~tst_QWebHistoryInterface() +{ +} + +void tst_QWebHistoryInterface::init() +{ + m_view = new QWebView(); + m_page = m_view->page(); +} + +void tst_QWebHistoryInterface::cleanup() +{ + delete m_view; +} + +class FakeHistoryImplementation : public QWebHistoryInterface { +public: + void addHistoryEntry(const QString&) {} + bool historyContains(const QString& url) const { + return url == QLatin1String("http://www.trolltech.com/"); + } +}; + + +/* + * Test that visited links are properly colored. http://www.trolltech.com is marked + * as visited, so the below website should have exactly one element in the a:visited + * state. + */ +void tst_QWebHistoryInterface::visitedLinks() +{ + QWebHistoryInterface::setDefaultInterface(new FakeHistoryImplementation); + m_view->setHtml("<html><style>:link{color:green}:visited{color:red}</style><body><a href='http://www.trolltech.com' id='vlink'>Trolltech</a></body></html>"); + QWebElement anchor = m_view->page()->mainFrame()->findFirstElement("a[id=vlink]"); + QString linkColor = anchor.styleProperty("color", QWebElement::ComputedStyle); + QCOMPARE(linkColor, QString::fromLatin1("rgb(255, 0, 0)")); +} + +QTEST_MAIN(tst_QWebHistoryInterface) +#include "tst_qwebhistoryinterface.moc" diff --git a/tests/widgets/qwebengineinspector/qwebengineinspector.pro b/tests/widgets/qwebengineinspector/qwebengineinspector.pro new file mode 100644 index 000000000..ff6c49628 --- /dev/null +++ b/tests/widgets/qwebengineinspector/qwebengineinspector.pro @@ -0,0 +1,2 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc diff --git a/tests/widgets/qwebengineinspector/tst_qwebengineinspector.cpp b/tests/widgets/qwebengineinspector/tst_qwebengineinspector.cpp new file mode 100644 index 000000000..37e62f67b --- /dev/null +++ b/tests/widgets/qwebengineinspector/tst_qwebengineinspector.cpp @@ -0,0 +1,75 @@ +/* + Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + + 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 <QtTest/QtTest> + +#include <qdir.h> +#include <qwebinspector.h> +#include <qwebpage.h> +#include <qwebsettings.h> + +class tst_QWebInspector : public QObject { + Q_OBJECT + +private Q_SLOTS: + void attachAndDestroyPageFirst(); + void attachAndDestroyInspectorFirst(); + void attachAndDestroyInternalInspector(); +}; + +void tst_QWebInspector::attachAndDestroyPageFirst() +{ + // External inspector + manual destruction of page first + QWebPage* page = new QWebPage(); + page->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); + QWebInspector* inspector = new QWebInspector(); + inspector->setPage(page); + page->updatePositionDependentActions(QPoint(0, 0)); + page->triggerAction(QWebPage::InspectElement); + + delete page; + delete inspector; +} + +void tst_QWebInspector::attachAndDestroyInspectorFirst() +{ + // External inspector + manual destruction of inspector first + QWebPage* page = new QWebPage(); + page->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); + QWebInspector* inspector = new QWebInspector(); + inspector->setPage(page); + page->updatePositionDependentActions(QPoint(0, 0)); + page->triggerAction(QWebPage::InspectElement); + + delete inspector; + delete page; +} + +void tst_QWebInspector::attachAndDestroyInternalInspector() +{ + // Internal inspector + QWebPage page; + page.settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); + page.updatePositionDependentActions(QPoint(0, 0)); + page.triggerAction(QWebPage::InspectElement); +} + +QTEST_MAIN(tst_QWebInspector) + +#include "tst_qwebinspector.moc" diff --git a/tests/widgets/qwebenginepage/qwebenginepage.pro b/tests/widgets/qwebenginepage/qwebenginepage.pro new file mode 100644 index 000000000..e56bbe8f7 --- /dev/null +++ b/tests/widgets/qwebenginepage/qwebenginepage.pro @@ -0,0 +1,3 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc +QT *= core-private gui-private diff --git a/tests/widgets/qwebenginepage/resources/content.html b/tests/widgets/qwebenginepage/resources/content.html new file mode 100644 index 000000000..360ad65ef --- /dev/null +++ b/tests/widgets/qwebenginepage/resources/content.html @@ -0,0 +1,5 @@ +<html> +<body> +<a>This is test content</a> +</body> +</html> diff --git a/tests/widgets/qwebenginepage/resources/frame_a.html b/tests/widgets/qwebenginepage/resources/frame_a.html new file mode 100644 index 000000000..9ff68f13a --- /dev/null +++ b/tests/widgets/qwebenginepage/resources/frame_a.html @@ -0,0 +1,2 @@ +<a href="http://google.com" target="frame_b"><img src="" width=100 height=100 alt="Google"></a> +<a href="http://yahoo.com" target="frame_b"><img src="" width=100 height=100 alt="Yahoo"></a> diff --git a/tests/widgets/qwebenginepage/resources/frame_c.html b/tests/widgets/qwebenginepage/resources/frame_c.html new file mode 100644 index 000000000..eba9ca0eb --- /dev/null +++ b/tests/widgets/qwebenginepage/resources/frame_c.html @@ -0,0 +1 @@ +<a href="content.html" target="frame_b"><img src="" width=100 height=100 alt="Google"></a> diff --git a/tests/widgets/qwebenginepage/resources/framedindex.html b/tests/widgets/qwebenginepage/resources/framedindex.html new file mode 100644 index 000000000..be4500483 --- /dev/null +++ b/tests/widgets/qwebenginepage/resources/framedindex.html @@ -0,0 +1,6 @@ +<html> +<frameset cols="25%,75%"> + <frame src="frame_c.html" name="frame_c"> + <frame src="frame_b.html" name="frame_b"> +</frameset> +</html> diff --git a/tests/widgets/qwebenginepage/resources/iframe.html b/tests/widgets/qwebenginepage/resources/iframe.html new file mode 100644 index 000000000..f17027c7a --- /dev/null +++ b/tests/widgets/qwebenginepage/resources/iframe.html @@ -0,0 +1,6 @@ +<html> +<body> +<p>top</p> +<iframe src="iframe2.html" width="80%" height="30%"/> +</body> +</html> diff --git a/tests/widgets/qwebenginepage/resources/iframe2.html b/tests/widgets/qwebenginepage/resources/iframe2.html new file mode 100644 index 000000000..758a44a2c --- /dev/null +++ b/tests/widgets/qwebenginepage/resources/iframe2.html @@ -0,0 +1,6 @@ +<html> +<body> +<p>another iframe</p> +<iframe src="iframe3.html" width="80%" height="30%"></iframe> +</body> +</html> diff --git a/tests/widgets/qwebenginepage/resources/iframe3.html b/tests/widgets/qwebenginepage/resources/iframe3.html new file mode 100644 index 000000000..ed6ac5b94 --- /dev/null +++ b/tests/widgets/qwebenginepage/resources/iframe3.html @@ -0,0 +1,5 @@ +<html> +<body> +<p>inner</p> +</body> +</html> diff --git a/tests/widgets/qwebenginepage/resources/index.html b/tests/widgets/qwebenginepage/resources/index.html new file mode 100644 index 000000000..638df364e --- /dev/null +++ b/tests/widgets/qwebenginepage/resources/index.html @@ -0,0 +1,6 @@ +<html> +<frameset cols="25%,75%"> + <frame src="frame_a.html" name="frame_a"> + <frame src="frame_b.html" name="frame_b"> +</frameset> +</html> diff --git a/tests/widgets/qwebenginepage/resources/script.html b/tests/widgets/qwebenginepage/resources/script.html new file mode 100644 index 000000000..ede986415 --- /dev/null +++ b/tests/widgets/qwebenginepage/resources/script.html @@ -0,0 +1,3 @@ +<html><head> +<script language="javascript" type="text/javascript" src="does_not_exist.js"></script> +</head></html> diff --git a/tests/widgets/qwebenginepage/resources/user.css b/tests/widgets/qwebenginepage/resources/user.css new file mode 100644 index 000000000..4ccb2f0fc --- /dev/null +++ b/tests/widgets/qwebenginepage/resources/user.css @@ -0,0 +1,3 @@ +p { + background-image: url('http://does.not/exist.png'); +}
\ No newline at end of file diff --git a/tests/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/widgets/qwebenginepage/tst_qwebenginepage.cpp new file mode 100644 index 000000000..a29f6c0f3 --- /dev/null +++ b/tests/widgets/qwebenginepage/tst_qwebenginepage.cpp @@ -0,0 +1,3393 @@ +/* + 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 "../WebCoreSupport/DumpRenderTreeSupportQt.h" +#include <QClipboard> +#include <QDir> +#include <QGraphicsWidget> +#include <QLineEdit> +#include <QMainWindow> +#include <QMenu> +#include <QMimeDatabase> +#include <QPushButton> +#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> + +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(); + void thirdPartyCookiePolicy(); + void contextMenuCopy(); + void contextMenuPopulatedOnce(); + void acceptNavigationRequest(); + 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(); + void multiplePageGroupsAndLocalStorage(); + void cursorMovements(); + void textSelection(); + void textEditing(); + void backActionUpdate(); + void frameAt(); + void requestCache(); + void loadCachedPage(); + void protectBindingsRuntimeObjectsFromCollector(); + void localURLSchemes(); + void testOptionalJSObjects(); + void testLocalStorageVisibility(); + void testEnablePersistentStorage(); + void consoleOutput(); + void inputMethods_data(); + void inputMethods(); + void inputMethodsTextFormat_data(); + void inputMethodsTextFormat(); + void defaultTextEncoding(); + void errorPageExtension(); + void errorPageExtensionInIFrames(); + void errorPageExtensionInFrameset(); + void errorPageExtensionLoadFinished(); + void userAgentApplicationName(); + void userAgentNewlineStripping(); + void undoActionHaveCustomText(); + + void viewModes(); + + void crashTests_LazyInitializationOfMainFrame(); + + void screenshot_data(); + void screenshot(); + +#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_MAC + void macCopyUnicodeToClipboard(); +#endif + +private: + QWebView* m_view; + QWebPage* m_page; + 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); +} + +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; +}; + +// [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; + W_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("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(WTF_USE_QT_MULTIMEDIA) && WTF_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 + W_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 necessary to call QWidget::setParent, + // which also takes a QWidget* instead of a QObject*. Therefore we need to + // upcast to T*, which is a QWidget. + static_cast<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; + +} + +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"); + 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")); +} + +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); + 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 beginning 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; +} + +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')"); +} + +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); +} + +void tst_QWebPage::errorPageExtensionLoadFinished() +{ + ErrorPage page; + m_view->setPage(&page); + + QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool))); + QSignalSpy spyFrameLoadFinished(m_view->page()->mainFrame(), SIGNAL(loadFinished(bool))); + + m_view->setUrl(QUrl("data:text/html,foo")); + QTRY_COMPARE(spyLoadFinished.count(), 1); + QTRY_COMPARE(spyFrameLoadFinished.count(), 1); + + const bool loadSucceded = spyLoadFinished.at(0).at(0).toBool(); + QVERIFY(loadSucceded); + const bool frameLoadSucceded = spyFrameLoadFinished.at(0).at(0).toBool(); + QVERIFY(frameLoadSucceded); + + m_view->page()->mainFrame()->setUrl(QUrl("http://non.existent/url")); + QTRY_COMPARE(spyLoadFinished.count(), 2); + QTRY_COMPARE(spyFrameLoadFinished.count(), 2); + + const bool nonExistantLoadSucceded = spyLoadFinished.at(1).at(0).toBool(); + QVERIFY(nonExistantLoadSucceded); + const bool nonExistantFrameLoadSucceded = spyFrameLoadFinished.at(1).at(0).toBool(); + QVERIFY(nonExistantFrameLoadSucceded); + + 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()) + W_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=\"data:text/html," + "<head><meta http-equiv='refresh' content='1'></head>foo \">" + "<frame src=\"data:text/html,bar\"></frameset>"), QUrl()); + QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool)))); + + QCOMPARE(networkManager->requests.count(), 2); + + QList<QWebFrame*> childFrames = m_page->mainFrame()->childFrames(); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=118660", Continue); + 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()); + } +} + +static QString getMimeTypeForExtension(const QString &ext) +{ + QMimeType mimeType = QMimeDatabase().mimeTypeForFile(QStringLiteral("filename.") + ext.toLower(), QMimeDatabase::MatchExtension); + if (mimeType.isValid() && !mimeType.isDefault()) + return mimeType.name(); + + return QString(); +} + +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"; + +#if ENABLE_MHTML + contentTypes << "application/x-mimearchive"; +#endif + + // Add supported image types... + Q_FOREACH (const QByteArray& imageType, QImageWriter::supportedImageFormats()) { + const QString mimeType = getMimeTypeForExtension(imageType); + if (!mimeType.isEmpty()) + contentTypes << mimeType; + } + + // 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()); +} + +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"))); +} + +#ifdef Q_OS_MAC +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::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()) { + QString entry = list.takeFirst()->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(); + + 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. + QVERIFY(requestedGeometry.width() == 0); + QVERIFY(requestedGeometry.height() == 0); + + requestedGeometry = page.createdWindows[1]->requestedGeometry; + // Check minimum size has been requested. + QVERIFY(requestedGeometry.width() == 100); + QVERIFY(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" diff --git a/tests/widgets/qwebenginepage/tst_qwebenginepage.qrc b/tests/widgets/qwebenginepage/tst_qwebenginepage.qrc new file mode 100644 index 000000000..994d71b43 --- /dev/null +++ b/tests/widgets/qwebenginepage/tst_qwebenginepage.qrc @@ -0,0 +1,14 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file>resources/index.html</file> + <file>resources/frame_a.html</file> + <file>resources/frame_c.html</file> + <file>resources/iframe.html</file> + <file>resources/iframe2.html</file> + <file>resources/iframe3.html</file> + <file>resources/framedindex.html</file> + <file>resources/content.html</file> + <file>resources/script.html</file> + <file>resources/user.css</file> +</qresource> +</RCC> diff --git a/tests/widgets/qwebengineview/qwebengineview.pro b/tests/widgets/qwebengineview/qwebengineview.pro new file mode 100644 index 000000000..ff6c49628 --- /dev/null +++ b/tests/widgets/qwebengineview/qwebengineview.pro @@ -0,0 +1,2 @@ +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc diff --git a/tests/widgets/qwebengineview/resources/frame_a.html b/tests/widgets/qwebengineview/resources/frame_a.html new file mode 100644 index 000000000..9ff68f13a --- /dev/null +++ b/tests/widgets/qwebengineview/resources/frame_a.html @@ -0,0 +1,2 @@ +<a href="http://google.com" target="frame_b"><img src="" width=100 height=100 alt="Google"></a> +<a href="http://yahoo.com" target="frame_b"><img src="" width=100 height=100 alt="Yahoo"></a> diff --git a/tests/widgets/qwebengineview/resources/index.html b/tests/widgets/qwebengineview/resources/index.html new file mode 100644 index 000000000..c53ad09a7 --- /dev/null +++ b/tests/widgets/qwebengineview/resources/index.html @@ -0,0 +1,4 @@ +<frameset cols="25%,75%"> + <frame src="frame_a.html" name="frame_a"> + <frame src="frame_b.html" name="frame_b"> +</frameset> diff --git a/tests/widgets/qwebengineview/resources/input_types.html b/tests/widgets/qwebengineview/resources/input_types.html new file mode 100644 index 000000000..2e893afae --- /dev/null +++ b/tests/widgets/qwebengineview/resources/input_types.html @@ -0,0 +1,9 @@ +<html><body> +<input type='text' maxlength='20' style='position: absolute; left: 10px; top: 0px; height: 50px; width: 100px;'/><br> +<input type='password' style='position: absolute; left: 10px; top: 50px; height: 50px; width: 100px;'/><br> +<input type='tel' style='position: absolute; left: 10px; top: 100px; height: 50px; width: 100px;'/><br> +<input type='number' style='position: absolute; left: 10px; top: 150px; height: 50px; width: 100px;'/><br> +<input type='email' style='position: absolute; left: 10px; top: 200px; height: 50px; width: 100px;'/><br> +<input type='url' style='position: absolute; left: 10px; top: 250px; height: 50px; width: 100px;'/><br> +<textarea style='position: absolute; left: 10px; top: 310px; height: 50px; width: 100px;' rows="2" cols="20">blah blah blah blah</textarea><br> +</body></html> diff --git a/tests/widgets/qwebengineview/resources/scrolltest_page.html b/tests/widgets/qwebengineview/resources/scrolltest_page.html new file mode 100644 index 000000000..18fcbbebe --- /dev/null +++ b/tests/widgets/qwebengineview/resources/scrolltest_page.html @@ -0,0 +1,6 @@ +<html> +<head><title>Scrolling test</title></head> +<body> + <div style="width: 1000px; height: 1000px; background-color: green"/> +</body> +</html> diff --git a/tests/widgets/qwebengineview/tst_qwebengineview.cpp b/tests/widgets/qwebengineview/tst_qwebengineview.cpp new file mode 100644 index 000000000..9d08f677d --- /dev/null +++ b/tests/widgets/qwebengineview/tst_qwebengineview.cpp @@ -0,0 +1,522 @@ +/* + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies) + Copyright (C) 2009 Torch Mobile Inc. + Copyright (C) 2009 Girish Ramakrishnan <girish@forwardbias.in> + + 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 <qtest.h> +#include "../util.h" + +#include <qpainter.h> +#include <qwebview.h> +#include <qwebpage.h> +#include <qnetworkrequest.h> +#include <qdiriterator.h> +#include <qwebelement.h> +#include <qwebframe.h> + +#define VERIFY_INPUTMETHOD_HINTS(actual, expect) \ + QVERIFY(actual == expect); + +class tst_QWebView : public QObject +{ + Q_OBJECT + +public Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + +private Q_SLOTS: + void renderingAfterMaxAndBack(); + void renderHints(); + void getWebKitVersion(); + + void reusePage_data(); + void reusePage(); + void microFocusCoordinates(); + void focusInputTypes(); + void horizontalScrollbarTest(); + + void crashTests(); +#if !(defined(WTF_USE_QT_MOBILE_THEME) && WTF_USE_QT_MOBILE_THEME) + void setPalette_data(); + void setPalette(); +#endif +}; + +// This will be called before the first test function is executed. +// It is only called once. +void tst_QWebView::initTestCase() +{ +} + +// This will be called after the last test function is executed. +// It is only called once. +void tst_QWebView::cleanupTestCase() +{ +} + +// This will be called before each test function is executed. +void tst_QWebView::init() +{ +} + +// This will be called after every test function. +void tst_QWebView::cleanup() +{ +} + +void tst_QWebView::renderHints() +{ + QWebView webView; + + // default is only text antialiasing + smooth pixmap transform + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); + QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); + QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); + + webView.setRenderHint(QPainter::Antialiasing, true); + QVERIFY(webView.renderHints() & QPainter::Antialiasing); + QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); + QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); + + webView.setRenderHint(QPainter::Antialiasing, false); + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); + QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); + QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); + + webView.setRenderHint(QPainter::SmoothPixmapTransform, true); + QVERIFY(!(webView.renderHints() & QPainter::Antialiasing)); + QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); + QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); + + webView.setRenderHint(QPainter::SmoothPixmapTransform, false); + QVERIFY(webView.renderHints() & QPainter::TextAntialiasing); + QVERIFY(!(webView.renderHints() & QPainter::SmoothPixmapTransform)); + QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing)); +} + +void tst_QWebView::getWebKitVersion() +{ + QVERIFY(qWebKitVersion().toDouble() > 0); +} + +void tst_QWebView::reusePage_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_QWebView::reusePage() +{ + if (!QDir(TESTS_SOURCE_DIR).exists()) + W_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); + QWebView* view1 = new QWebView; + QPointer<QWebPage> page = new QWebPage; + view1->setPage(page.data()); + page.data()->settings()->setAttribute(QWebSettings::PluginsEnabled, true); + QWebFrame* mainFrame = page.data()->mainFrame(); + mainFrame->setHtml(html, QUrl::fromLocalFile(TESTS_SOURCE_DIR)); + if (html.contains("</embed>")) { + // some reasonable time for the PluginStream to feed test.swf to flash and start painting + waitForSignal(view1, SIGNAL(loadFinished(bool)), 2000); + } + + view1->show(); + QTest::qWaitForWindowExposed(view1); + delete view1; + QVERIFY(page != 0); // deleting view must not have deleted the page, since it's not a child of view + + QWebView *view2 = new QWebView; + view2->setPage(page.data()); + view2->show(); // in Windowless mode, you should still be able to see the plugin here + QTest::qWaitForWindowExposed(view2); + delete view2; + + delete page.data(); // must not crash + + QDir::setCurrent(QApplication::applicationDirPath()); +} + +// Class used in crashTests +class WebViewCrashTest : public QObject { + Q_OBJECT + QWebView* m_view; +public: + bool m_executed; + + + WebViewCrashTest(QWebView* view) + : m_view(view) + , m_executed(false) + { + view->connect(view, SIGNAL(loadProgress(int)), this, SLOT(loading(int))); + } + +private Q_SLOTS: + void loading(int progress) + { + if (progress >= 20 && progress < 90) { + QVERIFY(!m_executed); + m_view->stop(); + m_executed = true; + } + } +}; + + +// Should not crash. +void tst_QWebView::crashTests() +{ + // Test if loading can be stopped in loadProgress handler without crash. + // Test page should have frames. + QWebView view; + WebViewCrashTest tester(&view); + QUrl url("qrc:///resources/index.html"); + view.load(url); + QTRY_VERIFY(tester.m_executed); // If fail it means that the test wasn't executed. +} + +void tst_QWebView::microFocusCoordinates() +{ + QWebPage* page = new QWebPage; + QWebView* webView = new QWebView; + webView->setPage( page ); + + page->mainFrame()->setHtml("<html><body>" \ + "<input type='text' id='input1' style='font--family: serif' value='' maxlength='20'/><br>" \ + "<canvas id='canvas1' width='500' height='500'></canvas>" \ + "<input type='password'/><br>" \ + "<canvas id='canvas2' width='500' height='500'></canvas>" \ + "</body></html>"); + + page->mainFrame()->setFocus(); + + QVariant initialMicroFocus = page->inputMethodQuery(Qt::ImMicroFocus); + QVERIFY(initialMicroFocus.isValid()); + + page->mainFrame()->scroll(0,50); + + QVariant currentMicroFocus = page->inputMethodQuery(Qt::ImMicroFocus); + QVERIFY(currentMicroFocus.isValid()); + + QCOMPARE(initialMicroFocus.toRect().translated(QPoint(0,-50)), currentMicroFocus.toRect()); +} + +void tst_QWebView::focusInputTypes() +{ + QWebView webView; + webView.show(); + QTest::qWaitForWindowExposed(&webView); + + QUrl url("qrc:///resources/input_types.html"); + QWebFrame* const mainFrame = webView.page()->mainFrame(); + mainFrame->load(url); + mainFrame->setFocus(); + + QVERIFY(waitForSignal(&webView, SIGNAL(loadFinished(bool)))); + + // 'text' type + QWebElement inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=text]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + QVERIFY(webView.inputMethodHints() == Qt::ImhNone); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'password' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=password]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhHiddenText); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'tel' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=tel]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhDialableCharactersOnly); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'number' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=number]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhDigitsOnly); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'email' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=email]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhEmailCharactersOnly); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'url' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=url]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhUrlCharactersOnly); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'password' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=password]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhHiddenText); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'text' type + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=text]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + QVERIFY(webView.inputMethodHints() == Qt::ImhNone); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'password' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=password]")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhHiddenText); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); + + // 'text area' field + inputElement = mainFrame->documentElement().findFirst(QLatin1String("textarea")); + QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center()); + QVERIFY(webView.inputMethodHints() == Qt::ImhNone); + QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled)); +} + +void tst_QWebView::horizontalScrollbarTest() +{ + QWebView webView; + webView.resize(600, 600); + webView.show(); + QTest::qWaitForWindowExposed(&webView); + + QUrl url("qrc:///resources/scrolltest_page.html"); + QWebFrame* const mainFrame = webView.page()->mainFrame(); + mainFrame->load(url); + mainFrame->setFocus(); + + QVERIFY(waitForSignal(&webView, SIGNAL(loadFinished(bool)))); + + QVERIFY(webView.page()->mainFrame()->scrollPosition() == QPoint(0, 0)); + + // Note: The test below assumes that the layout direction is Qt::LeftToRight. + QTest::mouseClick(&webView, Qt::LeftButton, 0, QPoint(550, 595)); + QVERIFY(webView.page()->mainFrame()->scrollPosition().x() > 0); + + // Note: The test below assumes that the layout direction is Qt::LeftToRight. + QTest::mouseClick(&webView, Qt::LeftButton, 0, QPoint(20, 595)); + QVERIFY(webView.page()->mainFrame()->scrollPosition() == QPoint(0, 0)); +} + + +#if !(defined(WTF_USE_QT_MOBILE_THEME) && WTF_USE_QT_MOBILE_THEME) +void tst_QWebView::setPalette_data() +{ + QTest::addColumn<bool>("active"); + QTest::addColumn<bool>("background"); + QTest::newRow("activeBG") << true << true; + QTest::newRow("activeFG") << true << false; + QTest::newRow("inactiveBG") << false << true; + QTest::newRow("inactiveFG") << false << false; +} + +// Render a QWebView to a QImage twice, each time with a different palette set, +// verify that images rendered are not the same, confirming WebCore usage of +// custom palette on selections. +void tst_QWebView::setPalette() +{ + QString html = "<html><head></head>" + "<body>" + "Some text here" + "</body>" + "</html>"; + + QFETCH(bool, active); + QFETCH(bool, background); + + QWidget* activeView = 0; + + // Use controlView to manage active/inactive state of test views by raising + // or lowering their position in the window stack. + QWebView controlView; + controlView.setHtml(html); + + QWebView view1; + + QPalette palette1; + QBrush brush1(Qt::red); + brush1.setStyle(Qt::SolidPattern); + if (active && background) { + // Rendered image must have red background on an active QWebView. + palette1.setBrush(QPalette::Active, QPalette::Highlight, brush1); + } else if (active && !background) { + // Rendered image must have red foreground on an active QWebView. + palette1.setBrush(QPalette::Active, QPalette::HighlightedText, brush1); + } else if (!active && background) { + // Rendered image must have red background on an inactive QWebView. + palette1.setBrush(QPalette::Inactive, QPalette::Highlight, brush1); + } else if (!active && !background) { + // Rendered image must have red foreground on an inactive QWebView. + palette1.setBrush(QPalette::Inactive, QPalette::HighlightedText, brush1); + } + + view1.setPalette(palette1); + view1.setHtml(html); + view1.page()->setViewportSize(view1.page()->currentFrame()->contentsSize()); + view1.show(); + + QTest::qWaitForWindowExposed(&view1); + + if (!active) { + controlView.show(); + QTest::qWaitForWindowExposed(&controlView); + activeView = &controlView; + controlView.activateWindow(); + } else { + view1.activateWindow(); + activeView = &view1; + } + + QTRY_COMPARE(QApplication::activeWindow(), activeView); + + view1.page()->triggerAction(QWebPage::SelectAll); + + QImage img1(view1.page()->viewportSize(), QImage::Format_ARGB32); + QPainter painter1(&img1); + view1.page()->currentFrame()->render(&painter1); + painter1.end(); + view1.close(); + controlView.close(); + + QWebView view2; + + QPalette palette2; + QBrush brush2(Qt::blue); + brush2.setStyle(Qt::SolidPattern); + if (active && background) { + // Rendered image must have blue background on an active QWebView. + palette2.setBrush(QPalette::Active, QPalette::Highlight, brush2); + } else if (active && !background) { + // Rendered image must have blue foreground on an active QWebView. + palette2.setBrush(QPalette::Active, QPalette::HighlightedText, brush2); + } else if (!active && background) { + // Rendered image must have blue background on an inactive QWebView. + palette2.setBrush(QPalette::Inactive, QPalette::Highlight, brush2); + } else if (!active && !background) { + // Rendered image must have blue foreground on an inactive QWebView. + palette2.setBrush(QPalette::Inactive, QPalette::HighlightedText, brush2); + } + + view2.setPalette(palette2); + view2.setHtml(html); + view2.page()->setViewportSize(view2.page()->currentFrame()->contentsSize()); + view2.show(); + + QTest::qWaitForWindowExposed(&view2); + + if (!active) { + controlView.show(); + QTest::qWaitForWindowExposed(&controlView); + activeView = &controlView; + controlView.activateWindow(); + } else { + view2.activateWindow(); + activeView = &view2; + } + + QTRY_COMPARE(QApplication::activeWindow(), activeView); + + view2.page()->triggerAction(QWebPage::SelectAll); + + QImage img2(view2.page()->viewportSize(), QImage::Format_ARGB32); + QPainter painter2(&img2); + view2.page()->currentFrame()->render(&painter2); + painter2.end(); + + view2.close(); + controlView.close(); + + QVERIFY(img1 != img2); +} +#endif + +void tst_QWebView::renderingAfterMaxAndBack() +{ + QUrl url = QUrl("data:text/html,<html><head></head>" + "<body width=1024 height=768 bgcolor=red>" + "</body>" + "</html>"); + + QWebView view; + view.page()->mainFrame()->load(url); + QVERIFY(waitForSignal(&view, SIGNAL(loadFinished(bool)))); + view.show(); + + view.page()->settings()->setMaximumPagesInCache(3); + + QTest::qWaitForWindowExposed(&view); + + QPixmap reference(view.page()->viewportSize()); + reference.fill(Qt::red); + + QPixmap image(view.page()->viewportSize()); + QPainter painter(&image); + view.page()->currentFrame()->render(&painter); + + QCOMPARE(image, reference); + + QUrl url2 = QUrl("data:text/html,<html><head></head>" + "<body width=1024 height=768 bgcolor=blue>" + "</body>" + "</html>"); + view.page()->mainFrame()->load(url2); + + QVERIFY(waitForSignal(&view, SIGNAL(loadFinished(bool)))); + + view.showMaximized(); + + QTest::qWaitForWindowExposed(&view); + + QPixmap reference2(view.page()->viewportSize()); + reference2.fill(Qt::blue); + + QPixmap image2(view.page()->viewportSize()); + QPainter painter2(&image2); + view.page()->currentFrame()->render(&painter2); + + QCOMPARE(image2, reference2); + + view.back(); + + QPixmap reference3(view.page()->viewportSize()); + reference3.fill(Qt::red); + QPixmap image3(view.page()->viewportSize()); + QPainter painter3(&image3); + view.page()->currentFrame()->render(&painter3); + + QCOMPARE(image3, reference3); +} + +QTEST_MAIN(tst_QWebView) +#include "tst_qwebview.moc" diff --git a/tests/widgets/qwebengineview/tst_qwebengineview.qrc b/tests/widgets/qwebengineview/tst_qwebengineview.qrc new file mode 100644 index 000000000..6685a8086 --- /dev/null +++ b/tests/widgets/qwebengineview/tst_qwebengineview.qrc @@ -0,0 +1,8 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file>resources/index.html</file> + <file>resources/frame_a.html</file> + <file>resources/input_types.html</file> + <file>resources/scrolltest_page.html</file> +</qresource> +</RCC> diff --git a/tests/widgets/resources/image2.png b/tests/widgets/resources/image2.png Binary files differnew file mode 100644 index 000000000..8d703640c --- /dev/null +++ b/tests/widgets/resources/image2.png diff --git a/tests/widgets/resources/test.swf b/tests/widgets/resources/test.swf Binary files differnew file mode 100644 index 000000000..895298271 --- /dev/null +++ b/tests/widgets/resources/test.swf diff --git a/tests/widgets/tests.pri b/tests/widgets/tests.pri new file mode 100644 index 000000000..b48806286 --- /dev/null +++ b/tests/widgets/tests.pri @@ -0,0 +1,22 @@ +TEMPLATE = app + +VPATH += $$_PRO_FILE_PWD_ +TARGET = tst_$$TARGET + +# Load mobilityconfig if Qt Mobility is available +load(mobilityconfig, true) +contains(MOBILITY_CONFIG, multimedia) { + # This define is used by tests depending on Qt Multimedia + DEFINES -= WTF_USE_QT_MULTIMEDIA=0 + DEFINES += WTF_USE_QT_MULTIMEDIA=1 +} + +SOURCES += $${TARGET}.cpp +INCLUDEPATH += \ + $$PWD \ + $$PWD/../Api + +QT += testlib network webkitwidgets widgets + +# This define is used by some tests to look up resources in the source tree +DEFINES += TESTS_SOURCE_DIR=\\\"$$PWD/\\\" diff --git a/tests/widgets/util.h b/tests/widgets/util.h new file mode 100644 index 000000000..4925aa4c7 --- /dev/null +++ b/tests/widgets/util.h @@ -0,0 +1,81 @@ +/* + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies) + + 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. +*/ +// Functions and macros that really need to be in QTestLib + +#if 0 +#pragma qt_no_master_include +#endif + +#include <QEventLoop> +#include <QSignalSpy> +#include <QTimer> + +#if !defined(TESTS_SOURCE_DIR) +#define TESTS_SOURCE_DIR "" +#endif + +/** + * Starts an event loop that runs until the given signal is received. + * Optionally the event loop + * can return earlier on a timeout. + * + * \return \p true if the requested signal was received + * \p false on timeout + */ +static inline bool waitForSignal(QObject* obj, const char* signal, int timeout = 10000) +{ + QEventLoop loop; + QObject::connect(obj, signal, &loop, SLOT(quit())); + QTimer timer; + QSignalSpy timeoutSpy(&timer, SIGNAL(timeout())); + if (timeout > 0) { + QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + timer.setSingleShot(true); + timer.start(timeout); + } + loop.exec(); + return timeoutSpy.isEmpty(); +} + +/** + * Just like QSignalSpy but facilitates sync and async + * signal emission. For example if you want to verify that + * page->foo() emitted a signal, it could be that the + * implementation decides to emit the signal asynchronously + * - in which case we want to spin a local event loop until + * emission - or that the call to foo() emits it right away. + */ +class SignalBarrier : private QSignalSpy +{ +public: + SignalBarrier(const QObject* obj, const char* aSignal) + : QSignalSpy(obj, aSignal) + { } + + bool ensureSignalEmitted() + { + bool result = count() > 0; + if (!result) + result = wait(); + clear(); + return result; + } +}; + +#define W_QSKIP(a, b) QSKIP(a) |