/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef QT_NO_OPENSSL #include #endif #include "../util.h" class tst_QWebEngineFrame : public QObject { Q_OBJECT public: bool eventFilter(QObject* watched, QEvent* event); public Q_SLOTS: void initTestCase(); 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 asyncAndDelete(); void earlyToHtml(); 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: QWebEngineView* m_view; QWebEnginePage* m_page; QWebEngineView* m_inputFieldsTestView; int m_inputFieldTestPaintCount; }; bool tst_QWebEngineFrame::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_QWebEngineFrame::initTestCase() { } void tst_QWebEngineFrame::init() { m_view = new QWebEngineView(); m_page = m_view->page(); m_page->settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); } void tst_QWebEngineFrame::cleanup() { delete m_view; } void tst_QWebEngineFrame::symmetricUrl() { QWebEngineView view; QSignalSpy loadFinishedSpy(view.page(), SIGNAL(loadFinished(bool))); QVERIFY(view.url().isEmpty()); QCOMPARE(view.history()->count(), 0); QUrl dataUrl("data:text/html,

Test"); view.setUrl(dataUrl); QCOMPARE(view.url(), dataUrl); QCOMPARE(view.history()->count(), 0); // loading is _not_ immediate, so the text isn't set just yet. QVERIFY(toPlainTextSync(view.page()).isEmpty()); QTRY_COMPARE(loadFinishedSpy.count(), 1); QCOMPARE(view.history()->count(), 1); QCOMPARE(toPlainTextSync(view.page()), QString("Test")); QUrl dataUrl2("data:text/html,

Test2"); QUrl dataUrl3("data:text/html,

Test3"); view.setUrl(dataUrl2); view.setUrl(dataUrl3); QCOMPARE(view.url(), dataUrl3); QTRY_VERIFY(loadFinishedSpy.count() >= 2); QTRY_COMPARE(loadFinishedSpy.count(), 3); QCOMPARE(view.history()->count(), 2); QCOMPARE(toPlainTextSync(view.page()), QString("Test3")); } void tst_QWebEngineFrame::progressSignal() { QSignalSpy progressSpy(m_view, SIGNAL(loadProgress(int))); QUrl dataUrl("data:text/html,

Test"); m_view->setUrl(dataUrl); ::waitForSignal(m_view, SIGNAL(loadFinished(bool))); QVERIFY(progressSpy.size() >= 2); int previousValue = -1; for (QSignalSpy::ConstIterator it = progressSpy.begin(); it < progressSpy.end(); ++it) { int current = (*it).first().toInt(); QVERIFY(current > previousValue); previousValue = current; } // But we always end at 100% QCOMPARE(progressSpy.last().first().toInt(), 100); } void tst_QWebEngineFrame::urlChange() { QSignalSpy urlSpy(m_page, SIGNAL(urlChanged(QUrl))); QUrl dataUrl("data:text/html,

Test"); m_view->setUrl(dataUrl); ::waitForSignal(m_page, SIGNAL(urlChanged(QUrl))); QCOMPARE(urlSpy.size(), 1); QUrl dataUrl2("data:text/html,title

Test"); m_view->setUrl(dataUrl2); ::waitForSignal(m_page, 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 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_QWebEngineFrame::requestedUrl() { #if !defined(QWEBENGINEPAGE_SETNETWORKACCESSMANAGER) QSKIP("QWEBENGINEPAGE_SETNETWORKACCESSMANAGER"); #else QWebEnginePage page; // in few seconds, the image should be completely loaded QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); FakeNetworkManager* networkManager = new FakeNetworkManager(&page); page.setNetworkAccessManager(networkManager); page.setUrl(QUrl("qrc:/test1.html")); waitForSignal(&page, SIGNAL(loadFinished(bool)), 200); QCOMPARE(spy.count(), 1); QCOMPARE(page.requestedUrl(), QUrl("qrc:/test1.html")); QCOMPARE(page.url(), QUrl("qrc:/test2.html")); page.setUrl(QUrl("qrc:/non-existent.html")); waitForSignal(&page, SIGNAL(loadFinished(bool)), 200); QCOMPARE(spy.count(), 2); QCOMPARE(page.requestedUrl(), QUrl("qrc:/non-existent.html")); QCOMPARE(page.url(), QUrl("qrc:/non-existent.html")); page.setUrl(QUrl("http://abcdef.abcdef")); waitForSignal(&page, SIGNAL(loadFinished(bool)), 200); QCOMPARE(spy.count(), 3); QCOMPARE(page.requestedUrl(), QUrl("http://abcdef.abcdef/")); QCOMPARE(page.url(), QUrl("http://abcdef.abcdef/")); #ifndef QT_NO_OPENSSL qRegisterMetaType >("QList"); qRegisterMetaType("QNetworkReply*"); QSignalSpy spy2(page.networkAccessManager(), SIGNAL(sslErrors(QNetworkReply*,QList))); page.setUrl(QUrl("qrc:/fake-ssl-error.html")); waitForSignal(&page, SIGNAL(loadFinished(bool)), 200); QCOMPARE(spy2.count(), 1); QCOMPARE(page.requestedUrl(), QUrl("qrc:/fake-ssl-error.html")); QCOMPARE(page.url(), QUrl("qrc:/fake-ssl-error.html")); #endif #endif } void tst_QWebEngineFrame::requestedUrlAfterSetAndLoadFailures() { QWebEnginePage page; page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); const QUrl first("http://abcdef.abcdef/"); page.setUrl(first); ::waitForSignal(&page, SIGNAL(loadFinished(bool))); QCOMPARE(spy.count(), 1); QCOMPARE(page.url(), first); QCOMPARE(page.requestedUrl(), first); QVERIFY(!spy.at(0).first().toBool()); const QUrl second("http://abcdef.abcdef/another_page.html"); QVERIFY(first != second); page.load(second); ::waitForSignal(&page, SIGNAL(loadFinished(bool))); QCOMPARE(spy.count(), 2); QCOMPARE(page.url(), first); QCOMPARE(page.requestedUrl(), second); QVERIFY(!spy.at(1).first().toBool()); } void tst_QWebEngineFrame::javaScriptWindowObjectCleared_data() { QTest::addColumn("html"); QTest::addColumn("signalCount"); QTest::newRow("with

