|
1 /* |
|
2 * Copyright (c) 2010, Google Inc. All rights reserved. |
|
3 * |
|
4 * Redistribution and use in source and binary forms, with or without |
|
5 * modification, are permitted provided that the following conditions are |
|
6 * met: |
|
7 * |
|
8 * * Redistributions of source code must retain the above copyright |
|
9 * notice, this list of conditions and the following disclaimer. |
|
10 * * Redistributions in binary form must reproduce the above |
|
11 * copyright notice, this list of conditions and the following disclaimer |
|
12 * in the documentation and/or other materials provided with the |
|
13 * distribution. |
|
14 * * Neither the name of Google Inc. nor the names of its |
|
15 * contributors may be used to endorse or promote products derived from |
|
16 * this software without specific prior written permission. |
|
17 * |
|
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
29 */ |
|
30 |
|
31 #include "config.h" |
|
32 #include "ScriptDebugServer.h" |
|
33 |
|
34 #if ENABLE(JAVASCRIPT_DEBUGGER) |
|
35 |
|
36 #include "Frame.h" |
|
37 #include "JavaScriptCallFrame.h" |
|
38 #include "Page.h" |
|
39 #include "ScriptDebugListener.h" |
|
40 #include "V8Binding.h" |
|
41 #include "V8DOMWindow.h" |
|
42 #include "V8Proxy.h" |
|
43 #include <wtf/StdLibExtras.h> |
|
44 |
|
45 namespace WebCore { |
|
46 |
|
47 static Frame* retrieveFrame(v8::Handle<v8::Context> context) |
|
48 { |
|
49 if (context.IsEmpty()) |
|
50 return 0; |
|
51 |
|
52 // Test that context has associated global dom window object. |
|
53 v8::Handle<v8::Object> global = context->Global(); |
|
54 if (global.IsEmpty()) |
|
55 return 0; |
|
56 |
|
57 global = V8DOMWrapper::lookupDOMWrapper(V8DOMWindow::GetTemplate(), global); |
|
58 if (global.IsEmpty()) |
|
59 return 0; |
|
60 |
|
61 return V8Proxy::retrieveFrame(context); |
|
62 } |
|
63 |
|
64 ScriptDebugServer& ScriptDebugServer::shared() |
|
65 { |
|
66 DEFINE_STATIC_LOCAL(ScriptDebugServer, server, ()); |
|
67 return server; |
|
68 } |
|
69 |
|
70 ScriptDebugServer::ScriptDebugServer() |
|
71 : m_pauseOnExceptionsState(DontPauseOnExceptions) |
|
72 , m_pausedPage(0) |
|
73 , m_enabled(true) |
|
74 { |
|
75 } |
|
76 |
|
77 void ScriptDebugServer::setDebuggerScriptSource(const String& scriptSource) |
|
78 { |
|
79 m_debuggerScriptSource = scriptSource; |
|
80 } |
|
81 |
|
82 void ScriptDebugServer::addListener(ScriptDebugListener* listener, Page* page) |
|
83 { |
|
84 if (!m_enabled) |
|
85 return; |
|
86 |
|
87 V8Proxy* proxy = V8Proxy::retrieve(page->mainFrame()); |
|
88 if (!proxy) |
|
89 return; |
|
90 |
|
91 v8::HandleScope scope; |
|
92 v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext(); |
|
93 v8::Context::Scope contextScope(debuggerContext); |
|
94 |
|
95 if (!m_listenersMap.size()) { |
|
96 ensureDebuggerScriptCompiled(); |
|
97 ASSERT(!m_debuggerScript.get()->IsUndefined()); |
|
98 v8::Debug::SetDebugEventListener2(&ScriptDebugServer::v8DebugEventCallback); |
|
99 } |
|
100 m_listenersMap.set(page, listener); |
|
101 |
|
102 v8::Local<v8::Context> context = proxy->mainWorldContext(); |
|
103 |
|
104 v8::Handle<v8::Function> getScriptsFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("getScripts"))); |
|
105 v8::Handle<v8::Value> argv[] = { context->GetData() }; |
|
106 v8::Handle<v8::Value> value = getScriptsFunction->Call(m_debuggerScript.get(), 1, argv); |
|
107 if (value.IsEmpty()) |
|
108 return; |
|
109 ASSERT(!value->IsUndefined() && value->IsArray()); |
|
110 v8::Handle<v8::Array> scriptsArray = v8::Handle<v8::Array>::Cast(value); |
|
111 for (unsigned i = 0; i < scriptsArray->Length(); ++i) |
|
112 dispatchDidParseSource(listener, v8::Handle<v8::Object>::Cast(scriptsArray->Get(v8::Integer::New(i)))); |
|
113 } |
|
114 |
|
115 void ScriptDebugServer::removeListener(ScriptDebugListener* listener, Page* page) |
|
116 { |
|
117 if (!m_listenersMap.contains(page)) |
|
118 return; |
|
119 |
|
120 if (m_pausedPage == page) |
|
121 continueProgram(); |
|
122 |
|
123 m_listenersMap.remove(page); |
|
124 |
|
125 if (m_listenersMap.isEmpty()) |
|
126 v8::Debug::SetDebugEventListener(0); |
|
127 // FIXME: Remove all breakpoints set by the agent. |
|
128 } |
|
129 |
|
130 bool ScriptDebugServer::setBreakpoint(const String& sourceID, ScriptBreakpoint breakpoint, unsigned lineNumber, unsigned* actualLineNumber) |
|
131 { |
|
132 v8::HandleScope scope; |
|
133 v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext(); |
|
134 v8::Context::Scope contextScope(debuggerContext); |
|
135 |
|
136 v8::Local<v8::Object> args = v8::Object::New(); |
|
137 args->Set(v8::String::New("scriptId"), v8String(sourceID)); |
|
138 args->Set(v8::String::New("lineNumber"), v8::Integer::New(lineNumber)); |
|
139 args->Set(v8::String::New("condition"), v8String(breakpoint.condition)); |
|
140 args->Set(v8::String::New("enabled"), v8::Boolean::New(breakpoint.enabled)); |
|
141 |
|
142 v8::Handle<v8::Function> setBreakpointFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setBreakpoint"))); |
|
143 v8::Handle<v8::Value> result = v8::Debug::Call(setBreakpointFunction, args); |
|
144 if (!result->IsNumber()) |
|
145 return false; |
|
146 ASSERT(result->Int32Value() >= 0); |
|
147 *actualLineNumber = result->Int32Value(); |
|
148 return true; |
|
149 } |
|
150 |
|
151 void ScriptDebugServer::removeBreakpoint(const String& sourceID, unsigned lineNumber) |
|
152 { |
|
153 v8::HandleScope scope; |
|
154 v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext(); |
|
155 v8::Context::Scope contextScope(debuggerContext); |
|
156 |
|
157 v8::Local<v8::Object> args = v8::Object::New(); |
|
158 args->Set(v8::String::New("scriptId"), v8String(sourceID)); |
|
159 args->Set(v8::String::New("lineNumber"), v8::Integer::New(lineNumber)); |
|
160 |
|
161 v8::Handle<v8::Function> removeBreakpointFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("removeBreakpoint"))); |
|
162 v8::Debug::Call(removeBreakpointFunction, args); |
|
163 } |
|
164 |
|
165 void ScriptDebugServer::clearBreakpoints() |
|
166 { |
|
167 ensureDebuggerScriptCompiled(); |
|
168 v8::HandleScope scope; |
|
169 v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext(); |
|
170 v8::Context::Scope contextScope(debuggerContext); |
|
171 |
|
172 v8::Handle<v8::Function> clearBreakpoints = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("clearBreakpoints"))); |
|
173 v8::Debug::Call(clearBreakpoints); |
|
174 } |
|
175 |
|
176 void ScriptDebugServer::setBreakpointsActivated(bool enabled) |
|
177 { |
|
178 ensureDebuggerScriptCompiled(); |
|
179 v8::HandleScope scope; |
|
180 v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext(); |
|
181 v8::Context::Scope contextScope(debuggerContext); |
|
182 |
|
183 v8::Local<v8::Object> args = v8::Object::New(); |
|
184 args->Set(v8::String::New("enabled"), v8::Boolean::New(enabled)); |
|
185 v8::Handle<v8::Function> setBreakpointsActivated = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setBreakpointsActivated"))); |
|
186 v8::Debug::Call(setBreakpointsActivated, args); |
|
187 } |
|
188 |
|
189 ScriptDebugServer::PauseOnExceptionsState ScriptDebugServer::pauseOnExceptionsState() |
|
190 { |
|
191 ensureDebuggerScriptCompiled(); |
|
192 v8::HandleScope scope; |
|
193 v8::Context::Scope contextScope(v8::Debug::GetDebugContext()); |
|
194 |
|
195 v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("pauseOnExceptionsState"))); |
|
196 v8::Handle<v8::Value> argv[] = { v8::Handle<v8::Value>() }; |
|
197 v8::Handle<v8::Value> result = function->Call(m_debuggerScript.get(), 0, argv); |
|
198 return static_cast<ScriptDebugServer::PauseOnExceptionsState>(result->Int32Value()); |
|
199 } |
|
200 |
|
201 void ScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pauseOnExceptionsState) |
|
202 { |
|
203 ensureDebuggerScriptCompiled(); |
|
204 v8::HandleScope scope; |
|
205 v8::Context::Scope contextScope(v8::Debug::GetDebugContext()); |
|
206 |
|
207 v8::Handle<v8::Function> setPauseOnExceptionsFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setPauseOnExceptionsState"))); |
|
208 v8::Handle<v8::Value> argv[] = { v8::Int32::New(pauseOnExceptionsState) }; |
|
209 setPauseOnExceptionsFunction->Call(m_debuggerScript.get(), 1, argv); |
|
210 } |
|
211 |
|
212 void ScriptDebugServer::continueProgram() |
|
213 { |
|
214 if (m_pausedPage) |
|
215 m_clientMessageLoop->quitNow(); |
|
216 didResume(); |
|
217 } |
|
218 |
|
219 void ScriptDebugServer::stepIntoStatement() |
|
220 { |
|
221 ASSERT(m_pausedPage); |
|
222 v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepIntoStatement"))); |
|
223 v8::Handle<v8::Value> argv[] = { m_executionState.get() }; |
|
224 function->Call(m_debuggerScript.get(), 1, argv); |
|
225 continueProgram(); |
|
226 } |
|
227 |
|
228 void ScriptDebugServer::stepOverStatement() |
|
229 { |
|
230 ASSERT(m_pausedPage); |
|
231 v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepOverStatement"))); |
|
232 v8::Handle<v8::Value> argv[] = { m_executionState.get() }; |
|
233 function->Call(m_debuggerScript.get(), 1, argv); |
|
234 continueProgram(); |
|
235 } |
|
236 |
|
237 void ScriptDebugServer::stepOutOfFunction() |
|
238 { |
|
239 ASSERT(m_pausedPage); |
|
240 v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepOutOfFunction"))); |
|
241 v8::Handle<v8::Value> argv[] = { m_executionState.get() }; |
|
242 function->Call(m_debuggerScript.get(), 1, argv); |
|
243 continueProgram(); |
|
244 } |
|
245 |
|
246 bool ScriptDebugServer::editScriptSource(const String& sourceID, const String& newContent, String& newSourceOrErrorMessage) |
|
247 { |
|
248 ensureDebuggerScriptCompiled(); |
|
249 v8::HandleScope scope; |
|
250 |
|
251 OwnPtr<v8::Context::Scope> contextScope; |
|
252 if (!m_pausedPage) |
|
253 contextScope.set(new v8::Context::Scope(v8::Debug::GetDebugContext())); |
|
254 |
|
255 v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("editScriptSource"))); |
|
256 v8::Handle<v8::Value> argv[] = { v8String(sourceID), v8String(newContent) }; |
|
257 |
|
258 v8::TryCatch tryCatch; |
|
259 tryCatch.SetVerbose(false); |
|
260 v8::Handle<v8::Value> result = function->Call(m_debuggerScript.get(), 2, argv); |
|
261 if (tryCatch.HasCaught()) { |
|
262 v8::Local<v8::Message> message = tryCatch.Message(); |
|
263 if (!message.IsEmpty()) |
|
264 newSourceOrErrorMessage = toWebCoreStringWithNullOrUndefinedCheck(message->Get()); |
|
265 return false; |
|
266 } |
|
267 ASSERT(!result.IsEmpty()); |
|
268 newSourceOrErrorMessage = toWebCoreStringWithNullOrUndefinedCheck(result); |
|
269 |
|
270 // Call stack may have changed after if the edited function was on the stack. |
|
271 if (m_currentCallFrame) |
|
272 m_currentCallFrame.clear(); |
|
273 return true; |
|
274 } |
|
275 |
|
276 PassRefPtr<JavaScriptCallFrame> ScriptDebugServer::currentCallFrame() |
|
277 { |
|
278 if (!m_currentCallFrame) { |
|
279 v8::Handle<v8::Function> currentCallFrameFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("currentCallFrame"))); |
|
280 v8::Handle<v8::Value> argv[] = { m_executionState.get() }; |
|
281 v8::Handle<v8::Value> currentCallFrameV8 = currentCallFrameFunction->Call(m_debuggerScript.get(), 1, argv); |
|
282 m_currentCallFrame = JavaScriptCallFrame::create(v8::Debug::GetDebugContext(), v8::Handle<v8::Object>::Cast(currentCallFrameV8)); |
|
283 } |
|
284 return m_currentCallFrame; |
|
285 } |
|
286 |
|
287 void ScriptDebugServer::setEnabled(bool value) |
|
288 { |
|
289 m_enabled = value; |
|
290 } |
|
291 |
|
292 bool ScriptDebugServer::isDebuggerAlwaysEnabled() |
|
293 { |
|
294 return m_enabled; |
|
295 } |
|
296 |
|
297 void ScriptDebugServer::v8DebugEventCallback(const v8::Debug::EventDetails& eventDetails) |
|
298 { |
|
299 ScriptDebugServer::shared().handleV8DebugEvent(eventDetails); |
|
300 } |
|
301 |
|
302 void ScriptDebugServer::handleV8DebugEvent(const v8::Debug::EventDetails& eventDetails) |
|
303 { |
|
304 v8::DebugEvent event = eventDetails.GetEvent(); |
|
305 if (event != v8::Break && event != v8::Exception && event != v8::AfterCompile) |
|
306 return; |
|
307 |
|
308 v8::Handle<v8::Context> eventContext = eventDetails.GetEventContext(); |
|
309 ASSERT(!eventContext.IsEmpty()); |
|
310 |
|
311 Frame* frame = retrieveFrame(eventContext); |
|
312 if (frame) { |
|
313 ScriptDebugListener* listener = m_listenersMap.get(frame->page()); |
|
314 if (listener) { |
|
315 v8::HandleScope scope; |
|
316 if (event == v8::AfterCompile) { |
|
317 v8::Context::Scope contextScope(v8::Debug::GetDebugContext()); |
|
318 v8::Handle<v8::Function> onAfterCompileFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("getAfterCompileScript"))); |
|
319 v8::Handle<v8::Value> argv[] = { eventDetails.GetEventData() }; |
|
320 v8::Handle<v8::Value> value = onAfterCompileFunction->Call(m_debuggerScript.get(), 1, argv); |
|
321 ASSERT(value->IsObject()); |
|
322 v8::Handle<v8::Object> object = v8::Handle<v8::Object>::Cast(value); |
|
323 dispatchDidParseSource(listener, object); |
|
324 } else if (event == v8::Break || event == v8::Exception) { |
|
325 if (event == v8::Exception) { |
|
326 v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(1); |
|
327 // Stack trace is empty in case of syntax error. Silently continue execution in such cases. |
|
328 if (!stackTrace->GetFrameCount()) |
|
329 return; |
|
330 } |
|
331 |
|
332 // Don't allow nested breaks. |
|
333 if (m_pausedPage) |
|
334 return; |
|
335 m_executionState.set(eventDetails.GetExecutionState()); |
|
336 m_pausedPage = frame->page(); |
|
337 ScriptState* currentCallFrameState = mainWorldScriptState(frame); |
|
338 listener->didPause(currentCallFrameState); |
|
339 |
|
340 // Wait for continue or step command. |
|
341 m_clientMessageLoop->run(m_pausedPage); |
|
342 ASSERT(!m_pausedPage); |
|
343 |
|
344 // The listener may have been removed in the nested loop. |
|
345 if (ScriptDebugListener* listener = m_listenersMap.get(frame->page())) |
|
346 listener->didContinue(); |
|
347 } |
|
348 } |
|
349 } |
|
350 } |
|
351 |
|
352 void ScriptDebugServer::dispatchDidParseSource(ScriptDebugListener* listener, v8::Handle<v8::Object> object) |
|
353 { |
|
354 listener->didParseSource( |
|
355 toWebCoreStringWithNullOrUndefinedCheck(object->Get(v8::String::New("id"))), |
|
356 toWebCoreStringWithNullOrUndefinedCheck(object->Get(v8::String::New("name"))), |
|
357 toWebCoreStringWithNullOrUndefinedCheck(object->Get(v8::String::New("source"))), |
|
358 object->Get(v8::String::New("lineOffset"))->ToInteger()->Value(), |
|
359 static_cast<ScriptWorldType>(object->Get(v8::String::New("scriptWorldType"))->Int32Value())); |
|
360 } |
|
361 |
|
362 void ScriptDebugServer::ensureDebuggerScriptCompiled() |
|
363 { |
|
364 if (m_debuggerScript.get().IsEmpty()) { |
|
365 v8::HandleScope scope; |
|
366 v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext(); |
|
367 v8::Context::Scope contextScope(debuggerContext); |
|
368 m_debuggerScript.set(v8::Handle<v8::Object>::Cast(v8::Script::Compile(v8String(m_debuggerScriptSource))->Run())); |
|
369 } |
|
370 } |
|
371 |
|
372 void ScriptDebugServer::didResume() |
|
373 { |
|
374 m_currentCallFrame.clear(); |
|
375 m_executionState.clear(); |
|
376 m_pausedPage = 0; |
|
377 } |
|
378 |
|
379 } // namespace WebCore |
|
380 |
|
381 #endif // ENABLE(JAVASCRIPT_DEBUGGER) |