2
|
1 |
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
|
2 |
// Use of this source code is governed by a BSD-style license that can be
|
|
3 |
// found in the LICENSE file.
|
|
4 |
|
|
5 |
package org.chromium.sdk.internal.tools.v8;
|
|
6 |
|
|
7 |
import java.util.ArrayList;
|
|
8 |
import java.util.Collections;
|
|
9 |
import java.util.List;
|
|
10 |
import java.util.concurrent.Semaphore;
|
|
11 |
import java.util.concurrent.TimeUnit;
|
|
12 |
|
|
13 |
import org.chromium.sdk.CallbackSemaphore;
|
|
14 |
import org.chromium.sdk.SyncCallback;
|
|
15 |
import org.chromium.sdk.JsValue.Type;
|
|
16 |
import org.chromium.sdk.internal.DataWithRef;
|
|
17 |
import org.chromium.sdk.internal.DebugSession;
|
|
18 |
import org.chromium.sdk.internal.FunctionAdditionalProperties;
|
|
19 |
import org.chromium.sdk.internal.JsDataTypeUtil;
|
|
20 |
import org.chromium.sdk.internal.PropertyHoldingValueMirror;
|
|
21 |
import org.chromium.sdk.internal.PropertyReference;
|
|
22 |
import org.chromium.sdk.internal.ScopeMirror;
|
|
23 |
import org.chromium.sdk.internal.ScriptManager;
|
|
24 |
import org.chromium.sdk.internal.SubpropertiesMirror;
|
|
25 |
import org.chromium.sdk.internal.ValueLoadException;
|
|
26 |
import org.chromium.sdk.internal.ValueMirror;
|
|
27 |
import org.chromium.sdk.internal.protocol.CommandResponse;
|
|
28 |
import org.chromium.sdk.internal.protocol.FrameObject;
|
|
29 |
import org.chromium.sdk.internal.protocol.ScopeRef;
|
|
30 |
import org.chromium.sdk.internal.protocol.SuccessCommandResponse;
|
|
31 |
import org.chromium.sdk.internal.protocol.data.FunctionValueHandle;
|
|
32 |
import org.chromium.sdk.internal.protocol.data.ObjectValueHandle;
|
|
33 |
import org.chromium.sdk.internal.protocol.data.PropertyObject;
|
|
34 |
import org.chromium.sdk.internal.protocol.data.RefWithDisplayData;
|
|
35 |
import org.chromium.sdk.internal.protocol.data.ScriptHandle;
|
|
36 |
import org.chromium.sdk.internal.protocol.data.ValueHandle;
|
|
37 |
import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException;
|
|
38 |
import org.chromium.sdk.internal.tools.v8.request.DebuggerMessageFactory;
|
|
39 |
import org.chromium.sdk.internal.tools.v8.request.ScriptsMessage;
|
|
40 |
|
|
41 |
/**
|
|
42 |
* A helper class for performing complex V8-related operations.
|
|
43 |
*/
|
|
44 |
public class V8Helper {
|
|
45 |
|
|
46 |
/**
|
|
47 |
* The debug context in which the operations are performed.
|
|
48 |
*/
|
|
49 |
private final DebugSession debugSession;
|
|
50 |
|
|
51 |
/**
|
|
52 |
* A semaphore that prevents concurrent script reloading (which may effectively
|
|
53 |
* double the efforts.)
|
|
54 |
*/
|
|
55 |
private final Semaphore scriptsReloadSemaphore = new Semaphore(1);
|
|
56 |
|
|
57 |
public V8Helper(DebugSession debugSession) {
|
|
58 |
this.debugSession = debugSession;
|
|
59 |
}
|
|
60 |
|
|
61 |
/**
|
|
62 |
* Reloads all normal scripts found in the page. First, all scripts without
|
|
63 |
* their sources are retrieved to save bandwidth (script list change during a
|
|
64 |
* page lifetime is a relatively rare event.) If at least one script has been
|
|
65 |
* added, the script cache is dropped and re-populated with new scripts that
|
|
66 |
* are re-requested together with their sources.
|
|
67 |
*
|
|
68 |
* @param callback to invoke when the script reloading has completed
|
|
69 |
*/
|
|
70 |
public void reloadAllScriptsAsync(V8CommandProcessor.V8HandlerCallback callback,
|
|
71 |
SyncCallback syncCallback) {
|
|
72 |
final V8CommandProcessor.V8HandlerCallback finalCallback = callback != null
|
|
73 |
? callback
|
|
74 |
: V8CommandProcessor.V8HandlerCallback.NULL_CALLBACK;
|
|
75 |
lock();
|
|
76 |
debugSession.sendMessageAsync(
|
|
77 |
DebuggerMessageFactory.scripts(ScriptsMessage.SCRIPTS_NORMAL, true),
|
|
78 |
true,
|
|
79 |
new V8CommandProcessor.V8HandlerCallback() {
|
|
80 |
public void failure(String message) {
|
|
81 |
unlock();
|
|
82 |
finalCallback.failure(message);
|
|
83 |
}
|
|
84 |
|
|
85 |
public void messageReceived(CommandResponse response) {
|
|
86 |
SuccessCommandResponse successResponse = response.asSuccess();
|
|
87 |
|
|
88 |
// TODO(peter.rybin): add try/finally for unlock, with some error reporting probably.
|
|
89 |
List<ScriptHandle> body;
|
|
90 |
try {
|
|
91 |
body = successResponse.getBody().asScripts();
|
|
92 |
} catch (JsonProtocolParseException e) {
|
|
93 |
throw new RuntimeException(e);
|
|
94 |
}
|
|
95 |
ScriptManager scriptManager = debugSession.getScriptManager();
|
|
96 |
for (int i = 0; i < body.size(); ++i) {
|
|
97 |
ScriptHandle scriptHandle = body.get(i);
|
|
98 |
Long id = V8ProtocolUtil.getScriptIdFromResponse(scriptHandle);
|
|
99 |
if (scriptManager.findById(id) == null &&
|
|
100 |
!ChromeDevToolSessionManager.JAVASCRIPT_VOID.equals(scriptHandle.source())) {
|
|
101 |
scriptManager.addScript(
|
|
102 |
scriptHandle,
|
|
103 |
successResponse.getRefs());
|
|
104 |
}
|
|
105 |
}
|
|
106 |
unlock();
|
|
107 |
finalCallback.messageReceived(response);
|
|
108 |
}
|
|
109 |
},
|
|
110 |
syncCallback);
|
|
111 |
}
|
|
112 |
|
|
113 |
protected void lock() {
|
|
114 |
try {
|
|
115 |
scriptsReloadSemaphore.acquire();
|
|
116 |
} catch (InterruptedException e) {
|
|
117 |
// consider it a successful acquisition
|
|
118 |
}
|
|
119 |
}
|
|
120 |
|
|
121 |
protected void unlock() {
|
|
122 |
scriptsReloadSemaphore.release();
|
|
123 |
}
|
|
124 |
|
|
125 |
/**
|
|
126 |
* Gets all resolved locals for the call frame, caches scripts and objects in
|
|
127 |
* the scriptManager and handleManager.
|
|
128 |
*
|
|
129 |
* @param frame to get the data for
|
|
130 |
* @return the mirrors corresponding to the frame locals
|
|
131 |
*/
|
|
132 |
public static List<PropertyReference> computeLocals(FrameObject frame) {
|
|
133 |
List<PropertyObject> args = frame.getArguments();
|
|
134 |
List<PropertyObject> locals = frame.getLocals();
|
|
135 |
|
|
136 |
int maxLookups = args.size() + locals.size() + 1 /* "this" */;
|
|
137 |
|
|
138 |
List<PropertyReference> localRefs = new ArrayList<PropertyReference>(maxLookups);
|
|
139 |
|
|
140 |
{
|
|
141 |
// Receiver ("this")
|
|
142 |
RefWithDisplayData receiverObject = frame.getReceiver().asWithDisplayData();
|
|
143 |
V8ProtocolUtil.putMirror(localRefs, receiverObject,
|
|
144 |
V8ProtocolUtil.PropertyNameGetter.THIS);
|
|
145 |
}
|
|
146 |
|
|
147 |
// Arguments
|
|
148 |
for (int i = 0; i < args.size(); i++) {
|
|
149 |
PropertyObject arg = args.get(i);
|
|
150 |
V8ProtocolUtil.putMirror(localRefs, arg, V8ProtocolUtil.PropertyNameGetter.SUBPROPERTY);
|
|
151 |
}
|
|
152 |
|
|
153 |
// Locals
|
|
154 |
for (int i = 0; i < locals.size(); i++) {
|
|
155 |
PropertyObject local = locals.get(i);
|
|
156 |
V8ProtocolUtil.putMirror(localRefs, local, V8ProtocolUtil.PropertyNameGetter.SUBPROPERTY);
|
|
157 |
}
|
|
158 |
|
|
159 |
return localRefs;
|
|
160 |
}
|
|
161 |
|
|
162 |
public static List<ScopeMirror> computeScopes(FrameObject frame) {
|
|
163 |
List<ScopeRef> scopes = frame.getScopes();
|
|
164 |
|
|
165 |
final List<ScopeMirror> result = new ArrayList<ScopeMirror>(scopes.size());
|
|
166 |
|
|
167 |
for (int i = 0; i < scopes.size(); i++) {
|
|
168 |
ScopeRef scope = scopes.get(i);
|
|
169 |
int type = (int) scope.type();
|
|
170 |
int index = (int) scope.index();
|
|
171 |
|
|
172 |
result.add(new ScopeMirror(type, index));
|
|
173 |
}
|
|
174 |
|
|
175 |
return result;
|
|
176 |
}
|
|
177 |
|
|
178 |
public static PropertyReference computeReceiverRef(FrameObject frame) {
|
|
179 |
RefWithDisplayData receiverObject = frame.getReceiver().asWithDisplayData();
|
|
180 |
return V8ProtocolUtil.extractProperty(receiverObject,
|
|
181 |
V8ProtocolUtil.PropertyNameGetter.THIS);
|
|
182 |
}
|
|
183 |
|
|
184 |
/**
|
|
185 |
* Constructs a ValueMirror given a V8 debugger object specification.
|
|
186 |
*
|
|
187 |
* @param jsonValue containing the object specification from the V8 debugger
|
|
188 |
* @return a {@link PropertyHoldingValueMirror} instance, containing data
|
|
189 |
* from jsonValue; not null
|
|
190 |
*/
|
|
191 |
public static PropertyHoldingValueMirror createMirrorFromLookup(ValueHandle valueHandle) {
|
|
192 |
String text = valueHandle.text();
|
|
193 |
if (text == null) {
|
|
194 |
throw new ValueLoadException("Bad lookup result");
|
|
195 |
}
|
|
196 |
String typeString = valueHandle.type();
|
|
197 |
String className = valueHandle.className();
|
|
198 |
Type type = JsDataTypeUtil.fromJsonTypeAndClassName(typeString, className);
|
|
199 |
if (type == null) {
|
|
200 |
throw new ValueLoadException("Bad lookup result: type field not recognized: " + typeString);
|
|
201 |
}
|
|
202 |
return createMirrorFromLookup(valueHandle, type);
|
|
203 |
}
|
|
204 |
|
|
205 |
/**
|
|
206 |
* Constructs a {@link ValueMirror} given a V8 debugger object specification if it's possible.
|
|
207 |
* @return a {@link ValueMirror} instance, containing data
|
|
208 |
* from {@code jsonValue}; or {@code null} if {@code jsonValue} is not a handle
|
|
209 |
*/
|
|
210 |
public static ValueMirror createValueMirrorOptional(DataWithRef handleFromProperty) {
|
|
211 |
RefWithDisplayData withData = handleFromProperty.getWithDisplayData();
|
|
212 |
if (withData == null) {
|
|
213 |
return null;
|
|
214 |
}
|
|
215 |
return createValueMirror(withData);
|
|
216 |
}
|
|
217 |
public static ValueMirror createValueMirrorOptional(ValueHandle valueHandle) {
|
|
218 |
return createValueMirror(valueHandle);
|
|
219 |
}
|
|
220 |
|
|
221 |
private static ValueMirror createValueMirror(ValueHandle valueHandle) {
|
|
222 |
String className = valueHandle.className();
|
|
223 |
Type type = JsDataTypeUtil.fromJsonTypeAndClassName(valueHandle.type(), className);
|
|
224 |
if (type == null) {
|
|
225 |
throw new ValueLoadException("Bad value object");
|
|
226 |
}
|
|
227 |
String text = valueHandle.text();
|
|
228 |
return createMirrorFromLookup(valueHandle, type).getValueMirror();
|
|
229 |
}
|
|
230 |
|
|
231 |
private static ValueMirror createValueMirror(RefWithDisplayData jsonValue) {
|
|
232 |
String className = jsonValue.className();
|
|
233 |
Type type = JsDataTypeUtil.fromJsonTypeAndClassName(jsonValue.type(), className);
|
|
234 |
if (type == null) {
|
|
235 |
throw new ValueLoadException("Bad value object");
|
|
236 |
}
|
|
237 |
{ // try another format
|
|
238 |
if (Type.isObjectType(type)) {
|
|
239 |
int refId = (int) jsonValue.ref();
|
|
240 |
return ValueMirror.createObjectUnknownProperties(refId, type, className);
|
|
241 |
} else {
|
|
242 |
// try another format
|
|
243 |
Object valueObj = jsonValue.value();
|
|
244 |
String valueStr;
|
|
245 |
if (valueObj == null) {
|
|
246 |
valueStr = jsonValue.type(); // e.g. "undefined"
|
|
247 |
} else {
|
|
248 |
valueStr = valueObj.toString();
|
|
249 |
}
|
|
250 |
return ValueMirror.createScalar(valueStr, type, className).getValueMirror();
|
|
251 |
}
|
|
252 |
}
|
|
253 |
}
|
|
254 |
|
|
255 |
private static PropertyHoldingValueMirror createMirrorFromLookup(ValueHandle valueHandle,
|
|
256 |
Type type) {
|
|
257 |
if (Type.isObjectType(type)) {
|
|
258 |
ObjectValueHandle objectValueHandle = valueHandle.asObject();
|
|
259 |
int refId = (int) valueHandle.handle();
|
|
260 |
SubpropertiesMirror subpropertiesMirror;
|
|
261 |
if (type == Type.TYPE_FUNCTION) {
|
|
262 |
FunctionValueHandle functionValueHandle = objectValueHandle.asFunction();
|
|
263 |
subpropertiesMirror = new SubpropertiesMirror.FunctionValueBased(functionValueHandle,
|
|
264 |
FUNCTION_PROPERTY_FACTORY2);
|
|
265 |
} else {
|
|
266 |
subpropertiesMirror =
|
|
267 |
new SubpropertiesMirror.ObjectValueBased(objectValueHandle, null);
|
|
268 |
}
|
|
269 |
return ValueMirror.createObject(refId, subpropertiesMirror, type, valueHandle.className());
|
|
270 |
} else {
|
|
271 |
return ValueMirror.createScalar(valueHandle.text(), type, valueHandle.className());
|
|
272 |
}
|
|
273 |
}
|
|
274 |
|
|
275 |
// TODO(peter.rybin): Get rid of this monstrosity once we switched to type JSON interfaces.
|
|
276 |
private static final
|
|
277 |
SubpropertiesMirror.JsonBased.AdditionalPropertyFactory<FunctionValueHandle>
|
|
278 |
FUNCTION_PROPERTY_FACTORY2 =
|
|
279 |
new SubpropertiesMirror.JsonBased.AdditionalPropertyFactory<FunctionValueHandle>() {
|
|
280 |
public Object createAdditionalProperties(FunctionValueHandle jsonWithProperties) {
|
|
281 |
Long pos = jsonWithProperties.position();
|
|
282 |
if (pos == null) {
|
|
283 |
pos = Long.valueOf(FunctionAdditionalProperties.NO_POSITION);
|
|
284 |
}
|
|
285 |
Long scriptId = jsonWithProperties.scriptId();
|
|
286 |
if (scriptId == null) {
|
|
287 |
scriptId = Long.valueOf(FunctionAdditionalProperties.NO_SCRIPT_ID);
|
|
288 |
}
|
|
289 |
return new FunctionAdditionalProperties(pos.intValue(), scriptId.intValue());
|
|
290 |
}
|
|
291 |
};
|
|
292 |
|
|
293 |
public static <MESSAGE, RES, EX extends Exception> RES callV8Sync(
|
|
294 |
V8CommandSender<MESSAGE, EX> commandSender, MESSAGE message,
|
|
295 |
V8BlockingCallback<RES> callback) throws EX {
|
|
296 |
return callV8Sync(commandSender, message, callback,
|
|
297 |
CallbackSemaphore.OPERATION_TIMEOUT_MS);
|
|
298 |
}
|
|
299 |
|
|
300 |
public static <MESSAGE, RES, EX extends Exception> RES callV8Sync(
|
|
301 |
V8CommandSender<MESSAGE, EX> commandSender,
|
|
302 |
MESSAGE message, final V8BlockingCallback<RES> callback, long timeoutMs) throws EX {
|
|
303 |
CallbackSemaphore syncCallback = new CallbackSemaphore();
|
|
304 |
final Exception [] exBuff = { null };
|
|
305 |
// A long way of creating buffer for generic type without warnings.
|
|
306 |
final List<RES> resBuff = new ArrayList<RES>(Collections.nCopies(1, (RES)null));
|
|
307 |
V8CommandProcessor.V8HandlerCallback callbackWrapper =
|
|
308 |
new V8CommandProcessor.V8HandlerCallback() {
|
|
309 |
public void failure(String message) {
|
|
310 |
exBuff[0] = new Exception("Failure: " + message);
|
|
311 |
}
|
|
312 |
|
|
313 |
public void messageReceived(CommandResponse response) {
|
|
314 |
RES result = callback.messageReceived(response);
|
|
315 |
resBuff.set(0, result);
|
|
316 |
}
|
|
317 |
};
|
|
318 |
commandSender.sendV8CommandAsync(message, true, callbackWrapper, syncCallback);
|
|
319 |
|
|
320 |
boolean waitRes;
|
|
321 |
try {
|
|
322 |
waitRes = syncCallback.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
|
|
323 |
} catch (RuntimeException e) {
|
|
324 |
throw new CallbackException(e);
|
|
325 |
}
|
|
326 |
|
|
327 |
if (!waitRes) {
|
|
328 |
throw new CallbackException("Timeout");
|
|
329 |
}
|
|
330 |
|
|
331 |
if (exBuff[0] != null) {
|
|
332 |
throw new CallbackException(exBuff[0]);
|
|
333 |
}
|
|
334 |
|
|
335 |
return resBuff.get(0);
|
|
336 |
}
|
|
337 |
|
|
338 |
/**
|
|
339 |
* Special kind of exceptions for problems in receiving or waiting for the answer.
|
|
340 |
* Clients may try to catch it.
|
|
341 |
*/
|
|
342 |
public static class CallbackException extends RuntimeException {
|
|
343 |
CallbackException() {
|
|
344 |
}
|
|
345 |
CallbackException(String message, Throwable cause) {
|
|
346 |
super(message, cause);
|
|
347 |
}
|
|
348 |
CallbackException(String message) {
|
|
349 |
super(message);
|
|
350 |
}
|
|
351 |
CallbackException(Throwable cause) {
|
|
352 |
super(cause);
|
|
353 |
}
|
|
354 |
}
|
|
355 |
}
|