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