|
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; |
|
6 |
|
7 import java.util.ArrayList; |
|
8 import java.util.Collection; |
|
9 import java.util.Collections; |
|
10 import java.util.List; |
|
11 |
|
12 import org.chromium.sdk.Breakpoint; |
|
13 import org.chromium.sdk.CallFrame; |
|
14 import org.chromium.sdk.DebugContext; |
|
15 import org.chromium.sdk.ExceptionData; |
|
16 import org.chromium.sdk.Script; |
|
17 import org.chromium.sdk.SyncCallback; |
|
18 import org.chromium.sdk.internal.protocol.CommandResponse; |
|
19 import org.chromium.sdk.internal.protocol.SuccessCommandResponse; |
|
20 import org.chromium.sdk.internal.tools.v8.V8CommandProcessor; |
|
21 import org.chromium.sdk.internal.tools.v8.V8CommandProcessor.V8HandlerCallback; |
|
22 import org.chromium.sdk.internal.tools.v8.request.DebuggerMessage; |
|
23 import org.chromium.sdk.internal.tools.v8.request.DebuggerMessageFactory; |
|
24 |
|
25 public class ContextBuilder { |
|
26 private final DebugSession debugSession; |
|
27 |
|
28 /** |
|
29 * Context building process comes though sequence of steps. No one should |
|
30 * be able to work with any step exception the current one. |
|
31 */ |
|
32 private Object currentStep = null; |
|
33 |
|
34 ContextBuilder(DebugSession debugSession) { |
|
35 this.debugSession = debugSession; |
|
36 } |
|
37 |
|
38 public interface ExpectingBreakEventStep { |
|
39 InternalContext getInternalContext(); |
|
40 /** |
|
41 * Stores the breakpoints associated with V8 suspension event (empty if an |
|
42 * exception or a step end). |
|
43 * |
|
44 * @param breakpointsHit the breakpoints that were hit |
|
45 */ |
|
46 ExpectingBacktraceStep setContextState(Collection<Breakpoint> breakpointsHit, |
|
47 ExceptionData exceptionData); |
|
48 } |
|
49 public interface ExpectingBacktraceStep { |
|
50 InternalContext getInternalContext(); |
|
51 |
|
52 DebugContext setFrames(FrameMirror[] frameMirrors); |
|
53 } |
|
54 |
|
55 /** |
|
56 * Starting point of building new DebugContext process. One should traverse |
|
57 * list of steps to get the result. |
|
58 * @return object representing first step of context building process |
|
59 */ |
|
60 public ExpectingBreakEventStep buildNewContext() { |
|
61 assertStep(null); |
|
62 |
|
63 final PreContext preContext = new PreContext(); |
|
64 final DebugContextData contextData = new DebugContextData(); |
|
65 |
|
66 return new ExpectingBreakEventStep() { |
|
67 { |
|
68 currentStep = this; |
|
69 } |
|
70 |
|
71 public InternalContext getInternalContext() { |
|
72 return preContext; |
|
73 } |
|
74 |
|
75 public ExpectingBacktraceStep setContextState(Collection<Breakpoint> breakpointsHit, |
|
76 ExceptionData exceptionData) { |
|
77 assertStep(this); |
|
78 |
|
79 DebugContext.State state; |
|
80 if (exceptionData == null) { |
|
81 state = DebugContext.State.NORMAL; |
|
82 } else { |
|
83 state = DebugContext.State.EXCEPTION; |
|
84 } |
|
85 |
|
86 contextData.contextState = state; |
|
87 contextData.breakpointsHit = Collections.unmodifiableCollection(breakpointsHit); |
|
88 contextData.exceptionData = exceptionData; |
|
89 |
|
90 return new ExpectingBacktraceStep() { |
|
91 { |
|
92 currentStep = this; |
|
93 } |
|
94 |
|
95 public InternalContext getInternalContext() { |
|
96 return preContext; |
|
97 } |
|
98 |
|
99 public DebugContext setFrames(FrameMirror[] frameMirrors) { |
|
100 assertStep(this); |
|
101 |
|
102 contextData.frames = new Frames(frameMirrors, preContext); |
|
103 |
|
104 preContext.createContext(contextData); |
|
105 |
|
106 DebugContext userContext = preContext.getContext(); |
|
107 currentStep = userContext; |
|
108 return userContext; |
|
109 } |
|
110 }; |
|
111 } |
|
112 }; |
|
113 } |
|
114 |
|
115 public ExpectingBreakEventStep buildNewContextWhenIdle() { |
|
116 if (currentStep == null) { |
|
117 return buildNewContext(); |
|
118 } else { |
|
119 return null; |
|
120 } |
|
121 } |
|
122 |
|
123 /** |
|
124 * Debug session is stopped. Cancel context in any state. |
|
125 */ |
|
126 public void forceCancelContext() { |
|
127 // TODO(peter.rybin): complete it |
|
128 } |
|
129 |
|
130 public void buildSequenceFailure() { |
|
131 // this means we can't go on debugging |
|
132 // TODO(peter.rybin): implement |
|
133 throw new RuntimeException(); |
|
134 } |
|
135 |
|
136 private void contextDismissed(DebugContext userContext) { |
|
137 assertStep(userContext); |
|
138 currentStep = null; |
|
139 } |
|
140 |
|
141 private void assertStep(Object step) { |
|
142 if (currentStep != step) { |
|
143 throw new IllegalStateException("Expected " + step + ", but was " + currentStep); |
|
144 } |
|
145 } |
|
146 |
|
147 private class PreContext implements InternalContext { |
|
148 private final HandleManager handleManager = new HandleManager(); |
|
149 private final ValueLoader valueLoader = new ValueLoader(this); |
|
150 |
|
151 /** |
|
152 * We synchronize {@link #isValid} state with commands that are being sent |
|
153 * using this monitor. |
|
154 */ |
|
155 private final Object sendContextCommandsMonitor = new Object(); |
|
156 private volatile boolean isValid = true; |
|
157 private UserContext context = null; |
|
158 |
|
159 public boolean isValid() { |
|
160 return isValid; |
|
161 } |
|
162 |
|
163 public DebugSession getDebugSession() { |
|
164 return debugSession; |
|
165 } |
|
166 |
|
167 public ContextBuilder getContextBuilder() { |
|
168 return ContextBuilder.this; |
|
169 } |
|
170 |
|
171 void assertValid() { |
|
172 if (!isValid) { |
|
173 throw new IllegalStateException("This instance of DebugContext cannot be used anymore"); |
|
174 } |
|
175 } |
|
176 |
|
177 /** |
|
178 * Check if context is valid right now. Throws exception if we are in a strict mode. |
|
179 * Ignores otherwise. |
|
180 */ |
|
181 void assertValidForUser() { |
|
182 if (!isValid) { |
|
183 debugSession.maybeRethrowContextException(null); |
|
184 } |
|
185 } |
|
186 |
|
187 public UserContext getContext() { |
|
188 if (context == null) { |
|
189 throw new IllegalStateException(); |
|
190 } |
|
191 return context; |
|
192 } |
|
193 |
|
194 public CallFrameImpl getTopFrameImpl() { |
|
195 assertValid(); |
|
196 return getContext().data.frames.getCallFrames().get(0); |
|
197 } |
|
198 |
|
199 public HandleManager getHandleManager() { |
|
200 // tolerates dismissed context |
|
201 return handleManager; |
|
202 } |
|
203 |
|
204 public ValueLoader getValueLoader() { |
|
205 return valueLoader; |
|
206 } |
|
207 |
|
208 |
|
209 void createContext(DebugContextData contextData) { |
|
210 if (context != null) { |
|
211 throw new IllegalStateException(); |
|
212 } |
|
213 context = new UserContext(contextData); |
|
214 } |
|
215 |
|
216 public void sendV8CommandAsync(DebuggerMessage message, boolean isImmediate, |
|
217 V8HandlerCallback commandCallback, SyncCallback syncCallback) |
|
218 throws ContextDismissedCheckedException { |
|
219 synchronized (sendContextCommandsMonitor) { |
|
220 if (!isValid) { |
|
221 throw new ContextDismissedCheckedException(); |
|
222 } |
|
223 debugSession.getV8CommandProcessor().sendV8CommandAsync(message, isImmediate, |
|
224 commandCallback, syncCallback); |
|
225 } |
|
226 } |
|
227 |
|
228 private void sendMessageAsyncAndIvalidate(DebuggerMessage message, |
|
229 V8CommandProcessor.V8HandlerCallback commandCallback, boolean isImmediate, |
|
230 SyncCallback syncCallback) { |
|
231 synchronized (sendContextCommandsMonitor) { |
|
232 assertValid(); |
|
233 debugSession.getV8CommandProcessor().sendV8CommandAsync(message, isImmediate, |
|
234 commandCallback, syncCallback); |
|
235 isValid = false; |
|
236 } |
|
237 } |
|
238 |
|
239 private class UserContext implements DebugContext { |
|
240 private final DebugContextData data; |
|
241 |
|
242 public UserContext(DebugContextData contextData) { |
|
243 this.data = contextData; |
|
244 } |
|
245 |
|
246 public State getState() { |
|
247 assertValidForUser(); |
|
248 return data.contextState; |
|
249 } |
|
250 public List<? extends CallFrame> getCallFrames() { |
|
251 assertValidForUser(); |
|
252 return data.frames.getCallFrames(); |
|
253 } |
|
254 |
|
255 public Collection<Breakpoint> getBreakpointsHit() { |
|
256 assertValidForUser(); |
|
257 if (data.breakpointsHit == null) { |
|
258 throw new RuntimeException(); |
|
259 } |
|
260 return data.breakpointsHit; |
|
261 } |
|
262 |
|
263 public ExceptionData getExceptionData() { |
|
264 assertValidForUser(); |
|
265 return data.exceptionData; |
|
266 } |
|
267 |
|
268 /** |
|
269 * @throws IllegalStateException if context has already been continued |
|
270 */ |
|
271 public void continueVm(StepAction stepAction, int stepCount, |
|
272 final ContinueCallback callback) { |
|
273 if (stepAction == null) { |
|
274 throw new NullPointerException(); |
|
275 } |
|
276 |
|
277 DebuggerMessage message = DebuggerMessageFactory.goOn(stepAction, stepCount); |
|
278 V8CommandProcessor.V8HandlerCallback commandCallback |
|
279 = new V8CommandProcessor.V8HandlerCallback() { |
|
280 public void messageReceived(CommandResponse response) { |
|
281 SuccessCommandResponse successResponse = response.asSuccess(); |
|
282 if (successResponse == null) { |
|
283 this.failure(response.asFailure().getMessage()); |
|
284 return; |
|
285 } |
|
286 |
|
287 contextDismissed(UserContext.this); |
|
288 |
|
289 if (callback != null) { |
|
290 callback.success(); |
|
291 } |
|
292 getDebugSession().getDebugEventListener().resumed(); |
|
293 } |
|
294 public void failure(String message) { |
|
295 synchronized (sendContextCommandsMonitor) { |
|
296 // resurrected |
|
297 isValid = true; |
|
298 } |
|
299 if (callback != null) { |
|
300 callback.failure(message); |
|
301 } |
|
302 } |
|
303 }; |
|
304 |
|
305 sendMessageAsyncAndIvalidate(message, commandCallback, true, null); |
|
306 } |
|
307 |
|
308 InternalContext getInternalContextForTests() { |
|
309 return PreContext.this; |
|
310 } |
|
311 } |
|
312 } |
|
313 |
|
314 /** |
|
315 * Simple structure of data which DebugConext implementation uses. |
|
316 */ |
|
317 private static class DebugContextData { |
|
318 private Frames frames; |
|
319 /** The breakpoints hit before suspending. */ |
|
320 private volatile Collection<Breakpoint> breakpointsHit; |
|
321 |
|
322 DebugContext.State contextState; |
|
323 /** The JavaScript exception state. */ |
|
324 private ExceptionData exceptionData; |
|
325 } |
|
326 |
|
327 private class Frames { |
|
328 /** The frame mirrors while on a breakpoint. */ |
|
329 private final FrameMirror[] frameMirrors; |
|
330 /** The cached call frames constructed using frameMirrors. */ |
|
331 private final List<CallFrameImpl> unmodifableFrames; |
|
332 private boolean scriptsLinkedToFrames; |
|
333 |
|
334 Frames(FrameMirror[] frameMirrors0, InternalContext internalContext) { |
|
335 this.frameMirrors = frameMirrors0; |
|
336 this.scriptsLinkedToFrames = false; |
|
337 |
|
338 int frameCount = frameMirrors.length; |
|
339 List<CallFrameImpl> frameList = new ArrayList<CallFrameImpl>(frameCount); |
|
340 for (int i = 0; i < frameCount; ++i) { |
|
341 frameList.add(new CallFrameImpl(frameMirrors[i], i, internalContext)); |
|
342 } |
|
343 this.unmodifableFrames = Collections.unmodifiableList(frameList); |
|
344 } |
|
345 |
|
346 synchronized List<CallFrameImpl> getCallFrames() { |
|
347 if (!scriptsLinkedToFrames) { |
|
348 // We expect that ALL the V8 scripts are loaded so we can |
|
349 // hook them up to the call frames. |
|
350 int frameCount = frameMirrors.length; |
|
351 for (int i = 0; i < frameCount; ++i) { |
|
352 hookupScriptToFrame(i); |
|
353 } |
|
354 scriptsLinkedToFrames = true; |
|
355 } |
|
356 return unmodifableFrames; |
|
357 } |
|
358 |
|
359 |
|
360 /** |
|
361 * Associates a script found in the ScriptManager with the given frame. |
|
362 * |
|
363 * @param frameIndex to associate a script with |
|
364 */ |
|
365 private void hookupScriptToFrame(int frameIndex) { |
|
366 FrameMirror frame = frameMirrors[frameIndex]; |
|
367 if (frame != null && frame.getScript() == null) { |
|
368 Script script = debugSession.getScriptManager().findById(frame.getScriptId()); |
|
369 if (script != null) { |
|
370 frame.setScript(script); |
|
371 } |
|
372 } |
|
373 } |
|
374 } |
|
375 |
|
376 static InternalContext getInternalContextForTests(DebugContext debugContext) { |
|
377 PreContext.UserContext userContext = (PreContext.UserContext) debugContext; |
|
378 return userContext.getInternalContextForTests(); |
|
379 } |
|
380 } |