diff --git a/app/src/main/assets/sample/协程/ui中使用协程/main.js b/app/src/main/assets/sample/协程/ui中使用协程/main.js new file mode 100644 index 00000000..5baee904 --- /dev/null +++ b/app/src/main/assets/sample/协程/ui中使用协程/main.js @@ -0,0 +1,24 @@ +"ui"; + +ui.layout( + + + UI中使用协程 + + +); + +continuation.delay(5000); +if (!requestScreenCapture()) { + dialogs.alert("请授予软件截图权限").await(); +} + + +// 退出应用对话框 +ui.emitter.on("back_pressed", function (e) { + e.consumed = true; + let exit = dialogs.confirm("确定要退出程序").await(); + if (exit) { + ui.finish(); + } +}); \ No newline at end of file diff --git a/app/src/main/assets/sample/协程/ui中使用协程/project.json b/app/src/main/assets/sample/协程/ui中使用协程/project.json new file mode 100644 index 00000000..8afb2e9c --- /dev/null +++ b/app/src/main/assets/sample/协程/ui中使用协程/project.json @@ -0,0 +1,11 @@ +{ + "name": "协程UI示例", + "main": "main.js", + "ignore": [ + "build" + ], + "packageName": "com.example.cont.ui", + "versionName": "1.0.0", + "versionCode": 1, + "useFeatures": ["continuation"] +} \ No newline at end of file diff --git a/app/src/main/assets/sample/协程/协程HelloWorld/hello.txt b/app/src/main/assets/sample/协程/协程HelloWorld/hello.txt new file mode 100644 index 00000000..9be9bcf8 --- /dev/null +++ b/app/src/main/assets/sample/协程/协程HelloWorld/hello.txt @@ -0,0 +1 @@ +Nothing \ No newline at end of file diff --git a/app/src/main/assets/sample/协程/协程HelloWorld/main.js b/app/src/main/assets/sample/协程/协程HelloWorld/main.js new file mode 100644 index 00000000..9e93812a --- /dev/null +++ b/app/src/main/assets/sample/协程/协程HelloWorld/main.js @@ -0,0 +1,54 @@ +// 注意,要使用协程这个特性,必须使用项目功能,并且在project.json配置好features属性 + + +// delay不同于sleep,不会阻塞当前线程 +function delay(millis) { + var cont = continuation.create(); + setTimeout(()=>{ + cont.resume(); + }, millis); + cont.await(); +} + +// 异步IO例子,在另一个线程读取文件,读取完成后返回当前线程继续执行 +function read(path) { + var cont = continuation.create(); + threads.start(function(){ + try { + cont.resume(files.read(path)); + }catch(err){ + cont.resumeError(err); + } + }); + return cont.await(); +} + +// 使用Promise和协程的例子 +function add(a, b) { + return new Promise(function(resolve, reject) { + var sum = a + b; + resolve(sum); + }); +} + +toastLog("Hello, Continuation!"); + +//3秒后发出提示 +setTimeout(()=>{ + toastLog("3秒后...."); +}, 3000); + +// 你可以尝试把delay更换成sleep,看会发生什么! +delay(6000); +toastLog("6秒后..."); + +try { + toastLog("读取文件hello.txt: " + read("./hello.txt")); +}catch(err){ + console.error(err); +} + +var sum = add(1, 2).await(); +toastLog("1 + 2 = " + sum); + + diff --git a/app/src/main/assets/sample/协程/协程HelloWorld/project.json b/app/src/main/assets/sample/协程/协程HelloWorld/project.json new file mode 100644 index 00000000..dccfca87 --- /dev/null +++ b/app/src/main/assets/sample/协程/协程HelloWorld/project.json @@ -0,0 +1,11 @@ +{ + "name": "协程HelloWorld", + "main": "main.js", + "ignore": [ + "build" + ], + "packageName": "com.example.cont.helloworld", + "versionName": "1.0.0", + "versionCode": 1, + "useFeatures": ["continuation"] +} \ No newline at end of file diff --git a/autojs/src/main/assets/init.js b/autojs/src/main/assets/init.js index 1b86ef43..78883805 100644 --- a/autojs/src/main/assets/init.js +++ b/autojs/src/main/assets/init.js @@ -16,16 +16,7 @@ runtime.init(); } })(); - - //初始化不依赖环境的模块 - global.JSON = require('__json2__.js'); - global.util = require('__util__.js'); - global.device = runtime.device; - - //设置JavaScriptBridges用于与Java层的交互和数据转换 - runtime.bridges.setBridges(require('__bridges__.js')); - - //一些内部函数 + //内部函数 global.__asGlobal__ = function (obj, functions) { var len = functions.length; for (var i = 0; i < len; i++) { @@ -50,7 +41,6 @@ runtime.init(); exit(err); } else if (err instanceof Error) { exit(new org.mozilla.javascript.EvaluatorException(err.name + ": " + err.message, err.fileName, err.lineNumber)); - //new java.lang.RuntimeException(err.name + ": " + err.message + "\n" + err.stack)); } else { exit(); } @@ -58,12 +48,24 @@ runtime.init(); } }; + // 初始化基础模块 + global.timers = require('__timers__.js')(runtime, global); + + //初始化不依赖环境的模块 + global.JSON = require('__json2__.js'); + global.util = require('__util__.js'); + global.device = runtime.device; + global.Promise = require('promise.js'); + + //设置JavaScriptBridges用于与Java层的交互和数据转换 + runtime.bridges.setBridges(require('__bridges__.js')); + //初始化全局函数 require("__globals__")(runtime, global); //初始化一般模块 (function (scope) { var modules = ['app', 'automator', 'console', 'dialogs', 'io', 'selector', 'shell', 'web', 'ui', - "images", "timers", "threads", "events", "engines", "RootAutomator", "http", "storages", "floaty", + "images", "threads", "events", "engines", "RootAutomator", "http", "storages", "floaty", "sensors", "media", "plugins", "continuation"]; var len = modules.length; for (var i = 0; i < len; i++) { @@ -72,11 +74,6 @@ runtime.init(); } })(global); - global.Promise = require('promise.js'); - global.Promise.prototype.await = function () { - return continuation.await(this); - } - importClass(android.view.KeyEvent); importClass(com.stardust.autojs.core.util.Shell); importClass(android.graphics.Paint); diff --git a/autojs/src/main/assets/modules/__continuation__.js b/autojs/src/main/assets/modules/__continuation__.js index 532139c7..15fa8a5b 100644 --- a/autojs/src/main/assets/modules/__continuation__.js +++ b/autojs/src/main/assets/modules/__continuation__.js @@ -44,5 +44,21 @@ module.exports = function (runtime, global) { throw new TypeError('cannot await ' + any); } + continuation.delay = function (millis) { + var cont = continuation.create(); + setTimeout(()=>{ + cont.resume(); + }, millis); + cont.await(); + } + + continuation.__defineGetter__('enabled', function () { + return engines.myEngine().hasFeature("continuation"); + }); + + global.Promise.prototype.await = function () { + return continuation.await(this); + } + return continuation; } \ No newline at end of file diff --git a/autojs/src/main/assets/modules/__http__.js b/autojs/src/main/assets/modules/__http__.js index 431e4768..551714d2 100644 --- a/autojs/src/main/assets/modules/__http__.js +++ b/autojs/src/main/assets/modules/__http__.js @@ -41,7 +41,7 @@ module.exports = function (runtime, scope) { http.request = function (url, options, callback) { var cont = null; - if (!callback && ui.isUiThread()) { + if (!callback && ui.isUiThread() && continuation.enabled) { cont = continuation.create(); } var call = http.client().newCall(http.buildRequest(url, options)); @@ -59,7 +59,7 @@ module.exports = function (runtime, scope) { callback && callback(null, ex); } })); - if(cont) { + if (cont) { return cont.await(); } } diff --git a/autojs/src/main/assets/modules/__images__.js b/autojs/src/main/assets/modules/__images__.js index 3f444eba..587e10af 100644 --- a/autojs/src/main/assets/modules/__images__.js +++ b/autojs/src/main/assets/modules/__images__.js @@ -65,7 +65,7 @@ module.exports = function (runtime, scope) { if(landscape === false){ orientation = ScreenCapturer.ORIENTATION_PORTRAIT; } - return ResultAdapter.promise(javaImages.requestScreenCapture(orientation)).await(); + return ResultAdapter.wait(javaImages.requestScreenCapture(orientation)); } images.save = function (img, path, format, quality) { diff --git a/autojs/src/main/assets/modules/__threads__.js b/autojs/src/main/assets/modules/__threads__.js index 01bc05f4..1e3c1a26 100644 --- a/autojs/src/main/assets/modules/__threads__.js +++ b/autojs/src/main/assets/modules/__threads__.js @@ -1,12 +1,26 @@ -module.exports = function(__runtime__, scope){ - var threads = Object.create(__runtime__.threads); +module.exports = function (__runtime__, scope) { + var threads = Object.create(__runtime__.threads); - scope.sync = function(func, lock){ + scope.sync = function (func, lock) { lock = lock || null; return new org.mozilla.javascript.Synchronizer(func, lock); - } + } - return threads; + global.Promise.prototype.wait = function () { + var disposable = threads.disposable(); + promise.then(result => { + disposable.setAndNotify({ result: result }); + }).catch(error => { + cont.resumeError({ error: error }); + }); + var r = cont.blockedGet(); + if (r.error) { + throw r.error; + } + return r.result; + } + + return threads; } \ No newline at end of file diff --git a/autojs/src/main/assets/modules/result_adapter.js b/autojs/src/main/assets/modules/result_adapter.js index 0f52acbe..b9d2496d 100644 --- a/autojs/src/main/assets/modules/result_adapter.js +++ b/autojs/src/main/assets/modules/result_adapter.js @@ -47,7 +47,7 @@ ResultAdapter.prototype.setError = function (error) { ResultAdapter.prototype.callback = function () { var that = this; return function (result, error) { - if(that.result !== undefined){ + if (that.result !== undefined) { that.result = { result: result, error: error @@ -63,21 +63,33 @@ ResultAdapter.prototype.callback = function () { } ResultAdapter.prototype.get = function () { - if(this.result){ + if (this.result) { return getOrThrow(this.result); } this.result = null; return this.impl.get(); } -ResultAdapter.promise = function(promiseAdapter) { - return new Promise(function(resolve, reject){ - promiseAdapter.onResolve(function(result) { +ResultAdapter.promise = function (promiseAdapter) { + return new Promise(function (resolve, reject) { + promiseAdapter.onResolve(function (result) { resolve(result); - }).onReject(function(error){ + }).onReject(function (error) { reject(error); }); }) } +ResultAdapter.wait = function (promise) { + var proto = Object.getPrototypeOf(promise); + if (!proto || proto.constructor !== Promise) { + promise = ResultAdapter.promise(promise); + } + if (continuation.enabled) { + return promise.await(); + } else { + return promise.wait(); + } +} + module.exports = ResultAdapter; \ No newline at end of file diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/dialog/BlockedMaterialDialog.java b/autojs/src/main/java/com/stardust/autojs/core/ui/dialog/BlockedMaterialDialog.java index c1ef2ee4..fb5f5766 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/ui/dialog/BlockedMaterialDialog.java +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/dialog/BlockedMaterialDialog.java @@ -63,7 +63,6 @@ public class BlockedMaterialDialog extends MaterialDialog { private VolatileDispose mResultBox; private UiHandler mUiHandler; private Object mCallback; - private Continuation mContinuation; private ScriptBridges mScriptBridges; private boolean mNotified = false; @@ -75,10 +74,6 @@ public class BlockedMaterialDialog extends MaterialDialog { mCallback = callback; if (Looper.getMainLooper() != Looper.myLooper()) { mResultBox = new VolatileDispose<>(); - } else { - if (mCallback == null) { - mContinuation = runtime.createContinuation(); - } } } @@ -96,9 +91,6 @@ public class BlockedMaterialDialog extends MaterialDialog { if (mCallback != null) { mScriptBridges.callFunction(mCallback, null, new Object[]{r}); } - if (mContinuation != null) { - mContinuation.resumeWith(Continuation.Result.Companion.success(r)); - } if (mResultBox != null) { mResultBox.setAndNotify(r); } @@ -179,9 +171,6 @@ public class BlockedMaterialDialog extends MaterialDialog { } else { mUiHandler.post(Builder.super::show); } - if (mContinuation != null) { - mContinuation.suspend(); - } if (mResultBox != null) { return mResultBox.blockedGetOrThrow(ScriptInterruptedException.class); } else { diff --git a/autojs/src/main/java/com/stardust/autojs/engine/LoopBasedJavaScriptEngine.java b/autojs/src/main/java/com/stardust/autojs/engine/LoopBasedJavaScriptEngine.java index 1db6b8aa..9c118f70 100644 --- a/autojs/src/main/java/com/stardust/autojs/engine/LoopBasedJavaScriptEngine.java +++ b/autojs/src/main/java/com/stardust/autojs/engine/LoopBasedJavaScriptEngine.java @@ -46,8 +46,7 @@ public class LoopBasedJavaScriptEngine extends RhinoJavaScriptEngine { Object o = LoopBasedJavaScriptEngine.super.execute((JavaScriptSource) source); if (callback != null) callback.onResult(o); - } catch (ContinuationPending pending) { - pending.printStackTrace(); + } catch (ContinuationPending ignored) { } catch (Exception e) { if (callback == null) { throw e; @@ -61,8 +60,17 @@ public class LoopBasedJavaScriptEngine extends RhinoJavaScriptEngine { mHandler.post(r); if (!mLooping && Looper.myLooper() != Looper.getMainLooper()) { mLooping = true; - Looper.loop(); - mLooping = false; + while (true) { + try { + Looper.loop(); + } catch (ContinuationPending ignored) { + continue; + } catch (Throwable t) { + mLooping = false; + throw t; + } + break; + } } } @@ -79,8 +87,7 @@ public class LoopBasedJavaScriptEngine extends RhinoJavaScriptEngine { @Override public synchronized void destroy() { Thread thread = getThread(); - if (thread != null) - LooperHelper.quitForThread(thread); + LooperHelper.quitForThread(thread); super.destroy(); } diff --git a/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.java b/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.java deleted file mode 100644 index 0cbca254..00000000 --- a/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.java +++ /dev/null @@ -1,197 +0,0 @@ -package com.stardust.autojs.engine; - -import android.util.Log; -import android.view.View; - -import com.stardust.autojs.BuildConfig; -import com.stardust.autojs.core.ui.ViewExtras; -import com.stardust.autojs.engine.module.AssetAndUrlModuleSourceProvider; -import com.stardust.autojs.rhino.RhinoAndroidHelper; -import com.stardust.autojs.rhino.TopLevelScope; -import com.stardust.autojs.runtime.ScriptRuntime; -import com.stardust.autojs.script.JavaScriptSource; -import com.stardust.automator.UiObjectCollection; -import com.stardust.pio.UncheckedIOException; - -import org.mozilla.javascript.Context; -import org.mozilla.javascript.Script; -import org.mozilla.javascript.Scriptable; -import org.mozilla.javascript.ScriptableObject; -import org.mozilla.javascript.commonjs.module.RequireBuilder; -import org.mozilla.javascript.commonjs.module.provider.SoftCachingModuleScriptProvider; - -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.Collections; -import java.util.Locale; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Created by Stardust on 2017/4/2. - */ - -public class RhinoJavaScriptEngine extends JavaScriptEngine { - - public static final String SOURCE_NAME_INIT = ""; - - private static final String LOG_TAG = "RhinoJavaScriptEngine"; - - private static final String MODULES_PATH = "modules"; - private static Script sInitScript; - private static final ConcurrentHashMap sContextEngineMap = new ConcurrentHashMap<>(); - - private Context mContext; - private TopLevelScope mScriptable; - private Thread mThread; - private android.content.Context mAndroidContext; - - public RhinoJavaScriptEngine(android.content.Context context) { - mAndroidContext = context; - mContext = enterContext(); - mScriptable = createScope(mContext); - } - - @Override - public void put(String name, Object value) { - ScriptableObject.putProperty(mScriptable, name, Context.javaToJS(value, mScriptable)); - } - - @Override - public void setRuntime(ScriptRuntime runtime) { - super.setRuntime(runtime); - runtime.setTopLevelScope(mScriptable); - } - - @Override - public Object doExecution(JavaScriptSource source) { - Reader reader = source.getNonNullScriptReader(); - try { - reader = preprocess(reader); - Script script = mContext.compileReader(reader, source.toString(), 1, null); - return mContext.executeScriptWithContinuations(script, mScriptable); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - protected Reader preprocess(Reader script) throws IOException { - return script; - } - - @Override - public void forceStop() { - Log.d(LOG_TAG, "forceStop: interrupt Thread: " + mThread); - mThread.interrupt(); - } - - - @Override - public synchronized void destroy() { - super.destroy(); - Log.d(LOG_TAG, "on destroy"); - sContextEngineMap.remove(getContext()); - Context.exit(); - } - - public Thread getThread() { - return mThread; - } - - @SuppressWarnings("unchecked") - @Override - public void init() { - mThread = Thread.currentThread(); - ScriptableObject.putProperty(mScriptable, "__engine__", this); - initRequireBuilder(mContext, mScriptable); - mContext.executeScriptWithContinuations(getInitScript(), mScriptable); - } - - private Script getInitScript() { - if (sInitScript == null || BuildConfig.DEBUG) { - try { - Reader reader = new InputStreamReader(mAndroidContext.getAssets().open("init.js")); - sInitScript = mContext.compileReader(reader, SOURCE_NAME_INIT, 1, null); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - return sInitScript; - } - - void initRequireBuilder(Context context, Scriptable scope) { - AssetAndUrlModuleSourceProvider provider = new AssetAndUrlModuleSourceProvider(mAndroidContext, MODULES_PATH, - Collections.singletonList(new File("/").toURI())); - new RequireBuilder() - .setModuleScriptProvider(new SoftCachingModuleScriptProvider(provider)) - .setSandboxed(true) - .createRequire(context, scope) - .install(scope); - - } - - public Context getContext() { - return mContext; - } - - public Scriptable getScriptable() { - return mScriptable; - } - - protected TopLevelScope createScope(Context context) { - TopLevelScope topLevelScope = new TopLevelScope(); - topLevelScope.initStandardObjects(context, false); - return topLevelScope; - } - - public Context enterContext() { - Context context = new RhinoAndroidHelper(mAndroidContext).enterContext(); - setupContext(context); - sContextEngineMap.put(context, this); - return context; - } - - protected void setupContext(Context context) { - context.setOptimizationLevel(-1); - context.setLanguageVersion(Context.VERSION_ES6); - context.setLocale(Locale.getDefault()); - context.setWrapFactory(new WrapFactory()); - } - - - public static RhinoJavaScriptEngine getEngineOfContext(Context context) { - return sContextEngineMap.get(context); - } - - private class WrapFactory extends org.mozilla.javascript.WrapFactory { - - @Override - public Object wrap(Context cx, Scriptable scope, Object obj, Class staticType) { - Object result; - if (obj instanceof String) { - result = getRuntime().bridges.toString(obj.toString()); - } else if (staticType == UiObjectCollection.class) { - result = getRuntime().bridges.asArray(obj); - } else { - result = super.wrap(cx, scope, obj, staticType); - } - return result; - } - - @Override - public Scriptable wrapAsJavaObject(Context cx, Scriptable scope, Object javaObject, Class staticType) { - Scriptable result; - if (javaObject instanceof View) { - result = ViewExtras.getNativeView(scope, (View) javaObject, staticType, getRuntime()); - } else { - result = super.wrapAsJavaObject(cx, scope, javaObject, staticType); - } - //Log.d(LOG_TAG, "wrapAsJavaObject: java = " + javaObject + ", result = " + result + ", scope = " + scope); - return result; - } - - } - - -} diff --git a/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.kt b/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.kt new file mode 100644 index 00000000..feccf132 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.kt @@ -0,0 +1,191 @@ +package com.stardust.autojs.engine + +import android.util.Log +import android.view.View + +import com.stardust.autojs.BuildConfig +import com.stardust.autojs.core.ui.ViewExtras +import com.stardust.autojs.engine.module.AssetAndUrlModuleSourceProvider +import com.stardust.autojs.execution.ExecutionConfig +import com.stardust.autojs.project.ScriptConfig +import com.stardust.autojs.rhino.RhinoAndroidHelper +import com.stardust.autojs.rhino.TopLevelScope +import com.stardust.autojs.runtime.ScriptRuntime +import com.stardust.autojs.script.JavaScriptSource +import com.stardust.automator.UiObjectCollection +import com.stardust.pio.UncheckedIOException + +import org.mozilla.javascript.Context +import org.mozilla.javascript.Script +import org.mozilla.javascript.Scriptable +import org.mozilla.javascript.ScriptableObject +import org.mozilla.javascript.commonjs.module.RequireBuilder +import org.mozilla.javascript.commonjs.module.provider.SoftCachingModuleScriptProvider + +import java.io.File +import java.io.IOException +import java.io.InputStreamReader +import java.io.Reader +import java.net.URI +import java.util.Locale +import java.util.concurrent.ConcurrentHashMap + +/** + * Created by Stardust on 2017/4/2. + */ + +open class RhinoJavaScriptEngine(private val mAndroidContext: android.content.Context) : JavaScriptEngine() { + + val context: Context + private val mScriptable: TopLevelScope + lateinit var thread: Thread + private set + + private val initScript: Script + get() { + return sInitScript ?: { + try { + val reader = InputStreamReader(mAndroidContext.assets.open("init.js")) + val script = context.compileReader(reader, SOURCE_NAME_INIT, 1, null) + sInitScript = script + script + } catch (e: IOException) { + throw UncheckedIOException(e) + } + }() + } + + val scriptable: Scriptable + get() = mScriptable + + init { + this.context = enterContext() + mScriptable = createScope(this.context) + } + + override fun put(name: String, value: Any?) { + ScriptableObject.putProperty(mScriptable, name, Context.javaToJS(value, mScriptable)) + } + + override fun setRuntime(runtime: ScriptRuntime) { + super.setRuntime(runtime) + runtime.topLevelScope = mScriptable + } + + public override fun doExecution(source: JavaScriptSource): Any { + var reader = source.nonNullScriptReader + try { + reader = preprocess(reader) + val script = context.compileReader(reader, source.toString(), 1, null) + return if (hasFeature(ScriptConfig.FEATURE_CONTINUATION)) { + context.executeScriptWithContinuations(script, mScriptable) + } else { + script.exec(context, mScriptable) + } + } catch (e: IOException) { + throw UncheckedIOException(e) + } + + } + + fun hasFeature(feature: String): Boolean { + val config = getTag(ExecutionConfig.tag) as ExecutionConfig? + return config != null && config.scriptConfig.hasFeature(feature) + } + + @Throws(IOException::class) + protected fun preprocess(script: Reader): Reader { + return script + } + + override fun forceStop() { + Log.d(LOG_TAG, "forceStop: interrupt Thread: $thread") + thread.interrupt() + } + + + @Synchronized + override fun destroy() { + super.destroy() + Log.d(LOG_TAG, "on destroy") + sContextEngineMap.remove(context) + Context.exit() + } + + override fun init() { + thread = Thread.currentThread() + ScriptableObject.putProperty(mScriptable, "__engine__", this) + initRequireBuilder(context, mScriptable) + context.executeScriptWithContinuations(initScript, mScriptable) + } + + internal fun initRequireBuilder(context: Context, scope: Scriptable) { + val provider = AssetAndUrlModuleSourceProvider(mAndroidContext, MODULES_PATH, + listOf(File("/").toURI())) + RequireBuilder() + .setModuleScriptProvider(SoftCachingModuleScriptProvider(provider)) + .setSandboxed(true) + .createRequire(context, scope) + .install(scope) + + } + + protected fun createScope(context: Context): TopLevelScope { + val topLevelScope = TopLevelScope() + topLevelScope.initStandardObjects(context, false) + return topLevelScope + } + + fun enterContext(): Context { + val context = RhinoAndroidHelper(mAndroidContext).enterContext() + setupContext(context) + sContextEngineMap[context] = this + return context + } + + protected fun setupContext(context: Context) { + context.optimizationLevel = -1 + context.languageVersion = Context.VERSION_ES6 + context.locale = Locale.getDefault() + context.wrapFactory = WrapFactory() + } + + private inner class WrapFactory : org.mozilla.javascript.WrapFactory() { + + override fun wrap(cx: Context, scope: Scriptable, obj: Any?, staticType: Class<*>?): Any? { + return when { + obj is String -> runtime.bridges.toString(obj.toString()) + staticType == UiObjectCollection::class.java -> runtime.bridges.asArray(obj) + else -> super.wrap(cx, scope, obj, staticType) + } + } + + override fun wrapAsJavaObject(cx: Context?, scope: Scriptable, javaObject: Any?, staticType: Class<*>?): Scriptable? { + //Log.d(LOG_TAG, "wrapAsJavaObject: java = " + javaObject + ", result = " + result + ", scope = " + scope); + return if (javaObject is View) { + ViewExtras.getNativeView(scope, javaObject, staticType, runtime) + } else { + super.wrapAsJavaObject(cx, scope, javaObject, staticType) + } + } + + } + + companion object { + + val SOURCE_NAME_INIT = "" + + private val LOG_TAG = "RhinoJavaScriptEngine" + + private val MODULES_PATH = "modules" + private var sInitScript: Script? = null + private val sContextEngineMap = ConcurrentHashMap() + + + fun getEngineOfContext(context: Context): RhinoJavaScriptEngine? { + return sContextEngineMap[context] + } + } + + +} diff --git a/autojs/src/main/java/com/stardust/autojs/execution/ExecutionConfig.kt b/autojs/src/main/java/com/stardust/autojs/execution/ExecutionConfig.kt index 2d838c71..3044be3b 100644 --- a/autojs/src/main/java/com/stardust/autojs/execution/ExecutionConfig.kt +++ b/autojs/src/main/java/com/stardust/autojs/execution/ExecutionConfig.kt @@ -2,6 +2,7 @@ package com.stardust.autojs.execution import android.os.Parcel import android.os.Parcelable +import com.stardust.autojs.project.ScriptConfig import java.util.* /** @@ -13,8 +14,7 @@ data class ExecutionConfig(var workingDirectory: String = "", var delay: Long = 0, var interval: Long = 0, var loopTimes: Int = 1, - var uiMode: Boolean = false, - var features: Int = 0) : Parcelable { + var scriptConfig: ScriptConfig = ScriptConfig()) : Parcelable { private val mArguments = HashMap() @@ -88,9 +88,6 @@ data class ExecutionConfig(var workingDirectory: String = "", val default: ExecutionConfig get() = ExecutionConfig() - @JvmStatic - val featureContinuation = 1 - override fun createFromParcel(parcel: Parcel): ExecutionConfig { return ExecutionConfig(parcel) } diff --git a/autojs/src/main/java/com/stardust/autojs/execution/RunnableScriptExecution.java b/autojs/src/main/java/com/stardust/autojs/execution/RunnableScriptExecution.java index 72173afe..6e35444e 100644 --- a/autojs/src/main/java/com/stardust/autojs/execution/RunnableScriptExecution.java +++ b/autojs/src/main/java/com/stardust/autojs/execution/RunnableScriptExecution.java @@ -8,13 +8,14 @@ import com.stardust.autojs.runtime.exception.ScriptInterruptedException; import com.stardust.autojs.script.ScriptSource; import com.stardust.lang.ThreadCompat; +import org.mozilla.javascript.ContinuationPending; + /** * Created by Stardust on 2017/5/1. */ public class RunnableScriptExecution extends ScriptExecution.AbstractScriptExecution implements Runnable { - private static final String TAG = "RunnableJSExecution"; private ScriptEngine mScriptEngine; private ScriptEngineManager mScriptEngineManager; diff --git a/autojs/src/main/java/com/stardust/autojs/project/LaunchConfig.java b/autojs/src/main/java/com/stardust/autojs/project/LaunchConfig.java index a358368e..a10d38e8 100644 --- a/autojs/src/main/java/com/stardust/autojs/project/LaunchConfig.java +++ b/autojs/src/main/java/com/stardust/autojs/project/LaunchConfig.java @@ -18,4 +18,5 @@ public class LaunchConfig { public void setHideLogs(boolean hideLogs) { mHideLogs = hideLogs; } + } diff --git a/autojs/src/main/java/com/stardust/autojs/project/ProjectConfig.java b/autojs/src/main/java/com/stardust/autojs/project/ProjectConfig.java index 3ec661dd..0f61c122 100644 --- a/autojs/src/main/java/com/stardust/autojs/project/ProjectConfig.java +++ b/autojs/src/main/java/com/stardust/autojs/project/ProjectConfig.java @@ -1,7 +1,6 @@ package com.stardust.autojs.project; import android.content.Context; -import androidx.annotation.NonNull; import android.text.TextUtils; import com.google.gson.Gson; @@ -12,7 +11,9 @@ import com.stardust.pio.PFiles; import java.io.File; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Created by Stardust on 2018/1/24. @@ -51,31 +52,38 @@ public class ProjectConfig { @SerializedName("icon") private String mIcon; + @SerializedName("scripts") + private Map mScriptConfigs = new HashMap<>(); + + @SerializedName("useFeatures") + private List mFeatures = new ArrayList<>(); + + public static ProjectConfig fromJson(String json) { if (json == null) { return null; } ProjectConfig config = GSON.fromJson(json, ProjectConfig.class); - if(!isValid(config)){ + if (!isValid(config)) { return null; } return config; } private static boolean isValid(ProjectConfig config) { - if(TextUtils.isEmpty(config.getName())){ + if (TextUtils.isEmpty(config.getName())) { return false; } - if(TextUtils.isEmpty(config.getPackageName())){ + if (TextUtils.isEmpty(config.getPackageName())) { return false; } - if(TextUtils.isEmpty(config.getVersionName())){ + if (TextUtils.isEmpty(config.getVersionName())) { return false; } - if(TextUtils.isEmpty(config.getMainScriptFile())){ + if (TextUtils.isEmpty(config.getMainScriptFile())) { return false; } - if(config.getVersionCode() == -1){ + if (config.getVersionCode() == -1) { return false; } return true; @@ -160,6 +168,10 @@ public class ProjectConfig { return this; } + public Map getScriptConfigs() { + return mScriptConfigs; + } + public List getAssets() { if (mAssets == null) { mAssets = Collections.emptyList(); @@ -210,4 +222,30 @@ public class ProjectConfig { public String getBuildDir() { return "build"; } + + public List getFeatures() { + return mFeatures; + } + + public void setFeatures(List features) { + mFeatures = features; + } + + public ScriptConfig getScriptConfig(String path) { + ScriptConfig config = mScriptConfigs.get(path); + if (config == null) { + config = new ScriptConfig(); + } + if(mFeatures.isEmpty()){ + return config; + } + ArrayList features = new ArrayList<>(config.getFeatures()); + for (String feature : mFeatures) { + if (!features.contains(feature)) { + features.add(feature); + } + } + config.setFeatures(features); + return config; + } } diff --git a/autojs/src/main/java/com/stardust/autojs/project/ProjectLauncher.java b/autojs/src/main/java/com/stardust/autojs/project/ProjectLauncher.java index c0e74a47..c8e85349 100644 --- a/autojs/src/main/java/com/stardust/autojs/project/ProjectLauncher.java +++ b/autojs/src/main/java/com/stardust/autojs/project/ProjectLauncher.java @@ -18,9 +18,10 @@ public class ProjectLauncher { mMainScriptFile = new File(mProjectDir, mProjectConfig.getMainScriptFile()); } - public void launch(ScriptEngineService service){ + public void launch(ScriptEngineService service) { ExecutionConfig config = new ExecutionConfig(); config.setWorkingDirectory(mProjectDir); + config.getScriptConfig().setFeatures(mProjectConfig.getFeatures()); service.execute(new JavaScriptFileSource(mMainScriptFile), config); } diff --git a/autojs/src/main/java/com/stardust/autojs/project/ScriptConfig.kt b/autojs/src/main/java/com/stardust/autojs/project/ScriptConfig.kt new file mode 100644 index 00000000..5737a34b --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/project/ScriptConfig.kt @@ -0,0 +1,18 @@ +package com.stardust.autojs.project + +import com.google.gson.annotations.SerializedName + +data class ScriptConfig( + @SerializedName("useFeatures") var features: List, + @SerializedName("uiMode") var uiMode: Boolean +) { + constructor() : this(emptyList(), false) + + fun hasFeature(feature: String): Boolean { + return features.contains(feature) + } + + companion object { + val FEATURE_CONTINUATION = "continuation" + } +} \ No newline at end of file diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/Dialogs.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/Dialogs.java index b5caf674..14425ba1 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/Dialogs.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/Dialogs.java @@ -65,7 +65,7 @@ public class Dialogs { builder.content(content); } return ((BlockedMaterialDialog.Builder) builder).showAndGet(); - } +} private Context getContext() { if (mThemeWrapper != null) diff --git a/autojs/src/main/java/org/mozilla/javascript/VMBridge_custom.java b/autojs/src/main/java/org/mozilla/javascript/VMBridge_custom.java index 6f1d1e7c..bcb67541 100644 --- a/autojs/src/main/java/org/mozilla/javascript/VMBridge_custom.java +++ b/autojs/src/main/java/org/mozilla/javascript/VMBridge_custom.java @@ -23,7 +23,7 @@ public class VMBridge_custom extends VMBridge_jdk15 { protected Object newInterfaceProxy(Object proxyHelper, ContextFactory cf, InterfaceAdapter adapter, Object target, Scriptable topScope) { Context context = Context.getCurrentContext(); InterfaceAdapterWrapper adapterWrapper = new InterfaceAdapterWrapper(adapter, context); - RhinoJavaScriptEngine engine = RhinoJavaScriptEngine.getEngineOfContext(context); + RhinoJavaScriptEngine engine = RhinoJavaScriptEngine.Companion.getEngineOfContext(context); // --- The following code is copied from super class -- Constructor c = (Constructor) proxyHelper; InvocationHandler handler = (proxy, method, args) -> { @@ -50,6 +50,8 @@ public class VMBridge_custom extends VMBridge_jdk15 { try { Object result = adapterWrapper.invoke(cf, target, topScope, proxy, method, args); return castReturnValue(method, result); + } catch (ContinuationPending pending) { + return defaultValue(method.getReturnType()); } catch (Throwable e) { e.printStackTrace(); // notify the script thread to exit @@ -143,6 +145,7 @@ public class VMBridge_custom extends VMBridge_jdk15 { public Object invokeImpl(Context cx, Object target, Scriptable topScope, Object thisObject, Method method, Object[] args) { + cx.isContinuationsTopCall = true; return mInterfaceAdapter.invokeImpl(cx, target, topScope, thisObject, method, args); } }