package com.stardust.autojs.engine; import android.util.Log; import com.stardust.autojs.rhino.AndroidContextFactory; import com.stardust.autojs.rhino.RhinoAndroidHelper; import com.stardust.autojs.runtime.ScriptInterruptedException; import com.stardust.autojs.script.ScriptSource; import com.stardust.pio.UncheckedIOException; import org.mozilla.javascript.Context; import org.mozilla.javascript.ContextFactory; import org.mozilla.javascript.ImporterTopLevel; 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.Reader; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Created by Stardust on 2017/4/2. */ public class RhinoJavaScriptEngine implements ScriptEngine { private static final String LOG_TAG = "RhinoJavaScriptEngine"; private static int contextCount = 0; private String[] mRequirePath = new String[0]; private Context mContext; private Scriptable mScriptable; private Thread mThread; private RhinoJavaScriptEngineManager mEngineManager; private Map<String, Object> mTags = new ConcurrentHashMap<>(); public RhinoJavaScriptEngine(RhinoJavaScriptEngineManager engineManager) { mEngineManager = engineManager; mThread = Thread.currentThread(); mContext = createContext(); mScriptable = createScope(mContext); } @Override public void put(String name, Object value) { ScriptableObject.putProperty(mScriptable, name, Context.javaToJS(value, mScriptable)); } @Override public Object execute(ScriptSource source) { Reader reader = source.getNonNullScriptReader(); try { reader = preprocess(reader); return mContext.evaluateReader(mScriptable, reader, "<" + source.getName() + ">", 1, null); } 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(); } public RhinoJavaScriptEngineManager getEngineManager() { return mEngineManager; } @Override public synchronized void destroy() { Log.d(LOG_TAG, "on destroy"); Context.exit(); contextCount--; Log.d(LOG_TAG, "contextCount = " + contextCount); mEngineManager.removeEngine(this); } @Override public synchronized void setTag(String key, Object value) { mTags.put(key, value); } @Override public synchronized Object getTag(String key) { return mTags.get(key); } @Override public void init() { ScriptableObject.putProperty(mScriptable, "__engine_name__", "rhino"); ScriptableObject.putProperty(mScriptable, "__engine__", this); mRequirePath = (String[]) getTag("__require_path__"); initRequireBuilder(mContext, mScriptable); mContext.evaluateString(mScriptable, mEngineManager.getInitScript().getScript(), "<init>", 1, null); } public void setRequirePath(String... requirePath) { setTag("__require_path__", requirePath); } void initRequireBuilder(Context context, Scriptable scope) { List<URI> list = new ArrayList<>(); for (String path : mRequirePath) { list.add(new File(path).toURI()); } AssetAndUrlModuleSourceProvider provider = new AssetAndUrlModuleSourceProvider(getEngineManager().getContext(), list); new RequireBuilder() .setModuleScriptProvider(new SoftCachingModuleScriptProvider(provider)) .setSandboxed(false) .createRequire(context, scope) .install(scope); } public Context getContext() { return mContext; } public Scriptable getScriptable() { return mScriptable; } protected Scriptable createScope(Context context) { ImporterTopLevel importerTopLevel = new ImporterTopLevel(); importerTopLevel.initStandardObjects(context, false); return importerTopLevel; } protected Context createContext() { if (!ContextFactory.hasExplicitGlobal()) { ContextFactory.initGlobal(new InterruptibleAndroidContextFactory(new File(mEngineManager.getContext().getCacheDir(), "classes"))); } Context context = new RhinoAndroidHelper(mEngineManager.getContext()).enterContext(); contextCount++; context.setOptimizationLevel(-1); context.setLanguageVersion(Context.VERSION_ES6); return context; } private static class InterruptibleAndroidContextFactory extends AndroidContextFactory { public InterruptibleAndroidContextFactory(File cacheDirectory) { super(cacheDirectory); } @Override protected void observeInstructionCount(Context cx, int instructionCount) { if (Thread.currentThread().isInterrupted()) { throw new ScriptInterruptedException(); } } @Override protected Context makeContext() { Context cx = super.makeContext(); cx.setInstructionObserverThreshold(10000); return cx; } } }