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