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("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 84void 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 97void 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 108void 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 123ScriptDebugServer::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 135void 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 146void 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 156void 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 179void ScriptDebugServer::continueProgram() 180{ 181 if (isPaused()) 182 quitMessageLoopOnPause(); 183 m_currentCallFrame.clear(); 184 m_executionState.clear(); 185} 186 187void 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 196void 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 205void 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 214bool 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 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 object->Get(v8::String::New("isContentScript"))->ToBoolean()->Value()); 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