WebCore/bindings/v8/ScriptDebugServer.cpp
changeset 0 4f2f89ce4247
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/bindings/v8/ScriptDebugServer.cpp	Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2010, Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "ScriptDebugServer.h"
+
+#if ENABLE(JAVASCRIPT_DEBUGGER)
+
+#include "Frame.h"
+#include "JavaScriptCallFrame.h"
+#include "Page.h"
+#include "ScriptDebugListener.h"
+#include "V8Binding.h"
+#include "V8DOMWindow.h"
+#include "V8Proxy.h"
+#include <wtf/StdLibExtras.h>
+
+namespace WebCore {
+
+static Frame* retrieveFrame(v8::Handle<v8::Context> context)
+{
+    if (context.IsEmpty())
+        return 0;
+
+    // Test that context has associated global dom window object.
+    v8::Handle<v8::Object> global = context->Global();
+    if (global.IsEmpty())
+        return 0;
+
+    global = V8DOMWrapper::lookupDOMWrapper(V8DOMWindow::GetTemplate(), global);
+    if (global.IsEmpty())
+        return 0;
+
+    return V8Proxy::retrieveFrame(context);
+}
+
+ScriptDebugServer& ScriptDebugServer::shared()
+{
+    DEFINE_STATIC_LOCAL(ScriptDebugServer, server, ());
+    return server;
+}
+
+ScriptDebugServer::ScriptDebugServer()
+    : m_pauseOnExceptionsState(DontPauseOnExceptions)
+    , m_pausedPage(0)
+    , m_enabled(true)
+{
+}
+
+void ScriptDebugServer::setDebuggerScriptSource(const String& scriptSource)
+{
+    m_debuggerScriptSource = scriptSource;
+}
+
+void ScriptDebugServer::addListener(ScriptDebugListener* listener, Page* page)
+{
+    if (!m_enabled)
+        return;
+
+    V8Proxy* proxy = V8Proxy::retrieve(page->mainFrame());
+    if (!proxy)
+        return;
+
+    v8::HandleScope scope;
+    v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
+    v8::Context::Scope contextScope(debuggerContext);
+
+    if (!m_listenersMap.size()) {
+        ensureDebuggerScriptCompiled();
+        ASSERT(!m_debuggerScript.get()->IsUndefined());
+        v8::Debug::SetDebugEventListener2(&ScriptDebugServer::v8DebugEventCallback);
+    }
+    m_listenersMap.set(page, listener);
+
+    v8::Local<v8::Context> context = proxy->mainWorldContext();
+
+    v8::Handle<v8::Function> getScriptsFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("getScripts")));
+    v8::Handle<v8::Value> argv[] = { context->GetData() };
+    v8::Handle<v8::Value> value = getScriptsFunction->Call(m_debuggerScript.get(), 1, argv);
+    if (value.IsEmpty())
+        return;
+    ASSERT(!value->IsUndefined() && value->IsArray());
+    v8::Handle<v8::Array> scriptsArray = v8::Handle<v8::Array>::Cast(value);
+    for (unsigned i = 0; i < scriptsArray->Length(); ++i)
+        dispatchDidParseSource(listener, v8::Handle<v8::Object>::Cast(scriptsArray->Get(v8::Integer::New(i))));
+}
+
+void ScriptDebugServer::removeListener(ScriptDebugListener* listener, Page* page)
+{
+    if (!m_listenersMap.contains(page))
+        return;
+
+    if (m_pausedPage == page)
+        continueProgram();
+
+    m_listenersMap.remove(page);
+
+    if (m_listenersMap.isEmpty())
+        v8::Debug::SetDebugEventListener(0);
+    // FIXME: Remove all breakpoints set by the agent.
+}
+
+bool ScriptDebugServer::setBreakpoint(const String& sourceID, ScriptBreakpoint breakpoint, unsigned lineNumber, unsigned* actualLineNumber)
+{
+    v8::HandleScope scope;
+    v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
+    v8::Context::Scope contextScope(debuggerContext);
+
+    v8::Local<v8::Object> args = v8::Object::New();
+    args->Set(v8::String::New("scriptId"), v8String(sourceID));
+    args->Set(v8::String::New("lineNumber"), v8::Integer::New(lineNumber));
+    args->Set(v8::String::New("condition"), v8String(breakpoint.condition));
+    args->Set(v8::String::New("enabled"), v8::Boolean::New(breakpoint.enabled));
+
+    v8::Handle<v8::Function> setBreakpointFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setBreakpoint")));
+    v8::Handle<v8::Value> result = v8::Debug::Call(setBreakpointFunction, args);
+    if (!result->IsNumber())
+        return false;
+    ASSERT(result->Int32Value() >= 0);
+    *actualLineNumber = result->Int32Value();
+    return true;
+}
+
+void ScriptDebugServer::removeBreakpoint(const String& sourceID, unsigned lineNumber)
+{
+    v8::HandleScope scope;
+    v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
+    v8::Context::Scope contextScope(debuggerContext);
+
+    v8::Local<v8::Object> args = v8::Object::New();
+    args->Set(v8::String::New("scriptId"), v8String(sourceID));
+    args->Set(v8::String::New("lineNumber"), v8::Integer::New(lineNumber));
+
+    v8::Handle<v8::Function> removeBreakpointFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("removeBreakpoint")));
+    v8::Debug::Call(removeBreakpointFunction, args);
+}
+
+void ScriptDebugServer::clearBreakpoints()
+{
+    ensureDebuggerScriptCompiled();
+    v8::HandleScope scope;
+    v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
+    v8::Context::Scope contextScope(debuggerContext);
+
+    v8::Handle<v8::Function> clearBreakpoints = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("clearBreakpoints")));
+    v8::Debug::Call(clearBreakpoints);
+}
+
+void ScriptDebugServer::setBreakpointsActivated(bool enabled)
+{
+    ensureDebuggerScriptCompiled();
+    v8::HandleScope scope;
+    v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
+    v8::Context::Scope contextScope(debuggerContext);
+
+    v8::Local<v8::Object> args = v8::Object::New();
+    args->Set(v8::String::New("enabled"), v8::Boolean::New(enabled));
+    v8::Handle<v8::Function> setBreakpointsActivated = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setBreakpointsActivated")));
+    v8::Debug::Call(setBreakpointsActivated, args);
+}
+
+ScriptDebugServer::PauseOnExceptionsState ScriptDebugServer::pauseOnExceptionsState()
+{
+    ensureDebuggerScriptCompiled();
+    v8::HandleScope scope;
+    v8::Context::Scope contextScope(v8::Debug::GetDebugContext());
+
+    v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("pauseOnExceptionsState")));
+    v8::Handle<v8::Value> argv[] = { v8::Handle<v8::Value>() };
+    v8::Handle<v8::Value> result = function->Call(m_debuggerScript.get(), 0, argv);
+    return static_cast<ScriptDebugServer::PauseOnExceptionsState>(result->Int32Value());
+}
+
+void ScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pauseOnExceptionsState)
+{
+    ensureDebuggerScriptCompiled();
+    v8::HandleScope scope;
+    v8::Context::Scope contextScope(v8::Debug::GetDebugContext());
+
+    v8::Handle<v8::Function> setPauseOnExceptionsFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("setPauseOnExceptionsState")));
+    v8::Handle<v8::Value> argv[] = { v8::Int32::New(pauseOnExceptionsState) };
+    setPauseOnExceptionsFunction->Call(m_debuggerScript.get(), 1, argv);
+}
+
+void ScriptDebugServer::continueProgram()
+{
+    if (m_pausedPage)
+        m_clientMessageLoop->quitNow();
+    didResume();
+}
+
+void ScriptDebugServer::stepIntoStatement()
+{
+    ASSERT(m_pausedPage);
+    v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepIntoStatement")));
+    v8::Handle<v8::Value> argv[] = { m_executionState.get() };
+    function->Call(m_debuggerScript.get(), 1, argv);
+    continueProgram();
+}
+
+void ScriptDebugServer::stepOverStatement()
+{
+    ASSERT(m_pausedPage);
+    v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepOverStatement")));
+    v8::Handle<v8::Value> argv[] = { m_executionState.get() };
+    function->Call(m_debuggerScript.get(), 1, argv);
+    continueProgram();
+}
+
+void ScriptDebugServer::stepOutOfFunction()
+{
+    ASSERT(m_pausedPage);
+    v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("stepOutOfFunction")));
+    v8::Handle<v8::Value> argv[] = { m_executionState.get() };
+    function->Call(m_debuggerScript.get(), 1, argv);
+    continueProgram();
+}
+
+bool ScriptDebugServer::editScriptSource(const String& sourceID, const String& newContent, String& newSourceOrErrorMessage)
+{
+    ensureDebuggerScriptCompiled();
+    v8::HandleScope scope;
+
+    OwnPtr<v8::Context::Scope> contextScope;
+    if (!m_pausedPage)
+        contextScope.set(new v8::Context::Scope(v8::Debug::GetDebugContext()));
+
+    v8::Handle<v8::Function> function = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("editScriptSource")));
+    v8::Handle<v8::Value> argv[] = { v8String(sourceID), v8String(newContent) };
+
+    v8::TryCatch tryCatch;
+    tryCatch.SetVerbose(false);
+    v8::Handle<v8::Value> result = function->Call(m_debuggerScript.get(), 2, argv);
+    if (tryCatch.HasCaught()) {
+        v8::Local<v8::Message> message = tryCatch.Message();
+        if (!message.IsEmpty())
+            newSourceOrErrorMessage = toWebCoreStringWithNullOrUndefinedCheck(message->Get());
+        return false;
+    }
+    ASSERT(!result.IsEmpty());
+    newSourceOrErrorMessage = toWebCoreStringWithNullOrUndefinedCheck(result);
+
+    // Call stack may have changed after if the edited function was on the stack.
+    if (m_currentCallFrame)
+        m_currentCallFrame.clear();
+    return true;
+}
+
+PassRefPtr<JavaScriptCallFrame> ScriptDebugServer::currentCallFrame()
+{
+    if (!m_currentCallFrame) {
+        v8::Handle<v8::Function> currentCallFrameFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("currentCallFrame")));
+        v8::Handle<v8::Value> argv[] = { m_executionState.get() };
+        v8::Handle<v8::Value> currentCallFrameV8 = currentCallFrameFunction->Call(m_debuggerScript.get(), 1, argv);
+        m_currentCallFrame = JavaScriptCallFrame::create(v8::Debug::GetDebugContext(), v8::Handle<v8::Object>::Cast(currentCallFrameV8));
+    }
+    return m_currentCallFrame;
+}
+
+void ScriptDebugServer::setEnabled(bool value)
+{
+     m_enabled = value;
+}
+
+bool ScriptDebugServer::isDebuggerAlwaysEnabled()
+{
+    return m_enabled;
+}
+
+void ScriptDebugServer::v8DebugEventCallback(const v8::Debug::EventDetails& eventDetails)
+{
+    ScriptDebugServer::shared().handleV8DebugEvent(eventDetails);
+}
+
+void ScriptDebugServer::handleV8DebugEvent(const v8::Debug::EventDetails& eventDetails)
+{
+    v8::DebugEvent event = eventDetails.GetEvent();
+    if (event != v8::Break && event != v8::Exception && event != v8::AfterCompile)
+        return;
+
+    v8::Handle<v8::Context> eventContext = eventDetails.GetEventContext();
+    ASSERT(!eventContext.IsEmpty());
+
+    Frame* frame = retrieveFrame(eventContext);
+    if (frame) {
+        ScriptDebugListener* listener = m_listenersMap.get(frame->page());
+        if (listener) {
+            v8::HandleScope scope;
+            if (event == v8::AfterCompile) {
+                v8::Context::Scope contextScope(v8::Debug::GetDebugContext());
+                v8::Handle<v8::Function> onAfterCompileFunction = v8::Local<v8::Function>::Cast(m_debuggerScript.get()->Get(v8::String::New("getAfterCompileScript")));
+                v8::Handle<v8::Value> argv[] = { eventDetails.GetEventData() };
+                v8::Handle<v8::Value> value = onAfterCompileFunction->Call(m_debuggerScript.get(), 1, argv);
+                ASSERT(value->IsObject());
+                v8::Handle<v8::Object> object = v8::Handle<v8::Object>::Cast(value);
+                dispatchDidParseSource(listener, object);
+            } else if (event == v8::Break || event == v8::Exception) {
+                if (event == v8::Exception) {
+                    v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(1);
+                    // Stack trace is empty in case of syntax error. Silently continue execution in such cases.
+                    if (!stackTrace->GetFrameCount())
+                        return;
+                }
+
+                // Don't allow nested breaks.
+                if (m_pausedPage)
+                    return;
+                m_executionState.set(eventDetails.GetExecutionState());
+                m_pausedPage = frame->page();
+                ScriptState* currentCallFrameState = mainWorldScriptState(frame);
+                listener->didPause(currentCallFrameState);
+
+                // Wait for continue or step command.
+                m_clientMessageLoop->run(m_pausedPage);
+                ASSERT(!m_pausedPage);
+
+                // The listener may have been removed in the nested loop.
+                if (ScriptDebugListener* listener = m_listenersMap.get(frame->page()))
+                    listener->didContinue();
+            }
+        }
+    }
+}
+
+void ScriptDebugServer::dispatchDidParseSource(ScriptDebugListener* listener, v8::Handle<v8::Object> object)
+{
+    listener->didParseSource(
+        toWebCoreStringWithNullOrUndefinedCheck(object->Get(v8::String::New("id"))),
+        toWebCoreStringWithNullOrUndefinedCheck(object->Get(v8::String::New("name"))),
+        toWebCoreStringWithNullOrUndefinedCheck(object->Get(v8::String::New("source"))),
+        object->Get(v8::String::New("lineOffset"))->ToInteger()->Value(),
+        static_cast<ScriptWorldType>(object->Get(v8::String::New("scriptWorldType"))->Int32Value()));
+}
+
+void ScriptDebugServer::ensureDebuggerScriptCompiled()
+{
+    if (m_debuggerScript.get().IsEmpty()) {
+        v8::HandleScope scope;
+        v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
+        v8::Context::Scope contextScope(debuggerContext);
+        m_debuggerScript.set(v8::Handle<v8::Object>::Cast(v8::Script::Compile(v8String(m_debuggerScriptSource))->Run()));
+    }
+}
+
+void ScriptDebugServer::didResume()
+{
+    m_currentCallFrame.clear();
+    m_executionState.clear();
+    m_pausedPage = 0;
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(JAVASCRIPT_DEBUGGER)