// Copyright (C) 2015 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include #include #include #include #include #if QT_CONFIG(webview_webengine_plugin) #include #endif #if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_NO_SDK) #include #define ANDROID_REQUIRES_API_LEVEL(N) \ if (QtAndroidPrivate::androidSdkVersion() < N) \ QSKIP("This feature is not supported on this version of Android"); #else #define ANDROID_REQUIRES_API_LEVEL(N) #endif // TODO: remove when c++ apis come class WebViewFactory { public: WebViewFactory() : m_webengine(QWebViewFactory::loadedPluginHasKey("webengine")), m_engine(m_webengine ? std::make_unique() : nullptr), m_quickView(m_webengine ? std::make_unique() : nullptr), m_view(m_webengine ? nullptr : std::make_unique()) { if (m_webengine) { QQmlContext *rootContext = m_engine->rootContext(); QQmlEngine::setContextForObject(m_quickView.get(), rootContext); } } QWebView &webViewRef() { return m_webengine ? m_quickView->webView() : *(m_view.get()); } private: bool m_webengine; std::unique_ptr m_engine; std::unique_ptr m_quickView; std::unique_ptr m_view; }; class tst_QWebView : public QObject { Q_OBJECT public: tst_QWebView() : m_cacheLocation(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) {} private slots: void initTestCase(); void load(); void runJavaScript(); void loadHtml(); void loadRequest(); void setAndDeleteCookie(); private: const QString m_cacheLocation; }; void tst_QWebView::initTestCase() { if (!qEnvironmentVariableIsEmpty("QEMU_LD_PREFIX")) QSKIP("This test is unstable on QEMU, so it will be skipped."); #if QT_CONFIG(webview_webengine_plugin) if (QWebViewFactory::loadedPluginHasKey("webengine")) QtWebEngineQuick::initialize(); #endif if (!QFileInfo(m_cacheLocation).isDir()) { QDir dir; QVERIFY(dir.mkpath(m_cacheLocation)); } } void tst_QWebView::load() { QTemporaryFile file(m_cacheLocation + QStringLiteral("/XXXXXXfile.html")); QVERIFY2(file.open(), qPrintable(QStringLiteral("Cannot create temporary file:") + file.errorString())); file.write("FooBar"); const QString fileName = file.fileName(); file.close(); WebViewFactory factory; QWebView &view = factory.webViewRef(); view.getSettings()->setAllowFileAccess(true); view.getSettings()->setLocalContentCanAccessFileUrls(true); QCOMPARE(view.loadProgress(), 0); const QUrl url = QUrl::fromLocalFile(fileName); view.setUrl(url); QTRY_COMPARE(view.loadProgress(), 100); QTRY_VERIFY(!view.isLoading()); QCOMPARE(view.title(), QStringLiteral("FooBar")); QVERIFY(!view.canGoBack()); QVERIFY(!view.canGoForward()); QCOMPARE(view.url(), url); } void tst_QWebView::runJavaScript() { ANDROID_REQUIRES_API_LEVEL(19) const QString tstProperty = QString(QLatin1String("Qt.tst_data")); const QString title = QString(QLatin1String("WebViewTitle")); QQmlEngine engine; QQmlContext *rootContext = engine.rootContext(); QQuickWebView view; QQmlEngine::setContextForObject(&view, rootContext); QCOMPARE(view.loadProgress(), 0); view.loadHtml(QString("%1").arg(title)); QTRY_COMPARE(view.loadProgress(), 100); QTRY_VERIFY(!view.isLoading()); QCOMPARE(view.title(), title); QJSValue callback = engine.evaluate(QString("(function(result) { %1 = result; })").arg(tstProperty)); QVERIFY2(!callback.isError(), qPrintable(callback.toString())); QVERIFY(!callback.isUndefined()); QVERIFY(callback.isCallable()); view.runJavaScript(QString(QLatin1String("document.title")), callback); QTRY_COMPARE(engine.evaluate(tstProperty).toString(), title); } void tst_QWebView::loadHtml() { WebViewFactory factory; QWebView &view = factory.webViewRef(); QCOMPARE(view.loadProgress(), 0); QSignalSpy loadChangedSingalSpy(&view, SIGNAL(loadingChanged(QWebViewLoadRequestPrivate))); const QByteArray content( QByteArrayLiteral("WebViewTitle" "Hello")); view.loadHtml(content); QTRY_COMPARE(view.loadProgress(), 100); QTRY_VERIFY(!view.isLoading()); QCOMPARE(view.title(), QStringLiteral("WebViewTitle")); QTRY_COMPARE(loadChangedSingalSpy.size(), 2); // take load finished const QWebViewLoadRequestPrivate &lr = loadChangedSingalSpy.at(1).at(0).value(); QCOMPARE(lr.m_status, QWebView::LoadSucceededStatus); // The following test is disabled because the content is not loaded in the same way in the webview // on darwin and url will be just the base url (which unless specified is about:blank) #if ! (defined(QT_PLATFORM_UIKIT) || defined(Q_OS_MACOS)) QByteArray encoded("data:text/html;charset=UTF-8,"); encoded.append(content.toPercentEncoding()); QVERIFY(view.url().isValid()); QCOMPARE(view.url(), QUrl(encoded)); #endif } void tst_QWebView::loadRequest() { // LoadSucceeded { QTemporaryFile file(m_cacheLocation + QStringLiteral("/XXXXXXfile.html")); QVERIFY2(file.open(), qPrintable(QStringLiteral("Cannot create temporary file:") + file.errorString())); file.write("FooBar"); const QString fileName = file.fileName(); file.close(); WebViewFactory factory; QWebView &view = factory.webViewRef(); view.getSettings()->setAllowFileAccess(true); view.getSettings()->setLocalContentCanAccessFileUrls(true); QCOMPARE(view.loadProgress(), 0); const QUrl url = QUrl::fromLocalFile(fileName); QSignalSpy loadChangedSingalSpy(&view, SIGNAL(loadingChanged(QWebViewLoadRequestPrivate))); view.setUrl(url); QTRY_VERIFY(!view.isLoading()); QTRY_COMPARE(view.loadProgress(), 100); QTRY_COMPARE(view.title(), QStringLiteral("FooBar")); QCOMPARE(view.url(), url); QTRY_COMPARE(loadChangedSingalSpy.size(), 2); { const QList &loadStartedArgs = loadChangedSingalSpy.takeFirst(); const QWebViewLoadRequestPrivate &lr = loadStartedArgs.at(0).value(); QCOMPARE(lr.m_status, QWebView::LoadStartedStatus); } { const QList &loadStartedArgs = loadChangedSingalSpy.takeFirst(); const QWebViewLoadRequestPrivate &lr = loadStartedArgs.at(0).value(); QCOMPARE(lr.m_status, QWebView::LoadSucceededStatus); } } // LoadFailed { WebViewFactory factory; QWebView &view = factory.webViewRef(); view.getSettings()->setAllowFileAccess(true); view.getSettings()->setLocalContentCanAccessFileUrls(true); QCOMPARE(view.loadProgress(), 0); QSignalSpy loadChangedSingalSpy(&view, SIGNAL(loadingChanged(QWebViewLoadRequestPrivate))); view.setUrl(QUrl(QStringLiteral("file:///file_that_does_not_exist.html"))); QTRY_VERIFY(!view.isLoading()); QTRY_COMPARE(loadChangedSingalSpy.size(), 2); { const QList &loadStartedArgs = loadChangedSingalSpy.takeFirst(); const QWebViewLoadRequestPrivate &lr = loadStartedArgs.at(0).value(); QCOMPARE(lr.m_status, QWebView::LoadStartedStatus); } { const QList &loadStartedArgs = loadChangedSingalSpy.takeFirst(); const QWebViewLoadRequestPrivate &lr = loadStartedArgs.at(0).value(); QCOMPARE(lr.m_status, QWebView::LoadFailedStatus); } if (QWebViewFactory::loadedPluginHasKey("webengine")) QCOMPARE(view.loadProgress(), 0); // darwin plugin returns 100 } } void tst_QWebView::setAndDeleteCookie() { WebViewFactory factory; QWebView &view = factory.webViewRef(); view.getSettings()->setLocalStorageEnabled(true); view.getSettings()->setAllowFileAccess(true); view.getSettings()->setLocalContentCanAccessFileUrls(true); QSignalSpy cookieAddedSpy(&view, SIGNAL(cookieAdded(QString,QString))); QSignalSpy cookieRemovedSpy(&view, SIGNAL(cookieRemoved(QString,QString))); view.setCookie(".example.com", "TestCookie", "testValue"); view.setCookie(".example2.com", "TestCookie2", "testValue2"); view.setCookie(".example3.com", "TestCookie3", "testValue3"); QTRY_COMPARE(cookieAddedSpy.size(), 3); view.deleteCookie(".example.com", "TestCookie"); QTRY_COMPARE(cookieRemovedSpy.size(), 1); // deleting a cookie using a name that has not been set view.deleteCookie(".example.com", "NewCookieName"); QTRY_COMPARE(cookieRemovedSpy.size(), 1); // deleting a cookie using a domain that has not been set view.deleteCookie(".new.domain.com", "TestCookie2"); QTRY_COMPARE(cookieRemovedSpy.size(), 1); view.deleteAllCookies(); if (QWebViewFactory::loadedPluginHasKey("android_view")) QEXPECT_FAIL("", "Notification for deleteAllCookies() is not implemented on Android, yet!", Continue); QTRY_COMPARE(cookieRemovedSpy.size(), 3); } QTEST_MAIN(tst_QWebView) #include "tst_qwebview.moc"