Home | History | Annotate | Download | only in v8
      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:
     48     ClientDataImpl(PassOwnPtr<ScriptDebugServer::Task> task) : m_task(task) { }
     49     virtual ~ClientDataImpl() { }
     50     ScriptDebugServer::Task* task() const { return m_task.get(); }
     51 private:
     52     OwnPtr<ScriptDebugServer::Task> m_task;
     53 };
     54 
     55 }
     56 
     57 ScriptDebugServer::ScriptDebugServer()
     58     : m_pauseOnExceptionsState(DontPauseOnExceptions)
     59     , m_breakpointsActivated(true)
     60 {
     61 }
     62 
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    179 void ScriptDebugServer::continueProgram()
    180 {
    181     if (isPaused())
    182         quitMessageLoopOnPause();
    183     m_currentCallFrame.clear();
    184     m_executionState.clear();
    185 }
    186 
    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 
    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 
    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 
    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 
    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 
    256 void ScriptDebugServer::interruptAndRun(PassOwnPtr<Task> task)
    257 {
    258     v8::Debug::DebugBreakForCommand(new ClientDataImpl(task));
    259 }
    260 
    261 void ScriptDebugServer::runPendingTasks()
    262 {
    263     v8::Debug::ProcessDebugMessages();
    264 }
    265 
    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 
    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 
    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 
    298 void ScriptDebugServer::v8DebugEventCallback(const v8::Debug::EventDetails& eventDetails)
    299 {
    300     ScriptDebugServer* thisPtr = toScriptDebugServer(eventDetails.GetCallbackData());
    301     thisPtr->handleV8DebugEvent(eventDetails);
    302 }
    303 
    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 
    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 
    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 
    368 bool ScriptDebugServer::isPaused()
    369 {
    370     return !m_executionState.get().IsEmpty();
    371 }
    372 
    373 } // namespace WebCore
    374 
    375 #endif // ENABLE(JAVASCRIPT_DEBUGGER)
    376