WebCore/bindings/v8/ScriptDebugServer.cpp
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     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 
       
    45 namespace WebCore {
       
    46 
       
    47 static Frame* retrieveFrame(v8::Handle<v8::Context> context)
       
    48 {
       
    49     if (context.IsEmpty())
       
    50         return 0;
       
    51 
       
    52     // Test that context has associated global dom window object.
       
    53     v8::Handle<v8::Object> global = context->Global();
       
    54     if (global.IsEmpty())
       
    55         return 0;
       
    56 
       
    57     global = V8DOMWrapper::lookupDOMWrapper(V8DOMWindow::GetTemplate(), global);
       
    58     if (global.IsEmpty())
       
    59         return 0;
       
    60 
       
    61     return V8Proxy::retrieveFrame(context);
       
    62 }
       
    63 
       
    64 ScriptDebugServer& ScriptDebugServer::shared()
       
    65 {
       
    66     DEFINE_STATIC_LOCAL(ScriptDebugServer, server, ());
       
    67     return server;
       
    68 }
       
    69 
       
    70 ScriptDebugServer::ScriptDebugServer()
       
    71     : m_pauseOnExceptionsState(DontPauseOnExceptions)
       
    72     , m_pausedPage(0)
       
    73     , m_enabled(true)
       
    74 {
       
    75 }
       
    76 
       
    77 void ScriptDebugServer::setDebuggerScriptSource(const String& scriptSource)
       
    78 {
       
    79     m_debuggerScriptSource = scriptSource;
       
    80 }
       
    81 
       
    82 void ScriptDebugServer::addListener(ScriptDebugListener* listener, Page* page)
       
    83 {
       
    84     if (!m_enabled)
       
    85         return;
       
    86 
       
    87     V8Proxy* proxy = V8Proxy::retrieve(page->mainFrame());
       
    88     if (!proxy)
       
    89         return;
       
    90 
       
    91     v8::HandleScope scope;
       
    92     v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
       
    93     v8::Context::Scope contextScope(debuggerContext);
       
    94 
       
    95     if (!m_listenersMap.size()) {
       
    96         ensureDebuggerScriptCompiled();
       
    97         ASSERT(!m_debuggerScript.get()->IsUndefined());
       
    98         v8::Debug::SetDebugEventListener2(&ScriptDebugServer::v8DebugEventCallback);
       
    99     }
       
   100     m_listenersMap.set(page, listener);
       
   101 
       
   102     v8::Local<v8::Context> context = proxy->mainWorldContext();
       
   103 
       
   104     v8::Handle<v8::Function> getScriptsFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("getScripts")));
       
   105     v8::Handle<v8::Value> argv[] = { context->GetData() };
       
   106     v8::Handle<v8::Value> value = getScriptsFunction->Call(m_debuggerScript.get(), 1, argv);
       
   107     if (value.IsEmpty())
       
   108         return;
       
   109     ASSERT(!value->IsUndefined() && value->IsArray());
       
   110     v8::Handle<v8::Array> scriptsArray = v8::Handle<v8::Array>::Cast(value);
       
   111     for (unsigned i = 0; i < scriptsArray->Length(); ++i)
       
   112         dispatchDidParseSource(listener, v8::Handle<v8::Object>::Cast(scriptsArray->Get(v8::Integer::New(i))));
       
   113 }
       
   114 
       
   115 void ScriptDebugServer::removeListener(ScriptDebugListener* listener, Page* page)
       
   116 {
       
   117     if (!m_listenersMap.contains(page))
       
   118         return;
       
   119 
       
   120     if (m_pausedPage == page)
       
   121         continueProgram();
       
   122 
       
   123     m_listenersMap.remove(page);
       
   124 
       
   125     if (m_listenersMap.isEmpty())
       
   126         v8::Debug::SetDebugEventListener(0);
       
   127     // FIXME: Remove all breakpoints set by the agent.
       
   128 }
       
   129 
       
   130 bool ScriptDebugServer::setBreakpoint(const String& sourceID, ScriptBreakpoint breakpoint, unsigned lineNumber, unsigned* actualLineNumber)
       
   131 {
       
   132     v8::HandleScope scope;
       
   133     v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
       
   134     v8::Context::Scope contextScope(debuggerContext);
       
   135 
       
   136     v8::Local<v8::Object> args = v8::Object::New();
       
   137     args->Set(v8::String::New("scriptId"), v8String(sourceID));
       
   138     args->Set(v8::String::New("lineNumber"), v8::Integer::New(lineNumber));
       
   139     args->Set(v8::String::New("condition"), v8String(breakpoint.condition));
       
   140     args->Set(v8::String::New("enabled"), v8::Boolean::New(breakpoint.enabled));
       
   141 
       
   142     v8::Handle<v8::Function> setBreakpointFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setBreakpoint")));
       
   143     v8::Handle<v8::Value> result = v8::Debug::Call(setBreakpointFunction, args);
       
   144     if (!result->IsNumber())
       
   145         return false;
       
   146     ASSERT(result->Int32Value() >= 0);
       
   147     *actualLineNumber = result->Int32Value();
       
   148     return true;
       
   149 }
       
   150 
       
   151 void ScriptDebugServer::removeBreakpoint(const String& sourceID, unsigned lineNumber)
       
   152 {
       
   153     v8::HandleScope scope;
       
   154     v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
       
   155     v8::Context::Scope contextScope(debuggerContext);
       
   156 
       
   157     v8::Local<v8::Object> args = v8::Object::New();
       
   158     args->Set(v8::String::New("scriptId"), v8String(sourceID));
       
   159     args->Set(v8::String::New("lineNumber"), v8::Integer::New(lineNumber));
       
   160 
       
   161     v8::Handle<v8::Function> removeBreakpointFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("removeBreakpoint")));
       
   162     v8::Debug::Call(removeBreakpointFunction, args);
       
   163 }
       
   164 
       
   165 void ScriptDebugServer::clearBreakpoints()
       
   166 {
       
   167     ensureDebuggerScriptCompiled();
       
   168     v8::HandleScope scope;
       
   169     v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
       
   170     v8::Context::Scope contextScope(debuggerContext);
       
   171 
       
   172     v8::Handle<v8::Function> clearBreakpoints = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("clearBreakpoints")));
       
   173     v8::Debug::Call(clearBreakpoints);
       
   174 }
       
   175 
       
   176 void ScriptDebugServer::setBreakpointsActivated(bool enabled)
       
   177 {
       
   178     ensureDebuggerScriptCompiled();
       
   179     v8::HandleScope scope;
       
   180     v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
       
   181     v8::Context::Scope contextScope(debuggerContext);
       
   182 
       
   183     v8::Local<v8::Object> args = v8::Object::New();
       
   184     args->Set(v8::String::New("enabled"), v8::Boolean::New(enabled));
       
   185     v8::Handle<v8::Function> setBreakpointsActivated = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setBreakpointsActivated")));
       
   186     v8::Debug::Call(setBreakpointsActivated, args);
       
   187 }
       
   188 
       
   189 ScriptDebugServer::PauseOnExceptionsState ScriptDebugServer::pauseOnExceptionsState()
       
   190 {
       
   191     ensureDebuggerScriptCompiled();
       
   192     v8::HandleScope scope;
       
   193     v8::Context::Scope contextScope(v8::Debug::GetDebugContext());
       
   194 
       
   195     v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("pauseOnExceptionsState")));
       
   196     v8::Handle<v8::Value> argv[] = { v8::Handle<v8::Value>() };
       
   197     v8::Handle<v8::Value> result = function->Call(m_debuggerScript.get(), 0, argv);
       
   198     return static_cast<ScriptDebugServer::PauseOnExceptionsState>(result->Int32Value());
       
   199 }
       
   200 
       
   201 void ScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pauseOnExceptionsState)
       
   202 {
       
   203     ensureDebuggerScriptCompiled();
       
   204     v8::HandleScope scope;
       
   205     v8::Context::Scope contextScope(v8::Debug::GetDebugContext());
       
   206 
       
   207     v8::Handle<v8::Function> setPauseOnExceptionsFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setPauseOnExceptionsState")));
       
   208     v8::Handle<v8::Value> argv[] = { v8::Int32::New(pauseOnExceptionsState) };
       
   209     setPauseOnExceptionsFunction->Call(m_debuggerScript.get(), 1, argv);
       
   210 }
       
   211 
       
   212 void ScriptDebugServer::continueProgram()
       
   213 {
       
   214     if (m_pausedPage)
       
   215         m_clientMessageLoop->quitNow();
       
   216     didResume();
       
   217 }
       
   218 
       
   219 void ScriptDebugServer::stepIntoStatement()
       
   220 {
       
   221     ASSERT(m_pausedPage);
       
   222     v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepIntoStatement")));
       
   223     v8::Handle<v8::Value> argv[] = { m_executionState.get() };
       
   224     function->Call(m_debuggerScript.get(), 1, argv);
       
   225     continueProgram();
       
   226 }
       
   227 
       
   228 void ScriptDebugServer::stepOverStatement()
       
   229 {
       
   230     ASSERT(m_pausedPage);
       
   231     v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepOverStatement")));
       
   232     v8::Handle<v8::Value> argv[] = { m_executionState.get() };
       
   233     function->Call(m_debuggerScript.get(), 1, argv);
       
   234     continueProgram();
       
   235 }
       
   236 
       
   237 void ScriptDebugServer::stepOutOfFunction()
       
   238 {
       
   239     ASSERT(m_pausedPage);
       
   240     v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepOutOfFunction")));
       
   241     v8::Handle<v8::Value> argv[] = { m_executionState.get() };
       
   242     function->Call(m_debuggerScript.get(), 1, argv);
       
   243     continueProgram();
       
   244 }
       
   245 
       
   246 bool ScriptDebugServer::editScriptSource(const String& sourceID, const String& newContent, String& newSourceOrErrorMessage)
       
   247 {
       
   248     ensureDebuggerScriptCompiled();
       
   249     v8::HandleScope scope;
       
   250 
       
   251     OwnPtr<v8::Context::Scope> contextScope;
       
   252     if (!m_pausedPage)
       
   253         contextScope.set(new v8::Context::Scope(v8::Debug::GetDebugContext()));
       
   254 
       
   255     v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("editScriptSource")));
       
   256     v8::Handle<v8::Value> argv[] = { v8String(sourceID), v8String(newContent) };
       
   257 
       
   258     v8::TryCatch tryCatch;
       
   259     tryCatch.SetVerbose(false);
       
   260     v8::Handle<v8::Value> result = function->Call(m_debuggerScript.get(), 2, argv);
       
   261     if (tryCatch.HasCaught()) {
       
   262         v8::Local<v8::Message> message = tryCatch.Message();
       
   263         if (!message.IsEmpty())
       
   264             newSourceOrErrorMessage = toWebCoreStringWithNullOrUndefinedCheck(message->Get());
       
   265         return false;
       
   266     }
       
   267     ASSERT(!result.IsEmpty());
       
   268     newSourceOrErrorMessage = toWebCoreStringWithNullOrUndefinedCheck(result);
       
   269 
       
   270     // Call stack may have changed after if the edited function was on the stack.
       
   271     if (m_currentCallFrame)
       
   272         m_currentCallFrame.clear();
       
   273     return true;
       
   274 }
       
   275 
       
   276 PassRefPtr<JavaScriptCallFrame> ScriptDebugServer::currentCallFrame()
       
   277 {
       
   278     if (!m_currentCallFrame) {
       
   279         v8::Handle<v8::Function> currentCallFrameFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("currentCallFrame")));
       
   280         v8::Handle<v8::Value> argv[] = { m_executionState.get() };
       
   281         v8::Handle<v8::Value> currentCallFrameV8 = currentCallFrameFunction->Call(m_debuggerScript.get(), 1, argv);
       
   282         m_currentCallFrame = JavaScriptCallFrame::create(v8::Debug::GetDebugContext(), v8::Handle<v8::Object>::Cast(currentCallFrameV8));
       
   283     }
       
   284     return m_currentCallFrame;
       
   285 }
       
   286 
       
   287 void ScriptDebugServer::setEnabled(bool value)
       
   288 {
       
   289      m_enabled = value;
       
   290 }
       
   291 
       
   292 bool ScriptDebugServer::isDebuggerAlwaysEnabled()
       
   293 {
       
   294     return m_enabled;
       
   295 }
       
   296 
       
   297 void ScriptDebugServer::v8DebugEventCallback(const v8::Debug::EventDetails& eventDetails)
       
   298 {
       
   299     ScriptDebugServer::shared().handleV8DebugEvent(eventDetails);
       
   300 }
       
   301 
       
   302 void ScriptDebugServer::handleV8DebugEvent(const v8::Debug::EventDetails& eventDetails)
       
   303 {
       
   304     v8::DebugEvent event = eventDetails.GetEvent();
       
   305     if (event != v8::Break && event != v8::Exception && event != v8::AfterCompile)
       
   306         return;
       
   307 
       
   308     v8::Handle<v8::Context> eventContext = eventDetails.GetEventContext();
       
   309     ASSERT(!eventContext.IsEmpty());
       
   310 
       
   311     Frame* frame = retrieveFrame(eventContext);
       
   312     if (frame) {
       
   313         ScriptDebugListener* listener = m_listenersMap.get(frame->page());
       
   314         if (listener) {
       
   315             v8::HandleScope scope;
       
   316             if (event == v8::AfterCompile) {
       
   317                 v8::Context::Scope contextScope(v8::Debug::GetDebugContext());
       
   318                 v8::Handle<v8::Function> onAfterCompileFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("getAfterCompileScript")));
       
   319                 v8::Handle<v8::Value> argv[] = { eventDetails.GetEventData() };
       
   320                 v8::Handle<v8::Value> value = onAfterCompileFunction->Call(m_debuggerScript.get(), 1, argv);
       
   321                 ASSERT(value->IsObject());
       
   322                 v8::Handle<v8::Object> object = v8::Handle<v8::Object>::Cast(value);
       
   323                 dispatchDidParseSource(listener, object);
       
   324             } else if (event == v8::Break || event == v8::Exception) {
       
   325                 if (event == v8::Exception) {
       
   326                     v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(1);
       
   327                     // Stack trace is empty in case of syntax error. Silently continue execution in such cases.
       
   328                     if (!stackTrace->GetFrameCount())
       
   329                         return;
       
   330                 }
       
   331 
       
   332                 // Don't allow nested breaks.
       
   333                 if (m_pausedPage)
       
   334                     return;
       
   335                 m_executionState.set(eventDetails.GetExecutionState());
       
   336                 m_pausedPage = frame->page();
       
   337                 ScriptState* currentCallFrameState = mainWorldScriptState(frame);
       
   338                 listener->didPause(currentCallFrameState);
       
   339 
       
   340                 // Wait for continue or step command.
       
   341                 m_clientMessageLoop->run(m_pausedPage);
       
   342                 ASSERT(!m_pausedPage);
       
   343 
       
   344                 // The listener may have been removed in the nested loop.
       
   345                 if (ScriptDebugListener* listener = m_listenersMap.get(frame->page()))
       
   346                     listener->didContinue();
       
   347             }
       
   348         }
       
   349     }
       
   350 }
       
   351 
       
   352 void ScriptDebugServer::dispatchDidParseSource(ScriptDebugListener* listener, v8::Handle<v8::Object> object)
       
   353 {
       
   354     listener->didParseSource(
       
   355         toWebCoreStringWithNullOrUndefinedCheck(object->Get(v8::String::New("id"))),
       
   356         toWebCoreStringWithNullOrUndefinedCheck(object->Get(v8::String::New("name"))),
       
   357         toWebCoreStringWithNullOrUndefinedCheck(object->Get(v8::String::New("source"))),
       
   358         object->Get(v8::String::New("lineOffset"))->ToInteger()->Value(),
       
   359         static_cast<ScriptWorldType>(object->Get(v8::String::New("scriptWorldType"))->Int32Value()));
       
   360 }
       
   361 
       
   362 void ScriptDebugServer::ensureDebuggerScriptCompiled()
       
   363 {
       
   364     if (m_debuggerScript.get().IsEmpty()) {
       
   365         v8::HandleScope scope;
       
   366         v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
       
   367         v8::Context::Scope contextScope(debuggerContext);
       
   368         m_debuggerScript.set(v8::Handle<v8::Object>::Cast(v8::Script::Compile(v8String(m_debuggerScriptSource))->Run()));
       
   369     }
       
   370 }
       
   371 
       
   372 void ScriptDebugServer::didResume()
       
   373 {
       
   374     m_currentCallFrame.clear();
       
   375     m_executionState.clear();
       
   376     m_pausedPage = 0;
       
   377 }
       
   378 
       
   379 } // namespace WebCore
       
   380 
       
   381 #endif // ENABLE(JAVASCRIPT_DEBUGGER)