ScriptDebugServer.cpp revision 2bde8e466a4451c7319e3a072d118917957d6554
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 42namespace WebCore { 43 44namespace { 45 46class ClientDataImpl : public v8::Debug::ClientData { 47public: 48 ClientDataImpl(PassOwnPtr<ScriptDebugServer::Task> task) : m_task(task) { } 49 virtual ~ClientDataImpl() { } 50 ScriptDebugServer::Task* task() const { return m_task.get(); } 51private: 52 OwnPtr<ScriptDebugServer::Task> m_task; 53}; 54 55} 56 57ScriptDebugServer::ScriptDebugServer() 58 : m_pauseOnExceptionsState(DontPauseOnExceptions) 59 , m_breakpointsActivated(true) 60{ 61} 62 63String 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("scriptId"), 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 args->Set(v8::String::New("enabled"), v8::Boolean::New(scriptBreakpoint.enabled)); 75 76 v8::Handle<v8::Function> setBreakpointFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setBreakpoint"))); 77 v8::Handle<v8::Value> breakpointId = v8::Debug::Call(setBreakpointFunction, args); 78 if (!breakpointId->IsString()) 79 return ""; 80 *actualLineNumber = args->Get(v8::String::New("lineNumber"))->Int32Value(); 81 *actualColumnNumber = args->Get(v8::String::New("columnNumber"))->Int32Value(); 82 return v8StringToWebCoreString(breakpointId->ToString()); 83} 84 85void ScriptDebugServer::removeBreakpoint(const String& breakpointId) 86{ 87 v8::HandleScope scope; 88 v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext(); 89 v8::Context::Scope contextScope(debuggerContext); 90 91 v8::Local<v8::Object> args = v8::Object::New(); 92 args->Set(v8::String::New("breakpointId"), v8String(breakpointId)); 93 94 v8::Handle<v8::Function> removeBreakpointFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("removeBreakpoint"))); 95 v8::Debug::Call(removeBreakpointFunction, args); 96} 97 98void ScriptDebugServer::clearBreakpoints() 99{ 100 ensureDebuggerScriptCompiled(); 101 v8::HandleScope scope; 102 v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext(); 103 v8::Context::Scope contextScope(debuggerContext); 104 105 v8::Handle<v8::Function> clearBreakpoints = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("clearBreakpoints"))); 106 v8::Debug::Call(clearBreakpoints); 107} 108 109void ScriptDebugServer::setBreakpointsActivated(bool activated) 110{ 111 ensureDebuggerScriptCompiled(); 112 v8::HandleScope scope; 113 v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext(); 114 v8::Context::Scope contextScope(debuggerContext); 115 116 v8::Local<v8::Object> args = v8::Object::New(); 117 args->Set(v8::String::New("enabled"), v8::Boolean::New(activated)); 118 v8::Handle<v8::Function> setBreakpointsActivated = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setBreakpointsActivated"))); 119 v8::Debug::Call(setBreakpointsActivated, args); 120 121 m_breakpointsActivated = activated; 122} 123 124ScriptDebugServer::PauseOnExceptionsState ScriptDebugServer::pauseOnExceptionsState() 125{ 126 ensureDebuggerScriptCompiled(); 127 v8::HandleScope scope; 128 v8::Context::Scope contextScope(v8::Debug::GetDebugContext()); 129 130 v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("pauseOnExceptionsState"))); 131 v8::Handle<v8::Value> argv[] = { v8::Handle<v8::Value>() }; 132 v8::Handle<v8::Value> result = function->Call(m_debuggerScript.get(), 0, argv); 133 return static_cast<ScriptDebugServer::PauseOnExceptionsState>(result->Int32Value()); 134} 135 136void ScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pauseOnExceptionsState) 137{ 138 ensureDebuggerScriptCompiled(); 139 v8::HandleScope scope; 140 v8::Context::Scope contextScope(v8::Debug::GetDebugContext()); 141 142 v8::Handle<v8::Function> setPauseOnExceptionsFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setPauseOnExceptionsState"))); 143 v8::Handle<v8::Value> argv[] = { v8::Int32::New(pauseOnExceptionsState) }; 144 setPauseOnExceptionsFunction->Call(m_debuggerScript.get(), 1, argv); 145} 146 147void ScriptDebugServer::setPauseOnNextStatement(bool pause) 148{ 149 if (isPaused()) 150 return; 151 if (pause) 152 v8::Debug::DebugBreak(); 153 else 154 v8::Debug::CancelDebugBreak(); 155} 156 157void ScriptDebugServer::breakProgram() 158{ 159 if (!m_breakpointsActivated) 160 return; 161 162 if (!v8::Context::InContext()) 163 return; 164 165 if (m_breakProgramCallbackTemplate.get().IsEmpty()) { 166 m_breakProgramCallbackTemplate.set(v8::FunctionTemplate::New()); 167 m_breakProgramCallbackTemplate.get()->SetCallHandler(&ScriptDebugServer::breakProgramCallback, v8::External::New(this)); 168 } 169 170 v8::Handle<v8::Context> context = v8::Context::GetCurrent(); 171 if (context.IsEmpty()) 172 return; 173 174 m_pausedPageContext = *context; 175 v8::Handle<v8::Function> breakProgramFunction = m_breakProgramCallbackTemplate.get()->GetFunction(); 176 v8::Debug::Call(breakProgramFunction); 177 m_pausedPageContext.Clear(); 178} 179 180void ScriptDebugServer::continueProgram() 181{ 182 if (isPaused()) 183 quitMessageLoopOnPause(); 184 m_currentCallFrame.clear(); 185 m_executionState.clear(); 186} 187 188void ScriptDebugServer::stepIntoStatement() 189{ 190 ASSERT(isPaused()); 191 v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepIntoStatement"))); 192 v8::Handle<v8::Value> argv[] = { m_executionState.get() }; 193 function->Call(m_debuggerScript.get(), 1, argv); 194 continueProgram(); 195} 196 197void ScriptDebugServer::stepOverStatement() 198{ 199 ASSERT(isPaused()); 200 v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepOverStatement"))); 201 v8::Handle<v8::Value> argv[] = { m_executionState.get() }; 202 function->Call(m_debuggerScript.get(), 1, argv); 203 continueProgram(); 204} 205 206void ScriptDebugServer::stepOutOfFunction() 207{ 208 ASSERT(isPaused()); 209 v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepOutOfFunction"))); 210 v8::Handle<v8::Value> argv[] = { m_executionState.get() }; 211 function->Call(m_debuggerScript.get(), 1, argv); 212 continueProgram(); 213} 214 215bool ScriptDebugServer::editScriptSource(const String& sourceID, const String& newContent, String& newSourceOrErrorMessage) 216{ 217 ensureDebuggerScriptCompiled(); 218 v8::HandleScope scope; 219 220 OwnPtr<v8::Context::Scope> contextScope; 221 if (!isPaused()) 222 contextScope.set(new v8::Context::Scope(v8::Debug::GetDebugContext())); 223 224 v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("editScriptSource"))); 225 v8::Handle<v8::Value> argv[] = { v8String(sourceID), v8String(newContent) }; 226 227 v8::TryCatch tryCatch; 228 tryCatch.SetVerbose(false); 229 v8::Handle<v8::Value> result = function->Call(m_debuggerScript.get(), 2, argv); 230 if (tryCatch.HasCaught()) { 231 v8::Local<v8::Message> message = tryCatch.Message(); 232 if (!message.IsEmpty()) 233 newSourceOrErrorMessage = toWebCoreStringWithNullOrUndefinedCheck(message->Get()); 234 return false; 235 } 236 ASSERT(!result.IsEmpty()); 237 newSourceOrErrorMessage = toWebCoreStringWithNullOrUndefinedCheck(result); 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 245PassRefPtr<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 256void ScriptDebugServer::interruptAndRun(PassOwnPtr<Task> task) 257{ 258 v8::Debug::DebugBreakForCommand(new ClientDataImpl(task)); 259} 260 261void ScriptDebugServer::runPendingTasks() 262{ 263 v8::Debug::ProcessDebugMessages(); 264} 265 266static 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 272v8::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 281void 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 298void ScriptDebugServer::v8DebugEventCallback(const v8::Debug::EventDetails& eventDetails) 299{ 300 ScriptDebugServer* thisPtr = toScriptDebugServer(eventDetails.GetCallbackData()); 301 thisPtr->handleV8DebugEvent(eventDetails); 302} 303 304void 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 346void 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 static_cast<ScriptWorldType>(object->Get(v8::String::New("scriptWorldType"))->Int32Value())); 355} 356 357void 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 368bool ScriptDebugServer::isPaused() 369{ 370 return !m_executionState.get().IsEmpty(); 371} 372 373} // namespace WebCore 374 375#endif // ENABLE(JAVASCRIPT_DEBUGGER) 376