/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.debug.core.webkit;
import com.google.dart.tools.debug.core.webkit.WebkitConnection.Callback;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.List;
// TODO(devoncarew): Add support for the Runtime.getProperty call.
/*
* { "name": "getProperty", "parameters": [ { "name": "objectId", "$ref": "RemoteObjectId",
* "description": "Identifier of the object to return a property for." }, { "name": "propertyPath",
* "type": "array", "items": { "type": "string" } } ], "returns": [ { "name": "result", "$ref":
* "RemoteObject", "description": "Call result." }, { "name": "wasThrown", "type": "boolean",
* "optional": true, "description": "True if the result was thrown during the evaluation." } ],
* "description": "Returns a property of a given object. Object group of the result "
* "is inherited from the target object." },
*/
/**
* A WIP runtime domain object.
* <p>
* Runtime domain exposes JavaScript runtime by means of remote evaluation and mirror objects.
* Evaluation results are returned as mirror object that expose object type, string representation
* and unique identifier that can be used for further object reference. Original objects are
* maintained in memory unless they are either explicitly released or are released along with the
* other objects in their object group.
*
* @see http://code.google.com/chrome/devtools/docs/protocol/tot/runtime.html
*/
public class WebkitRuntime extends WebkitDomain {
public static class CallArgument {
public static CallArgument fromDouble(double d) {
CallArgument arg = new CallArgument();
arg.value = new Double(d);
return arg;
}
public static CallArgument fromInt(int i) {
CallArgument arg = new CallArgument();
arg.value = new Integer(i);
return arg;
}
public static CallArgument fromObjectId(String objectId) {
CallArgument arg = new CallArgument();
arg.objectId = objectId;
return arg;
}
public static CallArgument fromString(String str) {
CallArgument arg = new CallArgument();
arg.value = str;
return arg;
}
private Object value;
private String objectId;
public JSONObject toJson() throws JSONException {
JSONObject obj = new JSONObject();
if (objectId != null) {
obj.put("objectId", objectId);
} else {
obj.put("value", value);
}
return obj;
}
}
public WebkitRuntime(WebkitConnection connection) {
super(connection);
}
/**
* Calls function with given declaration on the given object. Object group of the result is
* inherited from the target object.
*
* @param objectId Identifier of the object to call function on.
* @param functionDeclaration Declaration of the function to call.
* @param arguments Call arguments. All call arguments must belong to the same JavaScript world as
* the target object.
* @param returnByValue Whether the result is expected to be a JSON object which should be sent by
* value.
* @param callback
* @throws IOException
*/
public void callFunctionOn(String objectId, String functionDeclaration,
List<CallArgument> arguments, boolean returnByValue,
final WebkitCallback<WebkitRemoteObject> callback) throws IOException {
// TODO: optional
// boolean doNotPauseOnExceptionsAndMuteConsole
// Specifies whether evaluation should stop on exceptions and mute console. Overrides setPauseOnException state.
try {
JSONObject request = new JSONObject();
JSONObject params = new JSONObject();
params.put("objectId", objectId);
params.put("functionDeclaration", functionDeclaration);
params.put("returnByValue", returnByValue);
if (arguments != null) {
params.put("arguments", argsToArray(arguments));
}
request.put("method", "Runtime.callFunctionOn").put("params", params);
connection.sendRequest(request, new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
callback.handleResult(convertEvaluateResult(result));
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
public void callListLength(String objectId, final WebkitCallback<Integer> callback)
throws IOException {
if (objectId == null) {
WebkitResult<Integer> result = new WebkitResult<Integer>();
result.setResult(new Integer(0));
callback.handleResult(result);
return;
}
try {
JSONObject request = new JSONObject();
request.put("method", "Runtime.callFunctionOn");
request.put(
"params",
new JSONObject().put("objectId", objectId).put("functionDeclaration", "() => length").put(
"returnByValue",
false));
connection.sendRequest(request, new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
WebkitResult<WebkitRemoteObject> functionResult = convertEvaluateResult(result);
WebkitResult<Integer> r = new WebkitResult<Integer>();
if (functionResult.getWasThrown()) {
r.setError(functionResult.getResult().getValue());
} else {
r.setResult(functionResult.getResult() == null ? new Integer(0) : new Integer(
functionResult.getResult().getValue()));
}
callback.handleResult(r);
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
/**
* Calls the toString() method on the given remote object. This is a convenience method for the
* Runtime.callFunctionOn call.
*
* @param objectId
* @throws IOException
*/
public void callToString(String objectId, final WebkitCallback<String> callback)
throws IOException {
if (objectId == null) {
WebkitResult<String> result = new WebkitResult<String>();
result.setResult(null);
callback.handleResult(result);
return;
}
try {
JSONObject request = new JSONObject();
request.put("method", "Runtime.callFunctionOn");
request.put(
"params",
new JSONObject().put("objectId", objectId).put("functionDeclaration", "() => toString()").put(
"returnByValue",
false));
connection.sendRequest(request, new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
WebkitResult<WebkitRemoteObject> functionResult = convertEvaluateResult(result);
WebkitResult<String> r = new WebkitResult<String>();
if (functionResult.getWasThrown()) {
r.setError(functionResult.getResult().getValue());
} else {
r.setResult(functionResult.getResult() == null ? "null"
: functionResult.getResult().getValue());
}
callback.handleResult(r);
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
/**
* Evaluates expression on global object.
*
* @param expression Expression to evaluate.
* @param objectGroup (optional) Symbolic group name that can be used to release multiple objects.
* @param returnByValue (optional) Whether the result is expected to be a JSON object that should
* be sent by value.
* @throws IOException
*/
public void evaluate(String expression, String objectGroup, boolean returnByValue,
final WebkitCallback<WebkitRemoteObject> callback) throws IOException {
// TODO: optional
// boolean doNotPauseOnExceptionsAndMuteConsole
// Specifies whether evaluation should stop on exceptions and mute console. Overrides setPauseOnException state.
try {
JSONObject request = new JSONObject();
JSONObject params = new JSONObject();
params.put("expression", expression);
params.put("returnByValue", returnByValue);
if (objectGroup != null) {
params.put("objectGroup", objectGroup);
}
request.put("method", "Runtime.evaluate").put("params", params);
connection.sendRequest(request, new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
callback.handleResult(convertEvaluateResult(result));
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
/**
* Returns properties of a given object. Object group of the result is inherited from the target
* object.
* <p>
* If successful, the WebkitResult object will contain an array of property descriptors.
*
* @param object identifier of the object to return properties for
* @param ownProperties if true, returns properties belonging only to the element itself, not to
* its prototype chain
* @param accessorPropertiesOnly if true, returns accessor properties (with getter/setter) only;
* internal properties are not returned either
* @param callback
* @throws IOException
*/
public void getProperties(final WebkitRemoteObject object, boolean ownProperties,
boolean accessorPropertiesOnly, final WebkitCallback<WebkitPropertyDescriptor[]> callback)
throws IOException {
if (callback == null) {
throw new IllegalArgumentException("callback is required");
}
try {
JSONObject params = new JSONObject();
params.put("objectId", object.getObjectId());
params.put("ownProperties", ownProperties);
params.put("accessorPropertiesOnly", accessorPropertiesOnly);
JSONObject request = new JSONObject();
request.put("method", "Runtime.getProperties");
request.put("params", params);
connection.sendRequest(request, new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
callback.handleResult(convertGetPropertiesResult(object, result));
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
/**
* Releases remote object with given id.
*
* @param objectId
* @throws IOException
*/
public void releaseObject(String objectId) throws IOException {
try {
JSONObject request = new JSONObject();
request.put("method", "Runtime.releaseObject");
request.put("params", new JSONObject().put("objectId", objectId));
connection.sendRequest(request);
} catch (JSONException exception) {
throw new IOException(exception);
}
}
/**
* Releases all remote objects that belong to a given group.
*
* @param objectGroup
* @throws IOException
*/
public void releaseObjectGroup(String objectGroup) throws IOException {
try {
JSONObject request = new JSONObject();
request.put("method", "Runtime.releaseObjectGroup");
request.put("params", new JSONObject().put("objectGroup", objectGroup));
connection.sendRequest(request);
} catch (JSONException exception) {
throw new IOException(exception);
}
}
/**
* Tells inspected instance (worker or page) that it can run in case it was started paused.
*
* @throws IOException
*/
@WebkitUnsupported
public void run() throws IOException {
sendSimpleCommand("Runtime.run");
}
protected WebkitResult<WebkitRemoteObject> convertEvaluateResult(JSONObject object)
throws JSONException {
WebkitResult<WebkitRemoteObject> result = WebkitResult.createFrom(object);
// "result": {
// "result": <RemoteObject>,
// "wasThrown": <boolean>
// }
if (object.has("result")) {
JSONObject obj = object.getJSONObject("result");
WebkitRemoteObject remoteObject = WebkitRemoteObject.createFrom(obj.getJSONObject("result"));
if (JsonUtils.getBoolean(obj, "wasThrown")) {
result.setWasThrown(true);
result.setResult(remoteObject);
} else {
result.setResult(remoteObject);
}
}
return result;
}
private JSONArray argsToArray(List<CallArgument> arguments) throws JSONException {
JSONArray arr = new JSONArray();
for (CallArgument arg : arguments) {
arr.put(arg.toJson());
}
return arr;
}
private WebkitResult<WebkitPropertyDescriptor[]> convertGetPropertiesResult(
WebkitRemoteObject parentObject, JSONObject object) throws JSONException {
WebkitResult<WebkitPropertyDescriptor[]> result = WebkitResult.createFrom(object);
if (object.has("result")) {
JSONObject obj = object.getJSONObject("result");
result.setResult(WebkitPropertyDescriptor.createFrom(obj.getJSONArray("result")));
}
return result;
}
}