新增 特性continuation
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<LooperQuitHandler> handlers = looperQuitHandlers.get();
|
||||
if (handlers == null) {
|
||||
return true;
|
||||
|
||||
@@ -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<Object> 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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Context, RhinoJavaScriptEngine> 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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<Any>()
|
||||
|
||||
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 { }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ package com.stardust.autojs.runtime;
|
||||
public class ScriptBridges {
|
||||
|
||||
|
||||
|
||||
public interface Bridges {
|
||||
|
||||
Object[] NO_ARGUMENTS = new Object[0];
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Boolean> 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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user