diff options
-rw-r--r-- | src/imports/plugins.qmltypes | 9 | ||||
-rw-r--r-- | src/jar/src/org/qtproject/qt5/android/view/QtAndroidWebViewController.java | 35 | ||||
-rw-r--r-- | src/webview/qquickwebview.cpp | 75 | ||||
-rw-r--r-- | src/webview/qquickwebview.h | 7 | ||||
-rw-r--r-- | src/webview/qwebview.cpp | 9 | ||||
-rw-r--r-- | src/webview/qwebview_android.cpp | 53 | ||||
-rw-r--r-- | src/webview/qwebview_android_p.h | 4 | ||||
-rw-r--r-- | src/webview/qwebview_ios.mm | 8 | ||||
-rw-r--r-- | src/webview/qwebview_ios_p.h | 4 | ||||
-rw-r--r-- | src/webview/qwebview_p.h | 3 | ||||
-rw-r--r-- | src/webview/qwebviewinterface_p.h | 3 | ||||
-rw-r--r-- | tests/auto/webview/qwebview/qwebview.pro | 4 | ||||
-rw-r--r-- | tests/auto/webview/qwebview/tst_qwebview.cpp | 31 |
13 files changed, 244 insertions, 1 deletions
diff --git a/src/imports/plugins.qmltypes b/src/imports/plugins.qmltypes index 8acf6fd..a0a57bf 100644 --- a/src/imports/plugins.qmltypes +++ b/src/imports/plugins.qmltypes @@ -20,5 +20,14 @@ Module { Method { name: "goForward" } Method { name: "reload" } Method { name: "stop" } + Method { + name: "runJavaScript" + Parameter { name: "script"; type: "string" } + Parameter { name: "callback"; type: "QJValue" } + } + Method { + name: "runJavaScript" + Parameter { name: "script"; type: "string" } + } } } diff --git a/src/jar/src/org/qtproject/qt5/android/view/QtAndroidWebViewController.java b/src/jar/src/org/qtproject/qt5/android/view/QtAndroidWebViewController.java index dd3cab0..404fa3a 100644 --- a/src/jar/src/org/qtproject/qt5/android/view/QtAndroidWebViewController.java +++ b/src/jar/src/org/qtproject/qt5/android/view/QtAndroidWebViewController.java @@ -63,12 +63,16 @@ public class QtAndroidWebViewController private Method m_webViewOnPause = null; private Method m_webSettingsSetDisplayZoomControls = null; + // API 19 methods + private Method m_webViewEvaluateJavascript = null; + // Native callbacks private native void c_onPageFinished(long id, String url); private native void c_onPageStarted(long id, String url, Bitmap icon); private native void c_onProgressChanged(long id, int newProgress); private native void c_onReceivedIcon(long id, Bitmap icon); private native void c_onReceivedTitle(long id, String title); + private native void c_onRunJavaScriptResult(long id, long callbackId, String result); private class QtAndroidWebViewClient extends WebViewClient { @@ -136,6 +140,11 @@ public class QtAndroidWebViewController m_webViewOnResume = m_webView.getClass().getMethod("onResume"); m_webViewOnPause = m_webView.getClass().getMethod("onPause"); m_webSettingsSetDisplayZoomControls = webSettings.getClass().getMethod("setDisplayZoomControls", boolean.class); + if (Build.VERSION.SDK_INT > 18) { + m_webViewEvaluateJavascript = m_webView.getClass().getMethod("evaluateJavascript", + String.class, + ValueCallback.class); + } } catch (Exception e) { /* Do nothing */ e.printStackTrace(); } } @@ -300,6 +309,32 @@ public class QtAndroidWebViewController return progress[0]; } + public void runJavaScript(final String script, final long callbackId) + { + if (script == null) + return; + + if (Build.VERSION.SDK_INT < 19 || m_webViewEvaluateJavascript == null) + return; + + m_activity.runOnUiThread(new Runnable() { + @Override + public void run() { + try { + m_webViewEvaluateJavascript.invoke(m_webView, script, callbackId == -1 ? null : + new ValueCallback<String>() { + @Override + public void onReceiveValue(String result) { + c_onRunJavaScriptResult(m_id, callbackId, result); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + public String getUrl() { final String[] url = {""}; diff --git a/src/webview/qquickwebview.cpp b/src/webview/qquickwebview.cpp index be51fb7..1803994 100644 --- a/src/webview/qquickwebview.cpp +++ b/src/webview/qquickwebview.cpp @@ -38,6 +38,38 @@ #include <QtQml/qqmlengine.h> #include <QtCore/qmutex.h> +namespace { + +class CallbackStorage +{ +public: + int insertCallback(const QJSValue &callback) + { + QMutexLocker locker(&m_mtx); + const int nextId = qMax(++m_counter, 0); + if (nextId == 0) + m_counter = 1; + + m_callbacks.insert(nextId, callback); + return nextId; + } + + QJSValue takeCallback(int callbackId) + { + QMutexLocker lock(&m_mtx); + return m_callbacks.take(callbackId); + } + +private: + QMutex m_mtx; + int m_counter; + QHash<int, QJSValue> m_callbacks; +}; + +} // namespace + +Q_GLOBAL_STATIC(CallbackStorage, callbacks) + /*! \qmltype WebView \inqmlmodule QtWebView @@ -64,6 +96,7 @@ QQuickWebView::QQuickWebView(QQuickItem *parent) connect(m_webView.data(), &QWebView::loadingChanged, this, &QQuickWebView::loadingChanged); connect(m_webView.data(), &QWebView::loadProgressChanged, this, &QQuickWebView::loadProgressChanged); connect(m_webView.data(), &QWebView::requestFocus, this, &QQuickWebView::onFocusRequest); + connect(m_webView.data(), &QWebView::javaScriptResult, this, &QQuickWebView::onRunJavaScriptResult); } QQuickWebView::~QQuickWebView() @@ -185,6 +218,48 @@ void QQuickWebView::stop() m_webView->stop(); } +/*! + \qmlmethod void QtWebView::WebView::runJavaScript(string script, variant callback) + + Runs the specified JavaScript. + In case a callback function is provided, it will be invoked after the script finished running. + + \badcode + runJavaScript("document.title", function(result) { console.log(result); }); + \endcode +*/ +void QQuickWebView::runJavaScript(const QString &script, const QJSValue &callback) +{ + const int callbackId = callback.isCallable() ? callbacks->insertCallback(callback) + : -1; + runJavaScriptPrivate(script, callbackId); +} + +void QQuickWebView::runJavaScriptPrivate(const QString &script, int callbackId) +{ + m_webView->runJavaScriptPrivate(script, callbackId); +} + +void QQuickWebView::onRunJavaScriptResult(int id, const QVariant &variant) +{ + if (id == -1) + return; + + QJSValue callback = callbacks->takeCallback(id); + if (callback.isUndefined()) + return; + + QQmlEngine *engine = qmlEngine(this); + if (engine == 0) { + qWarning() << "No JavaScript engine, unable to handle JavaScript callback!"; + return; + } + + QJSValueList args; + args.append(engine->toScriptValue(variant)); + callback.call(args); +} + void QQuickWebView::onFocusRequest(bool focus) { setFocus(focus); diff --git a/src/webview/qquickwebview.h b/src/webview/qquickwebview.h index aab13b1..c36cc9d 100644 --- a/src/webview/qquickwebview.h +++ b/src/webview/qquickwebview.h @@ -84,6 +84,8 @@ public Q_SLOTS: void goForward() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void stop() Q_DECL_OVERRIDE; + void runJavaScript(const QString& script, + const QJSValue &callback = QJSValue()); Q_SIGNALS: void titleChanged(); @@ -91,7 +93,12 @@ Q_SIGNALS: void loadingChanged(); void loadProgressChanged(); +protected: + void runJavaScriptPrivate(const QString& script, + int callbackId) Q_DECL_OVERRIDE; + private Q_SLOTS: + void onRunJavaScriptResult(int id, const QVariant &variant); void onFocusRequest(bool focus); private: diff --git a/src/webview/qwebview.cpp b/src/webview/qwebview.cpp index 9d55331..1865c7e 100644 --- a/src/webview/qwebview.cpp +++ b/src/webview/qwebview.cpp @@ -48,6 +48,8 @@ QWebView::QWebView(QObject *p) connect(d, SIGNAL(loadingChanged()), this, SIGNAL(loadingChanged())); connect(d, SIGNAL(loadProgressChanged()), this, SIGNAL(loadProgressChanged())); connect(d, SIGNAL(requestFocus(bool)), this, SIGNAL(requestFocus(bool))); + connect(d, &QWebViewPrivate::javaScriptResult, + this, &QWebView::javaScriptResult); } QWebView::~QWebView() @@ -150,6 +152,13 @@ void QWebView::setFocus(bool focus) d->setFocus(focus); } +void QWebView::runJavaScriptPrivate(const QString &script, + int callbackId) +{ + Q_D(QWebView); + d->runJavaScriptPrivate(script, callbackId); +} + void QWebView::init() { Q_D(QWebView); diff --git a/src/webview/qwebview_android.cpp b/src/webview/qwebview_android.cpp index be87a56..1ff4271 100644 --- a/src/webview/qwebview_android.cpp +++ b/src/webview/qwebview_android.cpp @@ -45,6 +45,7 @@ #include <QtCore/qjsondocument.h> #include <QtCore/qjsonobject.h> #include <QtCore/qurl.h> +#include <QtCore/qdebug.h> QT_BEGIN_NAMESPACE @@ -152,6 +153,24 @@ void QAndroidWebViewPrivate::setVisibility(QWindow::Visibility visibility) m_window->setVisibility(visibility); } +void QAndroidWebViewPrivate::runJavaScriptPrivate(const QString &script, + int callbackId) +{ + if (QtAndroidPrivate::androidSdkVersion() < 19) { + qWarning() << "runJavaScript() requires API level 19 or higher."; + if (callbackId == -1) + return; + + // Emit signal here to remove the callback. + Q_EMIT javaScriptResult(callbackId, QVariant()); + } + + m_viewController.callMethod<void>("runJavaScript", + "(Ljava/lang/String;J)V", + static_cast<jstring>(QJNIObjectPrivate::fromString(script).object()), + callbackId); +} + void QAndroidWebViewPrivate::setVisible(bool visible) { m_window->setVisible(visible); @@ -195,6 +214,37 @@ void QAndroidWebViewPrivate::onApplicationStateChanged(Qt::ApplicationState stat QT_END_NAMESPACE +static void c_onRunJavaScriptResult(JNIEnv *env, + jobject thiz, + jlong id, + jlong callbackId, + jstring result) +{ + Q_UNUSED(env) + Q_UNUSED(thiz) + + const WebViews &wv = (*g_webViews); + QAndroidWebViewPrivate *wc = static_cast<QAndroidWebViewPrivate *>(wv[id]); + if (!wc) + return; + + const QString &resultString = QJNIObjectPrivate(result).toString(); + + // The result string is in JSON format, lets parse it to see what we got. + QJsonValue jsonValue; + const QByteArray &jsonData = "{ \"data\": " + resultString.toUtf8() + " }"; + QJsonParseError error; + const QJsonDocument &jsonDoc = QJsonDocument::fromJson(jsonData, &error); + if (error.error == QJsonParseError::NoError && jsonDoc.isObject()) { + const QJsonObject &object = jsonDoc.object(); + jsonValue = object.value(QStringLiteral("data")); + } + + Q_EMIT wc->javaScriptResult(int(callbackId), + jsonValue.isNull() ? resultString + : jsonValue.toVariant()); +} + static void c_onPageFinished(JNIEnv *env, jobject thiz, jlong id, @@ -315,7 +365,8 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {"c_onPageStarted", "(JLjava/lang/String;Landroid/graphics/Bitmap;)V", reinterpret_cast<void *>(c_onPageStarted)}, {"c_onProgressChanged", "(JI)V", reinterpret_cast<void *>(c_onProgressChanged)}, {"c_onReceivedIcon", "(JLandroid/graphics/Bitmap;)V", reinterpret_cast<void *>(c_onReceivedIcon)}, - {"c_onReceivedTitle", "(JLjava/lang/String;)V", reinterpret_cast<void *>(c_onReceivedTitle)} + {"c_onReceivedTitle", "(JLjava/lang/String;)V", reinterpret_cast<void *>(c_onReceivedTitle)}, + {"c_onRunJavaScriptResult", "(JJLjava/lang/String;)V", reinterpret_cast<void *>(c_onRunJavaScriptResult)} }; const int nMethods = sizeof(methods) / sizeof(methods[0]); diff --git a/src/webview/qwebview_android_p.h b/src/webview/qwebview_android_p.h index 751b44f..45a0213 100644 --- a/src/webview/qwebview_android_p.h +++ b/src/webview/qwebview_android_p.h @@ -83,6 +83,10 @@ public Q_SLOTS: void reload() Q_DECL_OVERRIDE; void stop() Q_DECL_OVERRIDE; +protected: + void runJavaScriptPrivate(const QString& script, + int callbackId) Q_DECL_OVERRIDE; + private Q_SLOTS: void onApplicationStateChanged(Qt::ApplicationState state); diff --git a/src/webview/qwebview_ios.mm b/src/webview/qwebview_ios.mm index faa82c3..b9fdff9 100644 --- a/src/webview/qwebview_ios.mm +++ b/src/webview/qwebview_ios.mm @@ -283,4 +283,12 @@ void QIosWebViewPrivate::reload() [uiWebView reload]; } +void QIosWebViewPrivate::runJavaScriptPrivate(const QString &script, int callbackId) +{ + // ### TODO needs more async + NSString *result = [uiWebView stringByEvaluatingJavaScriptFromString:script.toNSString()]; + if (callbackId != -1) + Q_EMIT javaScriptResult(callbackId, QString::fromNSString(result)); +} + QT_END_NAMESPACE diff --git a/src/webview/qwebview_ios_p.h b/src/webview/qwebview_ios_p.h index 774d707..8302a68 100644 --- a/src/webview/qwebview_ios_p.h +++ b/src/webview/qwebview_ios_p.h @@ -86,6 +86,10 @@ public Q_SLOTS: void reload() Q_DECL_OVERRIDE; void stop() Q_DECL_OVERRIDE; +protected: + void runJavaScriptPrivate(const QString& script, + int callbackId) Q_DECL_OVERRIDE; + public: UIWebView *uiWebView; UIGestureRecognizer *m_recognizer; diff --git a/src/webview/qwebview_p.h b/src/webview/qwebview_p.h index 2a92620..578403a 100644 --- a/src/webview/qwebview_p.h +++ b/src/webview/qwebview_p.h @@ -93,10 +93,13 @@ Q_SIGNALS: void urlChanged(); void loadingChanged(); void loadProgressChanged(); + void javaScriptResult(int id, const QVariant &result); void requestFocus(bool focus); protected: void init(); + void runJavaScriptPrivate(const QString &script, + int callbackId) Q_DECL_OVERRIDE; private: friend class QQuickViewController; diff --git a/src/webview/qwebviewinterface_p.h b/src/webview/qwebviewinterface_p.h index 86d370c..ef02042 100644 --- a/src/webview/qwebviewinterface_p.h +++ b/src/webview/qwebviewinterface_p.h @@ -76,6 +76,9 @@ public: virtual void goForward() = 0; virtual void stop() = 0; virtual void reload() = 0; + + virtual void runJavaScriptPrivate(const QString &script, + int callbackId) = 0; }; #endif // QWEBVIEWINTERFACE_H diff --git a/tests/auto/webview/qwebview/qwebview.pro b/tests/auto/webview/qwebview/qwebview.pro index 3cfaca9..198bb21 100644 --- a/tests/auto/webview/qwebview/qwebview.pro +++ b/tests/auto/webview/qwebview/qwebview.pro @@ -2,6 +2,10 @@ CONFIG += testcase parallel_test TARGET = tst_qwebview osx:CONFIG -= app_bundle +!qtHaveModule(quick) { + DEFINES += QT_NO_QQUICKWEBVIEW_TESTS +} + !android: qtHaveModule(webengine) { QT += webengine webengine-private DEFINES += QT_WEBVIEW_WEBENGINE_BACKEND diff --git a/tests/auto/webview/qwebview/tst_qwebview.cpp b/tests/auto/webview/qwebview/tst_qwebview.cpp index e9f8d51..9702324 100644 --- a/tests/auto/webview/qwebview/tst_qwebview.cpp +++ b/tests/auto/webview/qwebview/tst_qwebview.cpp @@ -41,6 +41,11 @@ #include <QtCore/qfileinfo.h> #include <QtWebView/private/qwebview_p.h> #include <QtQml/qqmlengine.h> + +#ifndef QT_NO_QQUICKWEBVIEW_TESTS +#include <QtWebView/qquickwebview.h> +#endif // QT_NO_QQUICKWEBVIEW_TESTS + #ifdef QT_WEBVIEW_WEBENGINE_BACKEND #include <QtWebEngine> #endif // QT_WEBVIEW_WEBENGINE_BACKEND @@ -54,6 +59,7 @@ public: private slots: void initTestCase(); void load(); + void runJavaScript(); private: const QString m_cacheLocation; @@ -92,6 +98,31 @@ void tst_QWebView::load() QCOMPARE(view.url(), url); } +void tst_QWebView::runJavaScript() +{ +#ifndef QT_NO_QQUICKWEBVIEW_TESTS + const QString tstProperty = QString(QLatin1String("Qt.tst_data")); + const QString title = QString(QLatin1String("WebViewTitle")); + + QQuickWebView view; + QQmlEngine engine; + QQmlContext *rootContext = engine.rootContext(); + QQmlEngine::setContextForObject(&view, rootContext); + + QCOMPARE(view.loadProgress(), 0); + view.loadHtml(QString("<html><head><title>%1</title></head><body /></html>").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); +#endif // QT_NO_QQUICKWEBVIEW_TESTS +} + QTEST_MAIN(tst_QWebView) #include "tst_qwebview.moc" |