|
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 } |