• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2010-2011 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 "DebuggerScriptSource.h"
37 #include "JavaScriptCallFrame.h"
38 #include "ScriptDebugListener.h"
39 #include "V8Binding.h"
40 #include <wtf/StdLibExtras.h>
41 
42 namespace WebCore {
43 
44 namespace {
45 
46 class ClientDataImpl : public v8::Debug::ClientData {
47 public:
ClientDataImpl(PassOwnPtr<ScriptDebugServer::Task> task)48     ClientDataImpl(PassOwnPtr<ScriptDebugServer::Task> task) : m_task(task) { }
~ClientDataImpl()49     virtual ~ClientDataImpl() { }
task() const50     ScriptDebugServer::Task* task() const { return m_task.get(); }
51 private:
52     OwnPtr<ScriptDebugServer::Task> m_task;
53 };
54 
55 }
56 
ScriptDebugServer()57 ScriptDebugServer::ScriptDebugServer()
58     : m_pauseOnExceptionsState(DontPauseOnExceptions)
59     , m_breakpointsActivated(true)
60 {
61 }
62 
setBreakpoint(const String & sourceID,const ScriptBreakpoint & scriptBreakpoint,int * actualLineNumber,int * actualColumnNumber)63 String ScriptDebugServer::setBreakpoint(const String& sourceID, const ScriptBreakpoint& scriptBreakpoint, int* actualLineNumber, int* actualColumnNumber)
64 {
65     v8::HandleScope scope;
66     v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
67     v8::Context::Scope contextScope(debuggerContext);
68 
69     v8::Local<v8::Object> args = v8::Object::New();
70     args->Set(v8::String::New("sourceID"), v8String(sourceID));
71     args->Set(v8::String::New("lineNumber"), v8::Integer::New(scriptBreakpoint.lineNumber));
72     args->Set(v8::String::New("columnNumber"), v8::Integer::New(scriptBreakpoint.columnNumber));
73     args->Set(v8::String::New("condition"), v8String(scriptBreakpoint.condition));
74 
75     v8::Handle<v8::Function> setBreakpointFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setBreakpoint")));
76     v8::Handle<v8::Value> breakpointId = v8::Debug::Call(setBreakpointFunction, args);
77     if (!breakpointId->IsString())
78         return "";
79     *actualLineNumber = args->Get(v8::String::New("lineNumber"))->Int32Value();
80     *actualColumnNumber = args->Get(v8::String::New("columnNumber"))->Int32Value();
81     return v8StringToWebCoreString(breakpointId->ToString());
82 }
83 
removeBreakpoint(const String & breakpointId)84 void ScriptDebugServer::removeBreakpoint(const String& breakpointId)
85 {
86     v8::HandleScope scope;
87     v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
88     v8::Context::Scope contextScope(debuggerContext);
89 
90     v8::Local<v8::Object> args = v8::Object::New();
91     args->Set(v8::String::New("breakpointId"), v8String(breakpointId));
92 
93     v8::Handle<v8::Function> removeBreakpointFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("removeBreakpoint")));
94     v8::Debug::Call(removeBreakpointFunction, args);
95 }
96 
clearBreakpoints()97 void ScriptDebugServer::clearBreakpoints()
98 {
99     ensureDebuggerScriptCompiled();
100     v8::HandleScope scope;
101     v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
102     v8::Context::Scope contextScope(debuggerContext);
103 
104     v8::Handle<v8::Function> clearBreakpoints = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("clearBreakpoints")));
105     v8::Debug::Call(clearBreakpoints);
106 }
107 
setBreakpointsActivated(bool activated)108 void ScriptDebugServer::setBreakpointsActivated(bool activated)
109 {
110     ensureDebuggerScriptCompiled();
111     v8::HandleScope scope;
112     v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
113     v8::Context::Scope contextScope(debuggerContext);
114 
115     v8::Local<v8::Object> args = v8::Object::New();
116     args->Set(v8::String::New("enabled"), v8::Boolean::New(activated));
117     v8::Handle<v8::Function> setBreakpointsActivated = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setBreakpointsActivated")));
118     v8::Debug::Call(setBreakpointsActivated, args);
119 
120     m_breakpointsActivated = activated;
121 }
122 
pauseOnExceptionsState()123 ScriptDebugServer::PauseOnExceptionsState ScriptDebugServer::pauseOnExceptionsState()
124 {
125     ensureDebuggerScriptCompiled();
126     v8::HandleScope scope;
127     v8::Context::Scope contextScope(v8::Debug::GetDebugContext());
128 
129     v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("pauseOnExceptionsState")));
130     v8::Handle<v8::Value> argv[] = { v8::Handle<v8::Value>() };
131     v8::Handle<v8::Value> result = function->Call(m_debuggerScript.get(), 0, argv);
132     return static_cast<ScriptDebugServer::PauseOnExceptionsState>(result->Int32Value());
133 }
134 
setPauseOnExceptionsState(PauseOnExceptionsState pauseOnExceptionsState)135 void ScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pauseOnExceptionsState)
136 {
137     ensureDebuggerScriptCompiled();
138     v8::HandleScope scope;
139     v8::Context::Scope contextScope(v8::Debug::GetDebugContext());
140 
141     v8::Handle<v8::Function> setPauseOnExceptionsFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setPauseOnExceptionsState")));
142     v8::Handle<v8::Value> argv[] = { v8::Int32::New(pauseOnExceptionsState) };
143     setPauseOnExceptionsFunction->Call(m_debuggerScript.get(), 1, argv);
144 }
145 
setPauseOnNextStatement(bool pause)146 void ScriptDebugServer::setPauseOnNextStatement(bool pause)
147 {
148     if (isPaused())
149         return;
150     if (pause)
151         v8::Debug::DebugBreak();
152     else
153         v8::Debug::CancelDebugBreak();
154 }
155 
breakProgram()156 void ScriptDebugServer::breakProgram()
157 {
158     if (!m_breakpointsActivated)
159         return;
160 
161     if (!v8::Context::InContext())
162         return;
163 
164     if (m_breakProgramCallbackTemplate.get().IsEmpty()) {
165         m_breakProgramCallbackTemplate.set(v8::FunctionTemplate::New());
166         m_breakProgramCallbackTemplate.get()->SetCallHandler(&ScriptDebugServer::breakProgramCallback, v8::External::New(this));
167     }
168 
169     v8::Handle<v8::Context> context = v8::Context::GetCurrent();
170     if (context.IsEmpty())
171         return;
172 
173     m_pausedPageContext = *context;
174     v8::Handle<v8::Function> breakProgramFunction = m_breakProgramCallbackTemplate.get()->GetFunction();
175     v8::Debug::Call(breakProgramFunction);
176     m_pausedPageContext.Clear();
177 }
178 
continueProgram()179 void ScriptDebugServer::continueProgram()
180 {
181     if (isPaused())
182         quitMessageLoopOnPause();
183     m_currentCallFrame.clear();
184     m_executionState.clear();
185 }
186 
stepIntoStatement()187 void ScriptDebugServer::stepIntoStatement()
188 {
189     ASSERT(isPaused());
190     v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepIntoStatement")));
191     v8::Handle<v8::Value> argv[] = { m_executionState.get() };
192     function->Call(m_debuggerScript.get(), 1, argv);
193     continueProgram();
194 }
195 
stepOverStatement()196 void ScriptDebugServer::stepOverStatement()
197 {
198     ASSERT(isPaused());
199     v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepOverStatement")));
200     v8::Handle<v8::Value> argv[] = { m_executionState.get() };
201     function->Call(m_debuggerScript.get(), 1, argv);
202     continueProgram();
203 }
204 
stepOutOfFunction()205 void ScriptDebugServer::stepOutOfFunction()
206 {
207     ASSERT(isPaused());
208     v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepOutOfFunction")));
209     v8::Handle<v8::Value> argv[] = { m_executionState.get() };
210     function->Call(m_debuggerScript.get(), 1, argv);
211     continueProgram();
212 }
213 
editScriptSource(const String & sourceID,const String & newContent,String * error)214 bool ScriptDebugServer::editScriptSource(const String& sourceID, const String& newContent, String* error)
215 {
216     ensureDebuggerScriptCompiled();
217     v8::HandleScope scope;
218 
219     OwnPtr<v8::Context::Scope> contextScope;
220     if (!isPaused())
221         contextScope.set(new v8::Context::Scope(v8::Debug::GetDebugContext()));
222 
223     v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("editScriptSource")));
224     v8::Handle<v8::Value> argv[] = { v8String(sourceID), v8String(newContent) };
225 
226     v8::TryCatch tryCatch;
227     tryCatch.SetVerbose(false);
228     v8::Handle<v8::Value> result = function->Call(m_debuggerScript.get(), 2, argv);
229     if (tryCatch.HasCaught()) {
230         v8::Local<v8::Message> message = tryCatch.Message();
231         if (!message.IsEmpty())
232             *error = toWebCoreStringWithNullOrUndefinedCheck(message->Get());
233         else
234             *error = "Unknown error.";
235         return false;
236     }
237     ASSERT(!result.IsEmpty());
238 
239     // Call stack may have changed after if the edited function was on the stack.
240     if (m_currentCallFrame)
241         m_currentCallFrame.clear();
242     return true;
243 }
244 
currentCallFrame()245 PassRefPtr<JavaScriptCallFrame> ScriptDebugServer::currentCallFrame()
246 {
247     if (!m_currentCallFrame) {
248         v8::Handle<v8::Function> currentCallFrameFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("currentCallFrame")));
249         v8::Handle<v8::Value> argv[] = { m_executionState.get() };
250         v8::Handle<v8::Value> currentCallFrameV8 = currentCallFrameFunction->Call(m_debuggerScript.get(), 1, argv);
251         m_currentCallFrame = JavaScriptCallFrame::create(v8::Debug::GetDebugContext(), v8::Handle<v8::Object>::Cast(currentCallFrameV8));
252     }
253     return m_currentCallFrame;
254 }
255 
interruptAndRun(PassOwnPtr<Task> task)256 void ScriptDebugServer::interruptAndRun(PassOwnPtr<Task> task)
257 {
258     v8::Debug::DebugBreakForCommand(new ClientDataImpl(task));
259 }
260 
runPendingTasks()261 void ScriptDebugServer::runPendingTasks()
262 {
263     v8::Debug::ProcessDebugMessages();
264 }
265 
toScriptDebugServer(v8::Handle<v8::Value> data)266 static ScriptDebugServer* toScriptDebugServer(v8::Handle<v8::Value> data)
267 {
268     void* p = v8::Handle<v8::External>::Cast(data)->Value();
269     return static_cast<ScriptDebugServer*>(p);
270 }
271 
breakProgramCallback(const v8::Arguments & args)272 v8::Handle<v8::Value> ScriptDebugServer::breakProgramCallback(const v8::Arguments& args)
273 {
274     ASSERT(2 == args.Length());
275 
276     ScriptDebugServer* thisPtr = toScriptDebugServer(args.Data());
277     thisPtr->breakProgram(v8::Handle<v8::Object>::Cast(args[0]));
278     return v8::Undefined();
279 }
280 
breakProgram(v8::Handle<v8::Object> executionState)281 void ScriptDebugServer::breakProgram(v8::Handle<v8::Object> executionState)
282 {
283     // Don't allow nested breaks.
284     if (isPaused())
285         return;
286 
287     ScriptDebugListener* listener = getDebugListenerForContext(m_pausedPageContext);
288     if (!listener)
289         return;
290 
291     m_executionState.set(executionState);
292     ScriptState* currentCallFrameState = ScriptState::forContext(m_pausedPageContext);
293     listener->didPause(currentCallFrameState);
294 
295     runMessageLoopOnPause(m_pausedPageContext);
296 }
297 
v8DebugEventCallback(const v8::Debug::EventDetails & eventDetails)298 void ScriptDebugServer::v8DebugEventCallback(const v8::Debug::EventDetails& eventDetails)
299 {
300     ScriptDebugServer* thisPtr = toScriptDebugServer(eventDetails.GetCallbackData());
301     thisPtr->handleV8DebugEvent(eventDetails);
302 }
303 
handleV8DebugEvent(const v8::Debug::EventDetails & eventDetails)304 void ScriptDebugServer::handleV8DebugEvent(const v8::Debug::EventDetails& eventDetails)
305 {
306     v8::DebugEvent event = eventDetails.GetEvent();
307 
308     if (event == v8::BreakForCommand) {
309         ClientDataImpl* data = static_cast<ClientDataImpl*>(eventDetails.GetClientData());
310         data->task()->run();
311         return;
312     }
313 
314     if (event != v8::Break && event != v8::Exception && event != v8::AfterCompile)
315         return;
316 
317     v8::Handle<v8::Context> eventContext = eventDetails.GetEventContext();
318     ASSERT(!eventContext.IsEmpty());
319 
320     ScriptDebugListener* listener = getDebugListenerForContext(eventContext);
321     if (listener) {
322         v8::HandleScope scope;
323         if (event == v8::AfterCompile) {
324             v8::Context::Scope contextScope(v8::Debug::GetDebugContext());
325             v8::Handle<v8::Function> onAfterCompileFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("getAfterCompileScript")));
326             v8::Handle<v8::Value> argv[] = { eventDetails.GetEventData() };
327             v8::Handle<v8::Value> value = onAfterCompileFunction->Call(m_debuggerScript.get(), 1, argv);
328             ASSERT(value->IsObject());
329             v8::Handle<v8::Object> object = v8::Handle<v8::Object>::Cast(value);
330             dispatchDidParseSource(listener, object);
331         } else if (event == v8::Break || event == v8::Exception) {
332             if (event == v8::Exception) {
333                 v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(1);
334                 // Stack trace is empty in case of syntax error. Silently continue execution in such cases.
335                 if (!stackTrace->GetFrameCount())
336                     return;
337             }
338 
339             m_pausedPageContext = *eventContext;
340             breakProgram(eventDetails.GetExecutionState());
341             m_pausedPageContext.Clear();
342         }
343     }
344 }
345 
dispatchDidParseSource(ScriptDebugListener * listener,v8::Handle<v8::Object> object)346 void ScriptDebugServer::dispatchDidParseSource(ScriptDebugListener* listener, v8::Handle<v8::Object> object)
347 {
348     listener->didParseSource(
349         toWebCoreStringWithNullOrUndefinedCheck(object->Get(v8::String::New("id"))),
350         toWebCoreStringWithNullOrUndefinedCheck(object->Get(v8::String::New("name"))),
351         toWebCoreStringWithNullOrUndefinedCheck(object->Get(v8::String::New("source"))),
352         object->Get(v8::String::New("lineOffset"))->ToInteger()->Value(),
353         object->Get(v8::String::New("columnOffset"))->ToInteger()->Value(),
354         object->Get(v8::String::New("isContentScript"))->ToBoolean()->Value());
355 }
356 
ensureDebuggerScriptCompiled()357 void ScriptDebugServer::ensureDebuggerScriptCompiled()
358 {
359     if (m_debuggerScript.get().IsEmpty()) {
360         v8::HandleScope scope;
361         v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
362         v8::Context::Scope contextScope(debuggerContext);
363         String debuggerScriptSource(reinterpret_cast<const char*>(DebuggerScriptSource_js), sizeof(DebuggerScriptSource_js));
364         m_debuggerScript.set(v8::Handle<v8::Object>::Cast(v8::Script::Compile(v8String(debuggerScriptSource))->Run()));
365     }
366 }
367 
isPaused()368 bool ScriptDebugServer::isPaused()
369 {
370     return !m_executionState.get().IsEmpty();
371 }
372 
373 } // namespace WebCore
374 
375 #endif // ENABLE(JAVASCRIPT_DEBUGGER)
376