ScriptDebugServer.cpp revision 65f03d4f644ce73618e5f4f50dd694b26f55ae12
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
45namespace WebCore {
46
47namespace {
48
49class ClientDataImpl : public v8::Debug::ClientData {
50public:
51    ClientDataImpl(PassOwnPtr<ScriptDebugServer::Task> task) : m_task(task) { }
52    virtual ~ClientDataImpl() { }
53    ScriptDebugServer::Task* task() const { return m_task.get(); }
54private:
55    OwnPtr<ScriptDebugServer::Task> m_task;
56};
57
58}
59
60static 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
77ScriptDebugServer& ScriptDebugServer::shared()
78{
79    DEFINE_STATIC_LOCAL(ScriptDebugServer, server, ());
80    return server;
81}
82
83ScriptDebugServer::ScriptDebugServer()
84    : m_pauseOnExceptionsState(DontPauseOnExceptions)
85    , m_pausedPage(0)
86    , m_enabled(true)
87    , m_breakpointsActivated(true)
88{
89}
90
91void ScriptDebugServer::setDebuggerScriptSource(const String& scriptSource)
92{
93    m_debuggerScriptSource = scriptSource;
94}
95
96void 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
131void 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
146String 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
166void 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
179void 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
190void 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
205ScriptDebugServer::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
217void 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
228void 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
238void 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
263void ScriptDebugServer::continueProgram()
264{
265    if (m_pausedPage)
266        m_clientMessageLoop->quitNow();
267    didResume();
268}
269
270void 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
279void 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
288void 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
297bool 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
327PassRefPtr<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
338void ScriptDebugServer::setEnabled(bool value)
339{
340     m_enabled = value;
341}
342
343bool ScriptDebugServer::isDebuggerAlwaysEnabled()
344{
345    return m_enabled;
346}
347
348void ScriptDebugServer::interruptAndRun(PassOwnPtr<Task> task)
349{
350    v8::Debug::DebugBreakForCommand(new ClientDataImpl(task));
351}
352
353void ScriptDebugServer::runPendingTasks()
354{
355    v8::Debug::ProcessDebugMessages();
356}
357
358v8::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
365void 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
393void ScriptDebugServer::v8DebugEventCallback(const v8::Debug::EventDetails& eventDetails)
394{
395    ScriptDebugServer::shared().handleV8DebugEvent(eventDetails);
396}
397
398void 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
443void 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        object->Get(v8::String::New("columnOffset"))->ToInteger()->Value(),
451        static_cast<ScriptWorldType>(object->Get(v8::String::New("scriptWorldType"))->Int32Value()));
452}
453
454void ScriptDebugServer::ensureDebuggerScriptCompiled()
455{
456    if (m_debuggerScript.get().IsEmpty()) {
457        v8::HandleScope scope;
458        v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
459        v8::Context::Scope contextScope(debuggerContext);
460        m_debuggerScript.set(v8::Handle<v8::Object>::Cast(v8::Script::Compile(v8String(m_debuggerScriptSource))->Run()));
461    }
462}
463
464void ScriptDebugServer::didResume()
465{
466    m_currentCallFrame.clear();
467    m_executionState.clear();
468    m_pausedPage = 0;
469}
470
471} // namespace WebCore
472
473#endif // ENABLE(JAVASCRIPT_DEBUGGER)
474