());
private final RuntimeReplFactory mReplFactory;
/**
* @see #Runtime(RuntimeReplFactory)
* @deprecated Provided for ABI compatibility
*/
@Deprecated
public Runtime() {
this(new RuntimeReplFactory() {
@Override
public RuntimeRepl newInstance() {
return new RuntimeRepl() {
@Override
public Object evaluate(String expression) throws Throwable {
return "Not supported with legacy Runtime module";
}
};
}
});
}
public Runtime(RuntimeReplFactory replFactory) {
mReplFactory = replFactory;
}
public static int mapObject(JsonRpcPeer peer, Object object) {
return getSession(peer).getObjects().putObject(object);
}
@Nonnull
private static synchronized Session getSession(final JsonRpcPeer peer) {
Session session = sSessions.get(peer);
if (session == null) {
session = new Session();
sSessions.put(peer, session);
peer.registerDisconnectReceiver(new DisconnectReceiver() {
@Override
public void onDisconnect() {
sSessions.remove(peer);
}
});
}
return session;
}
@ChromeDevtoolsMethod
public void releaseObject(JsonRpcPeer peer, JSONObject params) throws JSONException {
String objectId = params.getString("objectId");
getSession(peer).getObjects().removeObjectById(Integer.parseInt(objectId));
}
@ChromeDevtoolsMethod
public void releaseObjectGroup(JsonRpcPeer peer, JSONObject params) {
LogUtil.w("Ignoring request to releaseObjectGroup: " + params);
}
@ChromeDevtoolsMethod
public CallFunctionOnResponse callFunctionOn(JsonRpcPeer peer, JSONObject params)
throws JsonRpcException {
CallFunctionOnRequest args = mObjectMapper.convertValue(params, CallFunctionOnRequest.class);
Session session = getSession(peer);
Object object = session.getObjectOrThrow(args.objectId);
// The DevTools UI thinks it can run arbitrary JavaScript against us in order to figure out
// the class structure of an object. That obviously won't fly, and there's no way to
// translate without building a crude JavaScript parser so let's just go ahead and guess
// what this function does by name.
if (!args.functionDeclaration.startsWith("function protoList(")) {
throw new JsonRpcException(
new JsonRpcError(
JsonRpcError.ErrorCode.INTERNAL_ERROR,
"Expected protoList, got: " + args.functionDeclaration,
null /* data */));
}
// Since this is really a function call we have to create this fake object to hold the
// "result" of the function.
ObjectProtoContainer objectContainer = new ObjectProtoContainer(object);
RemoteObject result = new RemoteObject();
result.type = ObjectType.OBJECT;
result.subtype = ObjectSubType.NODE;
result.className = object.getClass().getName();
result.description = getPropertyClassName(object);
result.objectId = String.valueOf(session.getObjects().putObject(objectContainer));
CallFunctionOnResponse response = new CallFunctionOnResponse();
response.result = result;
response.wasThrown = false;
return response;
}
@ChromeDevtoolsMethod
public JsonRpcResult evaluate(JsonRpcPeer peer, JSONObject params) {
return getSession(peer).evaluate(mReplFactory, params);
}
@ChromeDevtoolsMethod
public JsonRpcResult getProperties(JsonRpcPeer peer, JSONObject params) throws JsonRpcException {
return getSession(peer).getProperties(params);
}
private static String getPropertyClassName(Object o) {
String name = o.getClass().getSimpleName();
if (name == null || name.length() == 0) {
// Looks better for anonymous classes.
name = o.getClass().getName();
}
return name;
}
private static class ObjectProtoContainer {
public final Object object;
public ObjectProtoContainer(Object object) {
this.object = object;
}
}
/**
* Object representing a session with a single client.
*
* Clients inherently leak object references because they can expand any object in the UI
* at any time. Grouping references by client allows us to drop them when the client
* disconnects.
*/
private static class Session {
private final ObjectIdMapper mObjects = new ObjectIdMapper();
private final ObjectMapper mObjectMapper = new ObjectMapper();
@Nullable
private RuntimeRepl mRepl;
public ObjectIdMapper getObjects() {
return mObjects;
}
public Object getObjectOrThrow(String objectId) throws JsonRpcException {
Object object = getObjects().getObjectForId(Integer.parseInt(objectId));
if (object == null) {
throw new JsonRpcException(new JsonRpcError(
JsonRpcError.ErrorCode.INVALID_REQUEST,
"No object found for " + objectId,
null /* data */));
}
return object;
}
public RemoteObject objectForRemote(Object value) {
RemoteObject result = new RemoteObject();
if (value == null) {
result.type = ObjectType.OBJECT;
result.subtype = ObjectSubType.NULL;
result.value = JSONObject.NULL;
} else if (value instanceof Boolean) {
result.type = ObjectType.BOOLEAN;
result.value = value;
} else if (value instanceof Number) {
result.type = ObjectType.NUMBER;
result.value = value;
} else if (value instanceof Character) {
// Unclear whether we should expose these as strings, numbers, or something else.
result.type = ObjectType.NUMBER;
result.value = Integer.valueOf(((Character) value).charValue());
} else if (value instanceof String) {
result.type = ObjectType.STRING;
result.value = String.valueOf(value);
} else {
result.type = ObjectType.OBJECT;
result.className = "What??"; // I have no idea where this is used.
result.objectId = String.valueOf(mObjects.putObject(value));
if (value.getClass().isArray()) {
result.description = "array";
} else if (value instanceof List) {
result.description = "List";
} else if (value instanceof Set) {
result.description = "Set";
} else if (value instanceof Map) {
result.description = "Map";
} else {
result.description = getPropertyClassName(value);
}
}
return result;
}
public EvaluateResponse evaluate(RuntimeReplFactory replFactory, JSONObject params) {
EvaluateRequest request = mObjectMapper.convertValue(params, EvaluateRequest.class);
try {
if (!request.objectGroup.equals("console")) {
return buildExceptionResponse("Not supported by FAB");
}
RuntimeRepl repl = getRepl(replFactory);
Object result = repl.evaluate(request.expression);
if (result == null) {
return null;
}
return buildNormalResponse(result);
} catch (Throwable t) {
return buildExceptionResponse(t);
}
}
@Nonnull
private synchronized RuntimeRepl getRepl(RuntimeReplFactory replFactory) {
if (mRepl == null) {
mRepl = replFactory.newInstance();
}
return mRepl;
}
private EvaluateResponse buildNormalResponse(Object retval) {
EvaluateResponse response = new EvaluateResponse();
response.wasThrown = false;
response.result = objectForRemote(retval);
return response;
}
private EvaluateResponse buildExceptionResponse(Object retval) {
EvaluateResponse response = new EvaluateResponse();
response.wasThrown = true;
response.result = objectForRemote(retval);
response.exceptionDetails = new ExceptionDetails();
response.exceptionDetails.text = retval.toString();
return response;
}
public GetPropertiesResponse getProperties(JSONObject params) throws JsonRpcException {
GetPropertiesRequest request = mObjectMapper.convertValue(params, GetPropertiesRequest.class);
if (!request.ownProperties) {
GetPropertiesResponse response = new GetPropertiesResponse();
response.result = new ArrayList<>();
return response;
}
Object object = getObjectOrThrow(request.objectId);
if (object.getClass().isArray()) {
object = arrayToList(object);
}
if (object instanceof ObjectProtoContainer) {
return getPropertiesForProtoContainer((ObjectProtoContainer) object);
} else if (object instanceof List) {
return getPropertiesForIterable((List) object, /* enumerate */ true);
} else if (object instanceof Set) {
return getPropertiesForIterable((Set) object, /* enumerate */ false);
} else if (object instanceof Map) {
return getPropertiesForMap(object);
} else {
return getPropertiesForObject(object);
}
}
private List> arrayToList(Object object) {
Class> type = object.getClass();
if (!type.isArray()) {
throw new IllegalArgumentException("Argument must be an array. Was " + type);
}
Class> component = type.getComponentType();
if (!component.isPrimitive()) {
return Arrays.asList((Object[]) object);
}
// Loop manually for primitives.
int length = Array.getLength(object);
List