/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.stetho.rhino;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.facebook.stetho.inspector.console.RuntimeRepl;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ScriptableObject;
class JsRuntimeRepl implements RuntimeRepl {
private final @NonNull ScriptableObject mJsScope;
JsRuntimeRepl(@NonNull ScriptableObject scope) {
mJsScope = scope;
}
@Override
public @Nullable Object evaluate(@NonNull String expression) throws Throwable {
Object result;
final Context jsContext = enterJsContext();
try {
result = jsContext.evaluateString(mJsScope, expression, "chrome", 1, null);
// Google chrome automatically saves the last expression to `$_`, we do the same
Object jsValue = Context.javaToJS(result, mJsScope);
ScriptableObject.putProperty(mJsScope, "$_", jsValue);
} finally {
Context.exit();
}
return Context.jsToJava(result, Object.class);
}
/**
* Setups a proper javascript context so that it can run javascript code properly under android.
* For android we need to disable bytecode generation since the android vms don't understand JVM bytecode.
* @return a proper javascript context
*/
static @NonNull Context enterJsContext() {
final Context jsContext = Context.enter();
// If we cause the context to throw a runtime exception from this point
// we need to make sure that exit the context.
try {
jsContext.setLanguageVersion(Context.VERSION_1_8);
// We can't let Rhino to optimize the JS and to use a JIT because it would generate JVM bytecode
// and android runs on DEX bytecode. Instead we need to go in interpreted mode.
jsContext.setOptimizationLevel(-1);
} catch (RuntimeException e) {
// Something bad happened to the javascript context but it might still be usable.
// The first thing to do is to exit the context and then propagate the error.
Context.exit();
throw e;
}
return jsContext;
}
}