4b4611a507a5cd8ac4eb98f78b916a6871265795
[WebKit-https.git] / WebCore / bindings / v8 / ScriptDebugServer.cpp
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 namespace {
48
49 class ClientDataImpl : public v8::Debug::ClientData {
50 public:
51     ClientDataImpl(PassOwnPtr<ScriptDebugServer::Task> task) : m_task(task) { }
52     virtual ~ClientDataImpl() { }
53     ScriptDebugServer::Task* task() const { return m_task.get(); }
54 private:
55     OwnPtr<ScriptDebugServer::Task> m_task;
56 };
57
58 }
59
60 static Frame* retrieveFrame(v8::Handle<v8::Context> context)
61 {
62     if (context.IsEmpty())
63         return 0;
64
65     // Test that context has associated global dom window object.
66     v8::Handle<v8::Object> global = context->Global();
67     if (global.IsEmpty())
68         return 0;
69
70     global = V8DOMWrapper::lookupDOMWrapper(V8DOMWindow::GetTemplate(), global);
71     if (global.IsEmpty())
72         return 0;
73
74     return V8Proxy::retrieveFrame(context);
75 }
76
77 ScriptDebugServer& ScriptDebugServer::shared()
78 {
79     DEFINE_STATIC_LOCAL(ScriptDebugServer, server, ());
80     return server;
81 }
82
83 ScriptDebugServer::ScriptDebugServer()
84     : m_pauseOnExceptionsState(DontPauseOnExceptions)
85     , m_pausedPage(0)
86     , m_enabled(true)
87     , m_breakpointsActivated(true)
88 {
89 }
90
91 void ScriptDebugServer::setDebuggerScriptSource(const String& scriptSource)
92 {
93     m_debuggerScriptSource = scriptSource;
94 }
95
96 void ScriptDebugServer::addListener(ScriptDebugListener* listener, Page* page)
97 {
98     if (!m_enabled)
99         return;
100
101     V8Proxy* proxy = V8Proxy::retrieve(page->mainFrame());
102     if (!proxy)
103         return;
104
105     v8::HandleScope scope;
106     v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
107     v8::Context::Scope contextScope(debuggerContext);
108
109     if (!m_listenersMap.size()) {
110         ensureDebuggerScriptCompiled();
111         ASSERT(!m_debuggerScript.get()->IsUndefined());
112         v8::Debug::SetDebugEventListener2(&ScriptDebugServer::v8DebugEventCallback);
113     }
114     m_listenersMap.set(page, listener);
115
116     V8DOMWindowShell* shell = proxy->windowShell();
117     if (!shell->isContextInitialized())
118         return;
119     v8::Handle<v8::Context> context = shell->context();
120     v8::Handle<v8::Function> getScriptsFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("getScripts")));
121     v8::Handle<v8::Value> argv[] = { context->GetData() };
122     v8::Handle<v8::Value> value = getScriptsFunction->Call(m_debuggerScript.get(), 1, argv);
123     if (value.IsEmpty())
124         return;
125     ASSERT(!value->IsUndefined() && value->IsArray());
126     v8::Handle<v8::Array> scriptsArray = v8::Handle<v8::Array>::Cast(value);
127     for (unsigned i = 0; i < scriptsArray->Length(); ++i)
128         dispatchDidParseSource(listener, v8::Handle<v8::Object>::Cast(scriptsArray->Get(v8::Integer::New(i))));
129 }
130
131 void ScriptDebugServer::removeListener(ScriptDebugListener* listener, Page* page)
132 {
133     if (!m_listenersMap.contains(page))
134         return;
135
136     if (m_pausedPage == page)
137         continueProgram();
138
139     m_listenersMap.remove(page);
140
141     if (m_listenersMap.isEmpty())
142         v8::Debug::SetDebugEventListener(0);
143     // FIXME: Remove all breakpoints set by the agent.
144 }
145
146 String ScriptDebugServer::setBreakpoint(const String& sourceID, unsigned lineNumber, const String& condition, bool enabled, unsigned* actualLineNumber)
147 {
148     v8::HandleScope scope;
149     v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
150     v8::Context::Scope contextScope(debuggerContext);
151
152     v8::Local<v8::Object> args = v8::Object::New();
153     args->Set(v8::String::New("scriptId"), v8String(sourceID));
154     args->Set(v8::String::New("lineNumber"), v8::Integer::New(lineNumber));
155     args->Set(v8::String::New("condition"), v8String(condition));
156     args->Set(v8::String::New("enabled"), v8::Boolean::New(enabled));
157
158     v8::Handle<v8::Function> setBreakpointFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setBreakpoint")));
159     v8::Handle<v8::Value> breakpointId = v8::Debug::Call(setBreakpointFunction, args);
160     if (!breakpointId->IsString())
161         return "";
162     *actualLineNumber = args->Get(v8::String::New("lineNumber"))->Int32Value();
163     return v8StringToWebCoreString(breakpointId->ToString());
164 }
165
166 void ScriptDebugServer::removeBreakpoint(const String& breakpointId)
167 {
168     v8::HandleScope scope;
169     v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
170     v8::Context::Scope contextScope(debuggerContext);
171
172     v8::Local<v8::Object> args = v8::Object::New();
173     args->Set(v8::String::New("breakpointId"), v8String(breakpointId));
174
175     v8::Handle<v8::Function> removeBreakpointFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("removeBreakpoint")));
176     v8::Debug::Call(removeBreakpointFunction, args);
177 }
178
179 void ScriptDebugServer::clearBreakpoints()
180 {
181     ensureDebuggerScriptCompiled();
182     v8::HandleScope scope;
183     v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
184     v8::Context::Scope contextScope(debuggerContext);
185
186     v8::Handle<v8::Function> clearBreakpoints = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("clearBreakpoints")));
187     v8::Debug::Call(clearBreakpoints);
188 }
189
190 void ScriptDebugServer::setBreakpointsActivated(bool activated)
191 {
192     ensureDebuggerScriptCompiled();
193     v8::HandleScope scope;
194     v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
195     v8::Context::Scope contextScope(debuggerContext);
196
197     v8::Local<v8::Object> args = v8::Object::New();
198     args->Set(v8::String::New("enabled"), v8::Boolean::New(activated));
199     v8::Handle<v8::Function> setBreakpointsActivated = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setBreakpointsActivated")));
200     v8::Debug::Call(setBreakpointsActivated, args);
201
202     m_breakpointsActivated = activated;
203 }
204
205 ScriptDebugServer::PauseOnExceptionsState ScriptDebugServer::pauseOnExceptionsState()
206 {
207     ensureDebuggerScriptCompiled();
208     v8::HandleScope scope;
209     v8::Context::Scope contextScope(v8::Debug::GetDebugContext());
210
211     v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("pauseOnExceptionsState")));
212     v8::Handle<v8::Value> argv[] = { v8::Handle<v8::Value>() };
213     v8::Handle<v8::Value> result = function->Call(m_debuggerScript.get(), 0, argv);
214     return static_cast<ScriptDebugServer::PauseOnExceptionsState>(result->Int32Value());
215 }
216
217 void ScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pauseOnExceptionsState)
218 {
219     ensureDebuggerScriptCompiled();
220     v8::HandleScope scope;
221     v8::Context::Scope contextScope(v8::Debug::GetDebugContext());
222
223     v8::Handle<v8::Function> setPauseOnExceptionsFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setPauseOnExceptionsState")));
224     v8::Handle<v8::Value> argv[] = { v8::Int32::New(pauseOnExceptionsState) };
225     setPauseOnExceptionsFunction->Call(m_debuggerScript.get(), 1, argv);
226 }
227
228 void ScriptDebugServer::setPauseOnNextStatement(bool pause)
229 {
230     if (m_pausedPage)
231         return;
232     if (pause)
233         v8::Debug::DebugBreak();
234     else
235         v8::Debug::CancelDebugBreak();
236 }
237
238 void ScriptDebugServer::breakProgram()
239 {
240     DEFINE_STATIC_LOCAL(v8::Persistent<v8::FunctionTemplate>, callbackTemplate, ());
241
242     if (!m_breakpointsActivated)
243         return;
244
245     if (!v8::Context::InContext())
246         return;
247
248     if (callbackTemplate.IsEmpty()) {
249         callbackTemplate = v8::Persistent<v8::FunctionTemplate>::New(v8::FunctionTemplate::New());
250         callbackTemplate->SetCallHandler(&ScriptDebugServer::breakProgramCallback);
251     }
252
253     v8::Handle<v8::Context> context = v8::Context::GetCurrent();
254     if (context.IsEmpty())
255         return;
256
257     m_pausedPageContext = *context;
258     v8::Handle<v8::Function> breakProgramFunction = callbackTemplate->GetFunction();
259     v8::Debug::Call(breakProgramFunction);
260     m_pausedPageContext.Clear();
261 }
262
263 void ScriptDebugServer::continueProgram()
264 {
265     if (m_pausedPage)
266         m_clientMessageLoop->quitNow();
267     didResume();
268 }
269
270 void ScriptDebugServer::stepIntoStatement()
271 {
272     ASSERT(m_pausedPage);
273     v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepIntoStatement")));
274     v8::Handle<v8::Value> argv[] = { m_executionState.get() };
275     function->Call(m_debuggerScript.get(), 1, argv);
276     continueProgram();
277 }
278
279 void ScriptDebugServer::stepOverStatement()
280 {
281     ASSERT(m_pausedPage);
282     v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepOverStatement")));
283     v8::Handle<v8::Value> argv[] = { m_executionState.get() };
284     function->Call(m_debuggerScript.get(), 1, argv);
285     continueProgram();
286 }
287
288 void ScriptDebugServer::stepOutOfFunction()
289 {
290     ASSERT(m_pausedPage);
291     v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepOutOfFunction")));
292     v8::Handle<v8::Value> argv[] = { m_executionState.get() };
293     function->Call(m_debuggerScript.get(), 1, argv);
294     continueProgram();
295 }
296
297 bool ScriptDebugServer::editScriptSource(const String& sourceID, const String& newContent, String& newSourceOrErrorMessage)
298 {
299     ensureDebuggerScriptCompiled();
300     v8::HandleScope scope;
301
302     OwnPtr<v8::Context::Scope> contextScope;
303     if (!m_pausedPage)
304         contextScope.set(new v8::Context::Scope(v8::Debug::GetDebugContext()));
305
306     v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("editScriptSource")));
307     v8::Handle<v8::Value> argv[] = { v8String(sourceID), v8String(newContent) };
308
309     v8::TryCatch tryCatch;
310     tryCatch.SetVerbose(false);
311     v8::Handle<v8::Value> result = function->Call(m_debuggerScript.get(), 2, argv);
312     if (tryCatch.HasCaught()) {
313         v8::Local<v8::Message> message = tryCatch.Message();
314         if (!message.IsEmpty())
315             newSourceOrErrorMessage = toWebCoreStringWithNullOrUndefinedCheck(message->Get());
316         return false;
317     }
318     ASSERT(!result.IsEmpty());
319     newSourceOrErrorMessage = toWebCoreStringWithNullOrUndefinedCheck(result);
320
321     // Call stack may have changed after if the edited function was on the stack.
322     if (m_currentCallFrame)
323         m_currentCallFrame.clear();
324     return true;
325 }
326
327 PassRefPtr<JavaScriptCallFrame> ScriptDebugServer::currentCallFrame()
328 {
329     if (!m_currentCallFrame) {
330         v8::Handle<v8::Function> currentCallFrameFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("currentCallFrame")));
331         v8::Handle<v8::Value> argv[] = { m_executionState.get() };
332         v8::Handle<v8::Value> currentCallFrameV8 = currentCallFrameFunction->Call(m_debuggerScript.get(), 1, argv);
333         m_currentCallFrame = JavaScriptCallFrame::create(v8::Debug::GetDebugContext(), v8::Handle<v8::Object>::Cast(currentCallFrameV8));
334     }
335     return m_currentCallFrame;
336 }
337
338 void ScriptDebugServer::setEnabled(bool value)
339 {
340      m_enabled = value;
341 }
342
343 bool ScriptDebugServer::isDebuggerAlwaysEnabled()
344 {
345     return m_enabled;
346 }
347
348 void ScriptDebugServer::interruptAndRun(PassOwnPtr<Task> task)
349 {
350     v8::Debug::DebugBreakForCommand(new ClientDataImpl(task));
351 }
352
353 void ScriptDebugServer::runPendingTasks()
354 {
355     v8::Debug::ProcessDebugMessages();
356 }
357
358 v8::Handle<v8::Value> ScriptDebugServer::breakProgramCallback(const v8::Arguments& args)
359 {
360     ASSERT(2 == args.Length());
361     ScriptDebugServer::shared().breakProgram(v8::Handle<v8::Object>::Cast(args[0]));
362     return v8::Undefined();
363 }
364
365 void ScriptDebugServer::breakProgram(v8::Handle<v8::Object> executionState)
366 {
367     // Don't allow nested breaks.
368     if (m_pausedPage)
369         return;
370
371     Frame* frame = retrieveFrame(m_pausedPageContext);
372     if (!frame)
373         return;
374
375     ScriptDebugListener* listener = m_listenersMap.get(frame->page());
376     if (!listener)
377         return;
378
379     m_executionState.set(executionState);
380     m_pausedPage = frame->page();
381     ScriptState* currentCallFrameState = ScriptState::forContext(m_pausedPageContext);
382     listener->didPause(currentCallFrameState);
383
384     // Wait for continue or step command.
385     m_clientMessageLoop->run(m_pausedPage);
386     ASSERT(!m_pausedPage);
387
388     // The listener may have been removed in the nested loop.
389     if (ScriptDebugListener* listener = m_listenersMap.get(frame->page()))
390         listener->didContinue();
391 }
392
393 void ScriptDebugServer::v8DebugEventCallback(const v8::Debug::EventDetails& eventDetails)
394 {
395     ScriptDebugServer::shared().handleV8DebugEvent(eventDetails);
396 }
397
398 void ScriptDebugServer::handleV8DebugEvent(const v8::Debug::EventDetails& eventDetails)
399 {
400     v8::DebugEvent event = eventDetails.GetEvent();
401
402     if (event == v8::BreakForCommand) {
403         ClientDataImpl* data = static_cast<ClientDataImpl*>(eventDetails.GetClientData());
404         data->task()->run();
405         return;
406     }
407
408     if (event != v8::Break && event != v8::Exception && event != v8::AfterCompile)
409         return;
410
411     v8::Handle<v8::Context> eventContext = eventDetails.GetEventContext();
412     ASSERT(!eventContext.IsEmpty());
413
414     Frame* frame = retrieveFrame(eventContext);
415     if (frame) {
416         ScriptDebugListener* listener = m_listenersMap.get(frame->page());
417         if (listener) {
418             v8::HandleScope scope;
419             if (event == v8::AfterCompile) {
420                 v8::Context::Scope contextScope(v8::Debug::GetDebugContext());
421                 v8::Handle<v8::Function> onAfterCompileFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("getAfterCompileScript")));
422                 v8::Handle<v8::Value> argv[] = { eventDetails.GetEventData() };
423                 v8::Handle<v8::Value> value = onAfterCompileFunction->Call(m_debuggerScript.get(), 1, argv);
424                 ASSERT(value->IsObject());
425                 v8::Handle<v8::Object> object = v8::Handle<v8::Object>::Cast(value);
426                 dispatchDidParseSource(listener, object);
427             } else if (event == v8::Break || event == v8::Exception) {
428                 if (event == v8::Exception) {
429                     v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(1);
430                     // Stack trace is empty in case of syntax error. Silently continue execution in such cases.
431                     if (!stackTrace->GetFrameCount())
432                         return;
433                 }
434
435                 m_pausedPageContext = *eventContext;
436                 breakProgram(eventDetails.GetExecutionState());
437                 m_pausedPageContext.Clear();
438             }
439         }
440     }
441 }
442
443 void ScriptDebugServer::dispatchDidParseSource(ScriptDebugListener* listener, v8::Handle<v8::Object> object)
444 {
445     listener->didParseSource(
446         toWebCoreStringWithNullOrUndefinedCheck(object->Get(v8::String::New("id"))),
447         toWebCoreStringWithNullOrUndefinedCheck(object->Get(v8::String::New("name"))),
448         toWebCoreStringWithNullOrUndefinedCheck(object->Get(v8::String::New("source"))),
449         object->Get(v8::String::New("lineOffset"))->ToInteger()->Value(),
450         static_cast<ScriptWorldType>(object->Get(v8::String::New("scriptWorldType"))->Int32Value()));
451 }
452
453 void ScriptDebugServer::ensureDebuggerScriptCompiled()
454 {
455     if (m_debuggerScript.get().IsEmpty()) {
456         v8::HandleScope scope;
457         v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
458         v8::Context::Scope contextScope(debuggerContext);
459         m_debuggerScript.set(v8::Handle<v8::Object>::Cast(v8::Script::Compile(v8String(m_debuggerScriptSource))->Run()));
460     }
461 }
462
463 void ScriptDebugServer::didResume()
464 {
465     m_currentCallFrame.clear();
466     m_executionState.clear();
467     m_pausedPage = 0;
468 }
469
470 } // namespace WebCore
471
472 #endif // ENABLE(JAVASCRIPT_DEBUGGER)