summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Strømme <[email protected]>2015-01-21 13:47:38 +0100
committerChristian Stromme <[email protected]>2015-02-09 16:35:47 +0000
commitb8fc57ba4aa5b25ae31df0ec74c62730dd0fa9d0 (patch)
treecd7def072034c2d14b213cfd5c8b2269bee869c0
parent011069957991ac884f6e5cbacc6391570c7091e8 (diff)
Add runJavaScript() on Android and iOS.
This is a rough implementation on both Android and iOS and both comes with certain limitations. On Android we require API level 19 or higher and on iOS the runJavaScript function is not asynchronous. Change-Id: Ia19782505e5e30d5517a4d627565e5432cc27bfa Reviewed-by: Eskil Abrahamsen Blomfeldt <[email protected]>
-rw-r--r--src/imports/plugins.qmltypes9
-rw-r--r--src/jar/src/org/qtproject/qt5/android/view/QtAndroidWebViewController.java35
-rw-r--r--src/webview/qquickwebview.cpp75
-rw-r--r--src/webview/qquickwebview.h7
-rw-r--r--src/webview/qwebview.cpp9
-rw-r--r--src/webview/qwebview_android.cpp53
-rw-r--r--src/webview/qwebview_android_p.h4
-rw-r--r--src/webview/qwebview_ios.mm8
-rw-r--r--src/webview/qwebview_ios_p.h4
-rw-r--r--src/webview/qwebview_p.h3
-rw-r--r--src/webview/qwebviewinterface_p.h3
-rw-r--r--tests/auto/webview/qwebview/qwebview.pro4
-rw-r--r--tests/auto/webview/qwebview/tst_qwebview.cpp31
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"