From 8b907d31c84a753b5e3588e92e52d5e33b00ac9d Mon Sep 17 00:00:00 2001 From: hyb1996 <946994919@qq.com> Date: Sun, 9 Dec 2018 14:19:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E7=89=B9=E6=80=A7continua?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autojs/src/main/assets/init.js | 60 ++++--- autojs/src/main/assets/modules/__http__.js | 147 +++++++++--------- autojs/src/main/assets/modules/__images__.js | 15 +- .../core/image/capture/ScreenCapturer.java | 5 +- .../stardust/autojs/core/looper/Loopers.java | 6 + .../core/ui/dialog/BlockedMaterialDialog.java | 20 ++- .../autojs/core/util/ScriptPromiseAdapter.kt | 49 ++++++ .../engine/LoopBasedJavaScriptEngine.java | 5 +- .../autojs/engine/RhinoJavaScriptEngine.java | 29 ++-- .../execution/ScriptExecuteActivity.java | 6 + .../stardust/autojs/rhino/AutoJsContext.kt | 31 ++++ .../InterruptibleAndroidContextFactory.java | 2 +- .../autojs/rhino/continuation/Continuation.kt | 59 +++++++ .../autojs/runtime/ScriptBridges.java | 1 - .../autojs/runtime/ScriptRuntime.java | 22 +-- .../stardust/autojs/runtime/api/Dialogs.java | 2 +- .../stardust/autojs/runtime/api/Images.java | 31 ++-- .../stardust/autojs/runtime/api/Timers.java | 10 +- 18 files changed, 349 insertions(+), 151 deletions(-) create mode 100644 autojs/src/main/java/com/stardust/autojs/core/util/ScriptPromiseAdapter.kt create mode 100644 autojs/src/main/java/com/stardust/autojs/rhino/AutoJsContext.kt create mode 100644 autojs/src/main/java/com/stardust/autojs/rhino/continuation/Continuation.kt diff --git a/autojs/src/main/assets/init.js b/autojs/src/main/assets/init.js index 7918ea99..1b86ef43 100644 --- a/autojs/src/main/assets/init.js +++ b/autojs/src/main/assets/init.js @@ -2,23 +2,22 @@ var global = this; runtime.init(); -(function(){ +(function () { //重定向importClass使得其支持字符串参数 global.importClass = - (function(){ - var __importClass__ = importClass; - return function(pack){ - if(typeof(pack) == "string"){ - __importClass__(Packages[pack]); - }else{ - __importClass__(pack); + (function () { + var __importClass__ = importClass; + return function (pack) { + if (typeof (pack) == "string") { + __importClass__(Packages[pack]); + } else { + __importClass__(pack); + } } - } - })(); + })(); //初始化不依赖环境的模块 - global.Promise = require('promise.js'); global.JSON = require('__json2__.js'); global.util = require('__util__.js'); global.device = runtime.device; @@ -27,26 +26,32 @@ runtime.init(); runtime.bridges.setBridges(require('__bridges__.js')); //一些内部函数 - global.__asGlobal__ = function(obj, functions){ + global.__asGlobal__ = function (obj, functions) { var len = functions.length; - for(var i = 0; i < len; i++) { + for (var i = 0; i < len; i++) { var funcName = functions[i]; - if(obj[funcName]){ - global[funcName] = obj[funcName].bind(obj); + var func = obj[funcName] + if (!func) { + continue; } + (function (obj, funcName, func) { + global[funcName] = function () { + return func.apply(obj, arguments); + }; + })(obj, funcName, func); } } - global.__exitIfError__ = function(action, defReturnValue){ - try{ - return action(); - }catch(err){ - if(err instanceof java.lang.Throwable){ + global.__exitIfError__ = function (action, defReturnValue) { + try { + return action(); + } catch (err) { + if (err instanceof java.lang.Throwable) { exit(err); - }else if(err instanceof Error){ + } 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{ + } else { exit(); } return defReturnValue; @@ -56,17 +61,22 @@ runtime.init(); //初始化全局函数 require("__globals__")(runtime, global); //初始化一般模块 - (function(scope){ + (function (scope) { var modules = ['app', 'automator', 'console', 'dialogs', 'io', 'selector', 'shell', 'web', 'ui', "images", "timers", "threads", "events", "engines", "RootAutomator", "http", "storages", "floaty", - "sensors", "media", "plugins"]; + "sensors", "media", "plugins", "continuation"]; var len = modules.length; - for(var i = 0; i < len; i++) { + for (var i = 0; i < len; i++) { var m = modules[i]; scope[m] = require('__' + m + '__')(scope.runtime, scope); } })(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/__http__.js b/autojs/src/main/assets/modules/__http__.js index 4a7e1bf7..431e4768 100644 --- a/autojs/src/main/assets/modules/__http__.js +++ b/autojs/src/main/assets/modules/__http__.js @@ -1,100 +1,107 @@ -module.exports = function(runtime, scope){ +module.exports = function (runtime, scope) { importPackage(Packages["okhttp3"]); importClass(com.stardust.autojs.core.http.MutableOkHttp); var http = {}; http.__okhttp__ = new MutableOkHttp(); - http.get = function(url, options, callback){ + http.get = function (url, options, callback) { options = options || {}; options.method = "GET"; return http.request(url, options, callback); } - http.client = function(){ + http.client = function () { return http.__okhttp__.client(); } - http.post = function(url, data, options, callback){ + http.post = function (url, data, options, callback) { options = options || {}; options.method = "POST"; options.contentType = options.contentType || "application/x-www-form-urlencoded"; - if(data){ + if (data) { fillPostData(options, data); } return http.request(url, options, callback); } - http.postJson = function(url, data, options, callback){ - options = options || {}; - options.contentType = "application/json"; - return http.post(url, data, options, callback); + http.postJson = function (url, data, options, callback) { + options = options || {}; + options.contentType = "application/json"; + return http.post(url, data, options, callback); } - http.postMultipart = function(url, files, options, callback){ - options = options || {}; - options.method = "POST"; - options.contentType = "multipart/form-data"; - options.files = files; - return http.request(url, options, callback); + http.postMultipart = function (url, files, options, callback) { + options = options || {}; + options.method = "POST"; + options.contentType = "multipart/form-data"; + options.files = files; + return http.request(url, options, callback); } - http.request = function(url, options, callback){ - if(!callback && ui.isUiThread()){ - throw new Error("不能在ui线程执行网络操作,请加上回调函数的参数callback或在子线程执行"); + http.request = function (url, options, callback) { + var cont = null; + if (!callback && ui.isUiThread()) { + cont = continuation.create(); } var call = http.client().newCall(http.buildRequest(url, options)); - if(!callback){ + if (!callback && !cont) { return wrapResponse(call.execute()); } call.enqueue(new Callback({ - onResponse: function(call, res){ - callback(wrapResponse(res)); + onResponse: function (call, res) { + res = wrapResponse(res); + cont && cont.resume(res); + callback && callback(res); }, - onFailure: function(call, ex){ - callback(null, ex); + onFailure: function (call, ex) { + cont && cont.resumeError(ex); + callback && callback(null, ex); } })); + if(cont) { + return cont.await(); + } } - http.buildRequest = function(url, options){ + http.buildRequest = function (url, options) { var r = new Request.Builder(); - if(!url.startsWith("http://") && !url.startsWith("https://")){ + if (!url.startsWith("http://") && !url.startsWith("https://")) { url = "http://" + url; } r.url(url); - if(options.headers){ + if (options.headers) { setHeaders(r, options.headers); } - if(options.body){ + if (options.body) { r.method(options.method, parseBody(options, options.body)); - }else if(options.files){ + } else if (options.files) { r.method(options.method, parseMultipart(options.files)); - }else { + } else { r.method(options.method, null); } return r.build(); } - function parseMultipart(files){ + function parseMultipart(files) { var builder = new MultipartBody.Builder() .setType(MultipartBody.FORM); - for(var key in files){ - if(!files.hasOwnProperty(key)){ + for (var key in files) { + if (!files.hasOwnProperty(key)) { continue; } var value = files[key]; - if(typeof(value) == 'string'){ + if (typeof (value) == 'string') { builder.addFormDataPart(key, value); continue; } var path, mimeType, fileName; - if(typeof(value.getPath) == 'function'){ + if (typeof (value.getPath) == 'function') { path = value.getPath(); - }else if(value.length == 2){ + } else if (value.length == 2) { fileName = value[0]; path = value[1]; - }else if(value.length >= 3){ + } else if (value.length >= 3) { fileName = value[0]; mimeType = value[1] path = value[2]; @@ -107,77 +114,77 @@ module.exports = function(runtime, scope){ return builder.build(); } - function parseMimeType(ext){ - if(ext.length == 0){ + function parseMimeType(ext) { + if (ext.length == 0) { return "application/octet-stream"; } return android.webkit.MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext) || "application/octet-stream"; } - function fillPostData(options, data){ - if(options.contentType == "application/x-www-form-urlencoded"){ + function fillPostData(options, data) { + if (options.contentType == "application/x-www-form-urlencoded") { var b = new FormBody.Builder(); - for(var key in data){ - if(data.hasOwnProperty(key)){ + for (var key in data) { + if (data.hasOwnProperty(key)) { b.add(key, data[key]); } } options.body = b.build(); - }else if(options.contentType == "application/json"){ + } else if (options.contentType == "application/json") { options.body = JSON.stringify(data); - }else{ + } else { options.body = data; } } - function setHeaders(r, headers){ - for(var key in headers){ - if(headers.hasOwnProperty(key)){ - let value = headers[key]; - if(Array.isArray(value)){ - value.forEach(v => { - r.header(key, v); - }); - }else{ - r.header(key, value); + function setHeaders(r, headers) { + for (var key in headers) { + if (headers.hasOwnProperty(key)) { + let value = headers[key]; + if (Array.isArray(value)) { + value.forEach(v => { + r.header(key, v); + }); + } else { + r.header(key, value); + } } } - } } - function parseBody(options, body){ - if(typeof(body) == "string"){ + function parseBody(options, body) { + if (typeof (body) == "string") { body = RequestBody.create(MediaType.parse(options.contentType), body); - }else if(body instanceof RequestBody){ + } else if (body instanceof RequestBody) { return body; - }else{ + } else { body = new RequestBody({ - contentType: function(){ - return MediaType.parse(options.contentType); - }, - writeTo: body - }); + contentType: function () { + return MediaType.parse(options.contentType); + }, + writeTo: body + }); } return body; } - function wrapResponse(res){ + function wrapResponse(res) { var r = {}; r.statusCode = res.code(); r.statusMessage = res.message(); var headers = res.headers(); r.headers = {}; - for(var i = 0; i < headers.size(); i++){ + for (var i = 0; i < headers.size(); i++) { let name = headers.name(i); let value = headers.value(i); - if(r.headers.hasOwnProperty(name)){ + if (r.headers.hasOwnProperty(name)) { let origin = r.headers[name]; - if(!Array.isArray(origin)){ + if (!Array.isArray(origin)) { r.headers[name] = [origin]; } r.headers[name].push(value); - }else{ + } else { r.headers[name] = value; } } @@ -185,7 +192,7 @@ module.exports = function(runtime, scope){ var body = res.body(); r.body.string = body.string.bind(body); r.body.bytes = body.bytes.bind(body); - r.body.json = function(){ + r.body.json = function () { return JSON.parse(r.body.string()); } r.body.contentType = body.contentType(); diff --git a/autojs/src/main/assets/modules/__images__.js b/autojs/src/main/assets/modules/__images__.js index ce45ad4c..3f444eba 100644 --- a/autojs/src/main/assets/modules/__images__.js +++ b/autojs/src/main/assets/modules/__images__.js @@ -1,9 +1,10 @@ module.exports = function (runtime, scope) { + const ResultAdapter = require("result_adapter"); function images(){ } if(android.os.Build.VERSION.SDK_INT >= 21){ - util.__assignFunctions__(runtime.images, images, ['requestScreenCapture', 'captureScreen', 'read', 'copy', 'load', 'clip', 'pixel']) + util.__assignFunctions__(runtime.images, images, ['captureScreen', 'read', 'copy', 'load', 'clip', 'pixel']) } images.opencvImporter = JavaImporter( org.opencv.core.Point, @@ -55,6 +56,18 @@ module.exports = function (runtime, scope) { var colorFinder = javaImages.colorFinder; + images.requestScreenCapture = function(landscape) { + let ScreenCapturer = com.stardust.autojs.core.image.capture.ScreenCapturer; + var orientation = ScreenCapturer.ORIENTATION_AUTO; + if(landscape === true){ + orientation = ScreenCapturer.ORIENTATION_LANDSCAPE; + } + if(landscape === false){ + orientation = ScreenCapturer.ORIENTATION_PORTRAIT; + } + return ResultAdapter.promise(javaImages.requestScreenCapture(orientation)).await(); + } + images.save = function (img, path, format, quality) { format = format || "png"; quality = quality == undefined ? 100 : quality; diff --git a/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCapturer.java b/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCapturer.java index f4d60b90..ed538bb0 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCapturer.java +++ b/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCapturer.java @@ -32,7 +32,10 @@ import java.util.concurrent.atomic.AtomicReference; @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public class ScreenCapturer { - public static final int ORIENTATION_AUTO = -1; + public static final int ORIENTATION_AUTO = Configuration.ORIENTATION_UNDEFINED; + public static final int ORIENTATION_LANDSCAPE = Configuration.ORIENTATION_LANDSCAPE ; + public static final int ORIENTATION_PORTRAIT = Configuration.ORIENTATION_PORTRAIT ; + private static final String LOG_TAG = "ScreenCapturer"; private final MediaProjectionManager mProjectionManager; diff --git a/autojs/src/main/java/com/stardust/autojs/core/looper/Loopers.java b/autojs/src/main/java/com/stardust/autojs/core/looper/Loopers.java index 39a76b16..31b402f0 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/looper/Loopers.java +++ b/autojs/src/main/java/com/stardust/autojs/core/looper/Loopers.java @@ -5,12 +5,15 @@ import android.os.Looper; import android.os.MessageQueue; import android.util.Log; +import com.stardust.autojs.rhino.AutoJsContext; import com.stardust.autojs.runtime.ScriptRuntime; import com.stardust.autojs.runtime.api.Threads; import com.stardust.autojs.runtime.api.Timers; import com.stardust.autojs.runtime.exception.ScriptInterruptedException; import com.stardust.lang.ThreadCompat; +import org.mozilla.javascript.Context; + import java.util.HashSet; import java.util.concurrent.CopyOnWriteArrayList; @@ -81,6 +84,9 @@ public class Loopers implements MessageQueue.IdleHandler { if (waitWhenIdle.get() || !waitIds.get().isEmpty()) { return false; } + if (((AutoJsContext) Context.getCurrentContext()).hasPendingContinuation()) { + return false; + } CopyOnWriteArrayList handlers = looperQuitHandlers.get(); if (handlers == null) { return true; 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 09156c02..c1ef2ee4 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 @@ -5,6 +5,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.os.Build; import android.os.Looper; + import androidx.annotation.Nullable; import android.view.WindowManager; @@ -12,7 +13,9 @@ import android.view.WindowManager; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.Theme; +import com.stardust.autojs.rhino.continuation.Continuation; import com.stardust.autojs.runtime.ScriptBridges; +import com.stardust.autojs.runtime.ScriptRuntime; import com.stardust.autojs.runtime.exception.ScriptInterruptedException; import com.stardust.concurrent.VolatileDispose; import com.stardust.util.ArrayUtils; @@ -60,17 +63,22 @@ public class BlockedMaterialDialog extends MaterialDialog { private VolatileDispose mResultBox; private UiHandler mUiHandler; private Object mCallback; + private Continuation mContinuation; private ScriptBridges mScriptBridges; private boolean mNotified = false; - public Builder(Context context, UiHandler uiHandler, ScriptBridges scriptBridges, Object callback) { + public Builder(Context context, ScriptRuntime runtime, Object callback) { super(context); super.theme(Theme.LIGHT); - mUiHandler = uiHandler; - mScriptBridges = scriptBridges; + mUiHandler = runtime.uiHandler; + mScriptBridges = runtime.bridges; mCallback = callback; if (Looper.getMainLooper() != Looper.myLooper()) { mResultBox = new VolatileDispose<>(); + } else { + if (mCallback == null) { + mContinuation = runtime.createContinuation(); + } } } @@ -88,6 +96,9 @@ 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); } @@ -168,6 +179,9 @@ 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/core/util/ScriptPromiseAdapter.kt b/autojs/src/main/java/com/stardust/autojs/core/util/ScriptPromiseAdapter.kt new file mode 100644 index 00000000..3e116da5 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/util/ScriptPromiseAdapter.kt @@ -0,0 +1,49 @@ +package com.stardust.autojs.core.util + +import com.stardust.autojs.core.internal.Functions + +class ScriptPromiseAdapter { + + interface Callback { + fun call(arg: Any?) + } + + private var mResolveCallback: Callback? = null + private var mRejectCallback: Callback? = null + private var mResult: Any? = UNSET + private var mError: Any? = UNSET + + fun onResolve(callback: Callback): ScriptPromiseAdapter { + mResolveCallback = callback + mResult.let { + if (it !== UNSET) { + callback.call(it) + } + } + return this + } + + fun onReject(callback: Callback): ScriptPromiseAdapter { + mRejectCallback = callback + mError.let { + if (it !== UNSET) { + callback.call(it) + } + } + return this + } + + fun resolve(result: Any?) { + mResult = result + mResolveCallback?.call(result) + } + + fun reject(error: Any?) { + mError = error + mRejectCallback?.call(error) + } + + companion object { + private val UNSET = Object() + } +} \ No newline at end of file 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 6f1dee1b..1db6b8aa 100644 --- a/autojs/src/main/java/com/stardust/autojs/engine/LoopBasedJavaScriptEngine.java +++ b/autojs/src/main/java/com/stardust/autojs/engine/LoopBasedJavaScriptEngine.java @@ -12,6 +12,8 @@ import com.stardust.autojs.script.JavaScriptSource; import com.stardust.autojs.script.ScriptSource; import com.stardust.util.Callback; +import org.mozilla.javascript.ContinuationPending; + /** * Created by Stardust on 2017/7/28. */ @@ -44,8 +46,9 @@ public class LoopBasedJavaScriptEngine extends RhinoJavaScriptEngine { Object o = LoopBasedJavaScriptEngine.super.execute((JavaScriptSource) source); if (callback != null) callback.onResult(o); + } catch (ContinuationPending pending) { + pending.printStackTrace(); } catch (Exception e) { - Log.d("DDDDD", "execute: " + e.getMessage()); if (callback == null) { throw e; } else { diff --git a/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.java b/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.java index 2c00b680..c3456144 100644 --- a/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.java +++ b/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.java @@ -16,6 +16,7 @@ import com.stardust.pio.PFiles; 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; @@ -23,6 +24,7 @@ import org.mozilla.javascript.commonjs.module.provider.SoftCachingModuleScriptPr import java.io.File; import java.io.IOException; +import java.io.InputStreamReader; import java.io.Reader; import java.util.Collections; import java.util.Locale; @@ -39,7 +41,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine { private static final String LOG_TAG = "RhinoJavaScriptEngine"; private static final String MODULES_PATH = "modules"; - private static StringScriptSource sInitScript; + private static Script sInitScript; private static final ConcurrentHashMap sContextEngineMap = new ConcurrentHashMap<>(); private Context mContext; @@ -69,7 +71,8 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine { Reader reader = source.getNonNullScriptReader(); try { reader = preprocess(reader); - return mContext.evaluateReader(mScriptable, reader, source.toString(), 1, null); + Script script = mContext.compileReader(reader, source.toString(), 1, null); + return mContext.executeScriptWithContinuations(script, mScriptable); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -104,21 +107,19 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine { mThread = Thread.currentThread(); ScriptableObject.putProperty(mScriptable, "__engine__", this); initRequireBuilder(mContext, mScriptable); - mContext.evaluateString(mScriptable, getInitScript().getScript(), SOURCE_NAME_INIT, 1, null); + mContext.executeScriptWithContinuations(getInitScript(), mScriptable); } - private JavaScriptSource getInitScript() { - if (sInitScript == null || BuildConfig.DEBUG) - sInitScript = new StringScriptSource(readInitScript()); - return sInitScript; - } - - private String readInitScript() { - try { - return PFiles.read(mAndroidContext.getAssets().open("init.js")); - } catch (IOException e) { - throw new RuntimeException(e); + 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) { diff --git a/autojs/src/main/java/com/stardust/autojs/execution/ScriptExecuteActivity.java b/autojs/src/main/java/com/stardust/autojs/execution/ScriptExecuteActivity.java index 93d29cf1..4e5331c4 100644 --- a/autojs/src/main/java/com/stardust/autojs/execution/ScriptExecuteActivity.java +++ b/autojs/src/main/java/com/stardust/autojs/execution/ScriptExecuteActivity.java @@ -4,9 +4,11 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; + import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; + import android.util.Log; import android.view.KeyEvent; import android.view.Menu; @@ -23,6 +25,8 @@ import com.stardust.autojs.engine.ScriptEngineManager; import com.stardust.autojs.runtime.ScriptRuntime; import com.stardust.autojs.script.ScriptSource; +import org.mozilla.javascript.ContinuationPending; + /** * Created by Stardust on 2017/2/5. */ @@ -84,6 +88,8 @@ public class ScriptExecuteActivity extends AppCompatActivity { try { prepare(); doExecution(); + } catch (ContinuationPending pending) { + pending.printStackTrace(); } catch (Exception e) { onException(e); } diff --git a/autojs/src/main/java/com/stardust/autojs/rhino/AutoJsContext.kt b/autojs/src/main/java/com/stardust/autojs/rhino/AutoJsContext.kt new file mode 100644 index 00000000..0c20d0fe --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/rhino/AutoJsContext.kt @@ -0,0 +1,31 @@ +package com.stardust.autojs.rhino + +import org.mozilla.javascript.Context +import org.mozilla.javascript.ContextFactory +import org.mozilla.javascript.ContinuationPending +import org.mozilla.javascript.Scriptable + +class AutoJsContext(factory: ContextFactory?) : Context(factory) { + + private val mContinuations = HashSet() + + override fun captureContinuation(): ContinuationPending { + val continuationPending = super.captureContinuation() + mContinuations.add(continuationPending.continuation) + return continuationPending + } + + override fun resumeContinuation(continuation: Any, scope: Scriptable?, functionResult: Any?): Any { + mContinuations.remove(continuation) + return super.resumeContinuation(continuation, scope, functionResult) + } + + fun hasPendingContinuation(): Boolean { + return mContinuations.isNotEmpty() + } + + companion object { + private val EMPTY_RUNNABLE = Runnable { } + } + +} \ No newline at end of file diff --git a/autojs/src/main/java/com/stardust/autojs/rhino/InterruptibleAndroidContextFactory.java b/autojs/src/main/java/com/stardust/autojs/rhino/InterruptibleAndroidContextFactory.java index b3daa1e4..07fcc61b 100644 --- a/autojs/src/main/java/com/stardust/autojs/rhino/InterruptibleAndroidContextFactory.java +++ b/autojs/src/main/java/com/stardust/autojs/rhino/InterruptibleAndroidContextFactory.java @@ -33,7 +33,7 @@ public class InterruptibleAndroidContextFactory extends AndroidContextFactory { @Override protected Context makeContext() { - Context cx = super.makeContext(); + Context cx = new AutoJsContext(this); cx.setInstructionObserverThreshold(10000); return cx; } diff --git a/autojs/src/main/java/com/stardust/autojs/rhino/continuation/Continuation.kt b/autojs/src/main/java/com/stardust/autojs/rhino/continuation/Continuation.kt new file mode 100644 index 00000000..8520711b --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/rhino/continuation/Continuation.kt @@ -0,0 +1,59 @@ +package com.stardust.autojs.rhino.continuation + +import com.stardust.autojs.core.looper.Timer +import com.stardust.autojs.core.looper.TimerThread +import com.stardust.autojs.rhino.AutoJsContext +import com.stardust.autojs.runtime.ScriptRuntime + +import org.mozilla.javascript.Context +import org.mozilla.javascript.ContinuationPending +import org.mozilla.javascript.Scriptable + +class Continuation(val context: AutoJsContext, val scope: Scriptable, private val mTimer: Timer) { + var pending: ContinuationPending? = null + private set + private val mThread: Thread = Thread.currentThread() + + class Result(val result: Any?, val error: Any?) { + companion object { + + fun success(result: Any?): Result { + return Result(result, null) + } + + fun failure(error: Any?): Result { + return Result(null, error) + } + } + } + + fun suspend() { + if (pending != null) { + throw IllegalStateException("call suspend twice!") + } + context.captureContinuation().let { + pending = it + throw it + } + } + + fun resumeWith(result: Result) { + val continuation = pending?.continuation + ?: throw IllegalStateException("call resume() without suspend()!") + if (mThread == Thread.currentThread()) { + context.resumeContinuation(continuation, scope, result) + } else { + mTimer.postDelayed({ + context.resumeContinuation(continuation, scope, result) + }, 0) + } + } + + companion object { + + fun create(runtime: ScriptRuntime, scope: Scriptable): Continuation { + val context = Context.getCurrentContext() as AutoJsContext + return Continuation(context, scope, runtime.timers.timerForCurrentThread) + } + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptBridges.java b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptBridges.java index feca662d..c567d8b0 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptBridges.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptBridges.java @@ -7,7 +7,6 @@ package com.stardust.autojs.runtime; public class ScriptBridges { - public interface Bridges { Object[] NO_ARGUMENTS = new Object[0]; diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java index 5a79e827..93a7b3c9 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java @@ -1,7 +1,5 @@ package com.stardust.autojs.runtime; -import android.Manifest; -import android.app.Activity; import android.content.Context; import android.os.Build; import android.os.Looper; @@ -13,10 +11,10 @@ import com.stardust.autojs.ScriptEngineService; import com.stardust.autojs.annotation.ScriptVariable; import com.stardust.autojs.core.accessibility.AccessibilityBridge; import com.stardust.autojs.core.image.Colors; -import com.stardust.autojs.core.permission.PermissionRequestProxyActivity; import com.stardust.autojs.core.permission.Permissions; import com.stardust.autojs.rhino.AndroidClassLoader; import com.stardust.autojs.rhino.TopLevelScope; +import com.stardust.autojs.rhino.continuation.Continuation; import com.stardust.autojs.runtime.api.AbstractShell; import com.stardust.autojs.runtime.api.AppUtils; import com.stardust.autojs.runtime.api.Console; @@ -54,6 +52,7 @@ import com.stardust.view.accessibility.AccessibilityInfoProvider; import org.mozilla.javascript.ContextFactory; import org.mozilla.javascript.RhinoException; import org.mozilla.javascript.ScriptStackElement; +import org.mozilla.javascript.Scriptable; import java.io.BufferedReader; import java.io.File; @@ -62,13 +61,9 @@ import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import static android.content.pm.PackageManager.PERMISSION_DENIED; - /** * Created by Stardust on 2017/1/27. @@ -407,8 +402,8 @@ public class ScriptRuntime { ignoresException(floaty::closeAll); try { events.emit("exit"); - } catch (Throwable ignored) { - console.error("exception on exit: ", ignored); + } catch (Throwable e) { + console.error("exception on exit: ", e); } ignoresException(threads::shutDownAll); ignoresException(events::recycle); @@ -451,6 +446,15 @@ public class ScriptRuntime { return mProperties.remove(key); } + public Continuation createContinuation() { + return Continuation.Companion.create(this, mTopLevelScope); + } + + public Continuation createContinuation(Scriptable scope) { + return Continuation.Companion.create(this, scope); + } + + public static String getStackTrace(Throwable e, boolean printJavaStackTrace) { String message = e.getMessage(); StringBuilder scriptTrace = new StringBuilder(message == null ? "" : message + "\n"); 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 a6ac2813..b5caf674 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 @@ -132,7 +132,7 @@ public class Dialogs { if (context == null || ((Activity) context).isFinishing()) { context = getContext(); } - return (BlockedMaterialDialog.Builder) new BlockedMaterialDialog.Builder(context, mRuntime.uiHandler, mRuntime.bridges, callback) + return (BlockedMaterialDialog.Builder) new BlockedMaterialDialog.Builder(context, mRuntime, callback) .theme(Theme.LIGHT); } diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/Images.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/Images.java index 672d303b..711a6038 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/Images.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/Images.java @@ -2,7 +2,6 @@ package com.stardust.autojs.runtime.api; import android.app.Activity; import android.content.Context; -import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; @@ -12,7 +11,9 @@ import android.media.Image; import android.os.Build; import android.os.Handler; import android.os.Looper; + import androidx.annotation.RequiresApi; + import android.util.Base64; import android.view.Gravity; @@ -25,8 +26,8 @@ import com.stardust.autojs.core.image.capture.ScreenCapturer; import com.stardust.autojs.core.opencv.Mat; import com.stardust.autojs.core.opencv.OpenCVHelper; import com.stardust.autojs.core.ui.inflater.util.Drawables; +import com.stardust.autojs.core.util.ScriptPromiseAdapter; import com.stardust.autojs.runtime.ScriptRuntime; -import com.stardust.autojs.runtime.exception.ScriptInterruptedException; import com.stardust.concurrent.VolatileDispose; import com.stardust.pio.UncheckedIOException; import com.stardust.util.ScreenMetrics; @@ -70,34 +71,26 @@ public class Images { } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public boolean requestScreenCapture(int orientation) { + public ScriptPromiseAdapter requestScreenCapture(int orientation) { ScriptRuntime.requiresApi(21); + ScriptPromiseAdapter promiseAdapter = new ScriptPromiseAdapter(); if (mScreenCapturer != null) { mScreenCapturer.setOrientation(orientation); - return true; + promiseAdapter.resolve(true); + return promiseAdapter; } - final VolatileDispose requestResult = new VolatileDispose<>(); + Looper servantLooper = mScriptRuntime.loopers.getServantLooper(); mScreenCaptureRequester.setOnActivityResultCallback((result, data) -> { if (result == Activity.RESULT_OK) { mScreenCapturer = new ScreenCapturer(mContext, data, orientation, ScreenMetrics.getDeviceScreenDensity(), - new Handler(mScriptRuntime.loopers.getServantLooper())); - requestResult.setAndNotify(true); + new Handler(servantLooper)); + promiseAdapter.resolve(true); } else { - requestResult.setAndNotify(false); + promiseAdapter.resolve(false); } }); mScreenCaptureRequester.request(); - return requestResult.blockedGetOrThrow(ScriptInterruptedException.class); - } - - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public boolean requestScreenCapture(boolean landscape) { - return requestScreenCapture(landscape ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT); - } - - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public boolean requestScreenCapture() { - return requestScreenCapture(ScreenCapturer.ORIENTATION_AUTO); + return promiseAdapter; } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/Timers.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/Timers.java index 7c67e38f..233fafad 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/Timers.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/Timers.java @@ -81,14 +81,14 @@ public class Timers { public boolean hasPendingCallbacks() { //如果是脚本主线程,则检查所有子线程中的定时回调。mFutureCallbackUptimeMillis用来记录所有子线程中定时最久的一个。 - // if (mThreads.getMainThread() == Thread.currentThread()) { - // return mMaxCallbackUptimeMillisForAllThreads.get() > SystemClock.uptimeMillis(); - //} - //否则检查当前线程的定时回调 + if (mThreads.getMainThread() == Thread.currentThread()) { + return mMaxCallbackUptimeMillisForAllThreads.get() > SystemClock.uptimeMillis(); + } + // 否则检查当前线程的定时回调 return getTimerForCurrentThread().hasPendingCallbacks(); } - public void recycle(){ + public void recycle() { mMainTimer.removeAllCallbacks(); }