hello world

" << 1; // NOTE: Empty scripts no longer cause this signal to be emitted. QTest::newRow("with empty

hello world

" << 0; QTest::newRow("without

hello world

"); MyPage page; page.setHtml(html, QUrl(QStringLiteral("http://test.origin.com/path#fragment"))); waitForSignal(&page, SIGNAL(loadFinished(bool))); QCOMPARE(page.alerts, 1); QCOMPARE(toHtmlSync(&page), html); } class TestNetworkManager : public QNetworkAccessManager { public: TestNetworkManager(QObject* parent) : QNetworkAccessManager(parent) {} QList 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,

hello")); return QNetworkAccessManager::createRequest(op, redirectedRequest, outgoingData); } }; void tst_QWebEngineFrame::ipv6HostEncoding() { #if !defined(QWEBENGINEPAGE_EVALUATEJAVASCRIPT) QSKIP("QWEBENGINEPAGE_EVALUATEJAVASCRIPT"); #else TestNetworkManager* networkManager = new TestNetworkManager(m_page); m_page->setNetworkAccessManager(networkManager); networkManager->requestedUrls.clear(); QUrl baseUrl = QUrl::fromEncoded("http://[::1]/index.html"); m_view->setHtml("

Hi", baseUrl); m_view->page()->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")); #endif } void tst_QWebEngineFrame::metaData() { #if !defined(QWEBENGINEPAGE_METADATA) QSKIP("QWEBENGINEPAGE_METADATA"); #else m_view->setHtml("" " " " " " " " " ""); QMultiMap metaData = m_view->page()->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("" " " " " " " " " ""); metaData = m_view->page()->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()); #endif } #if !defined(QT_NO_COMBOBOX) void tst_QWebEngineFrame::popupFocus() { #if !defined(QWEBENGINEELEMENT) QSKIP("QWEBENGINEELEMENT"); #else QWebEngineView view; view.setHtml("" " " " " " " " " " " ""); 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 QWebEngineElement webCombo = view.page()->documentElement().findFirst(QLatin1String("select[name=select]")); QTest::mouseClick(&view, Qt::LeftButton, 0, webCombo.geometry().center()); QComboBox* combo = view.findChild(); 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 } #endif void tst_QWebEngineFrame::inputFieldFocus() { #if !defined(QWEBENGINEELEMENT) QSKIP("QWEBENGINEELEMENT"); #else QWebEngineView view; view.setHtml(""); 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 QWebEngineElement inputElement = view.page()->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); #endif } void tst_QWebEngineFrame::hitTestContent() { #if !defined(QWEBENGINEELEMENT) QSKIP("QWEBENGINEELEMENT"); #else QString html("

A paragraph




link text"); QWebEnginePage page; page.setHtml(html); page.setViewportSize(QSize(200, 0)); //no height so link is not visible const QWebEngineElement linkElement = page.documentElement().findFirst(QLatin1String("a#link")); QWebEngineHitTestResult result = page.hitTestContent(linkElement.geometry().center()); QCOMPARE(result.linkText(), QString("link text")); QWebEngineElement link = result.linkElement(); QCOMPARE(link.attribute("target"), QString("_foo")); #endif } void tst_QWebEngineFrame::baseUrl_data() { QTest::addColumn("html"); QTest::addColumn("loadUrl"); QTest::addColumn("url"); QTest::addColumn("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 = "" "" "" "" ""; QTest::newRow("customBaseUrl") << html << QUrl("http://foobar.baz/") << QUrl("http://foobar.baz/") << QUrl("http://foobaz.bar/"); } void tst_QWebEngineFrame::baseUrl() { QFETCH(QString, html); QFETCH(QUrl, loadUrl); QFETCH(QUrl, url); QFETCH(QUrl, baseUrl); QSignalSpy loadSpy(m_page, SIGNAL(loadFinished(bool))); m_page->setHtml(html, loadUrl); QTRY_COMPARE(loadSpy.count(), 1); QCOMPARE(m_page->url(), url); QEXPECT_FAIL("null", "Slight change: We now translate QUrl() to about:blank for the virtual url, but not for the baseUrl", Continue); QCOMPARE(baseUrlSync(m_page), baseUrl); } void tst_QWebEngineFrame::hasSetFocus() { #if !defined(QWEBENGINEFRAME) QSKIP("QWEBENGINEFRAME"); #else QString html("

top

" \ "