/* * 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 "DebuggerAgentManager.h" #include "DebuggerAgentImpl.h" #include "Frame.h" #include "PageGroupLoadDeferrer.h" #include "ScriptDebugServer.h" #include "V8Proxy.h" #include "WebDevToolsAgentImpl.h" #include "WebFrameImpl.h" #include "WebViewImpl.h" #include #include #include namespace WebKit { WebDevToolsAgent::MessageLoopDispatchHandler DebuggerAgentManager::s_messageLoopDispatchHandler = 0; bool DebuggerAgentManager::s_inHostDispatchHandler = false; DebuggerAgentManager::DeferrersMap DebuggerAgentManager::s_pageDeferrers; bool DebuggerAgentManager::s_exposeV8DebuggerProtocol = false; namespace { class CallerIdWrapper : public v8::Debug::ClientData { WTF_MAKE_NONCOPYABLE(CallerIdWrapper); public: CallerIdWrapper() : m_callerIsMananager(true), m_callerId(0) { } explicit CallerIdWrapper(int callerId) : m_callerIsMananager(false) , m_callerId(callerId) { } ~CallerIdWrapper() { } bool callerIsMananager() const { return m_callerIsMananager; } int callerId() const { return m_callerId; } private: bool m_callerIsMananager; int m_callerId; }; } // namespace void DebuggerAgentManager::debugHostDispatchHandler() { if (!s_messageLoopDispatchHandler || !s_attachedAgentsMap) return; if (s_inHostDispatchHandler) return; s_inHostDispatchHandler = true; Vector views; // 1. Disable active objects and input events. for (AttachedAgentsMap::iterator it = s_attachedAgentsMap->begin(); it != s_attachedAgentsMap->end(); ++it) { DebuggerAgentImpl* agent = it->second; s_pageDeferrers.set(agent->webView(), new WebCore::PageGroupLoadDeferrer(agent->page(), true)); views.append(agent->webView()); agent->webView()->setIgnoreInputEvents(true); } // 2. Process messages. s_messageLoopDispatchHandler(); // 3. Bring things back. for (Vector::iterator it = views.begin(); it != views.end(); ++it) { if (s_pageDeferrers.contains(*it)) { // The view was not closed during the dispatch. (*it)->setIgnoreInputEvents(false); } } deleteAllValues(s_pageDeferrers); s_pageDeferrers.clear(); s_inHostDispatchHandler = false; if (!s_attachedAgentsMap) { // Remove handlers if all agents were detached within host dispatch. v8::Debug::SetMessageHandler(0); v8::Debug::SetHostDispatchHandler(0); } } DebuggerAgentManager::AttachedAgentsMap* DebuggerAgentManager::s_attachedAgentsMap = 0; void DebuggerAgentManager::debugAttach(DebuggerAgentImpl* debuggerAgent) { if (!s_exposeV8DebuggerProtocol) return; if (!s_attachedAgentsMap) { s_attachedAgentsMap = new AttachedAgentsMap(); v8::Debug::SetMessageHandler2(&DebuggerAgentManager::onV8DebugMessage); v8::Debug::SetHostDispatchHandler(&DebuggerAgentManager::debugHostDispatchHandler, 100 /* ms */); } int hostId = debuggerAgent->webdevtoolsAgent()->hostId(); ASSERT(hostId); s_attachedAgentsMap->set(hostId, debuggerAgent); } void DebuggerAgentManager::debugDetach(DebuggerAgentImpl* debuggerAgent) { if (!s_exposeV8DebuggerProtocol) return; if (!s_attachedAgentsMap) { ASSERT_NOT_REACHED(); return; } int hostId = debuggerAgent->webdevtoolsAgent()->hostId(); ASSERT(s_attachedAgentsMap->get(hostId) == debuggerAgent); bool isOnBreakpoint = (findAgentForCurrentV8Context() == debuggerAgent); s_attachedAgentsMap->remove(hostId); if (s_attachedAgentsMap->isEmpty()) { delete s_attachedAgentsMap; s_attachedAgentsMap = 0; // Note that we do not empty handlers while in dispatch - we schedule // continue and do removal once we are out of the dispatch. Also there is // no need to send continue command in this case since removing message // handler will cause debugger unload and all breakpoints will be cleared. if (!s_inHostDispatchHandler) { v8::Debug::SetMessageHandler2(0); v8::Debug::SetHostDispatchHandler(0); } } else { // Remove all breakpoints set by the agent. String clearBreakpointGroupCmd = makeString( "{\"seq\":1,\"type\":\"request\",\"command\":\"clearbreakpointgroup\"," "\"arguments\":{\"groupId\":", String::number(hostId), "}}"); sendCommandToV8(clearBreakpointGroupCmd, new CallerIdWrapper()); if (isOnBreakpoint) { // Force continue if detach happened in nessted message loop while // debugger was paused on a breakpoint(as long as there are other // attached agents v8 will wait for explicit'continue' message). sendContinueCommandToV8(); } } } void DebuggerAgentManager::onV8DebugMessage(const v8::Debug::Message& message) { v8::HandleScope scope; v8::String::Value value(message.GetJSON()); WTF::String out(reinterpret_cast(*value), value.length()); // If callerData is not 0 the message is a response to a debugger command. if (v8::Debug::ClientData* callerData = message.GetClientData()) { CallerIdWrapper* wrapper = static_cast(callerData); if (wrapper->callerIsMananager()) { // Just ignore messages sent by this manager. return; } DebuggerAgentImpl* debuggerAgent = debuggerAgentForHostId(wrapper->callerId()); if (debuggerAgent) debuggerAgent->debuggerOutput(out); else if (!message.WillStartRunning()) { // Autocontinue execution if there is no handler. sendContinueCommandToV8(); } return; } // Otherwise it's an event message. ASSERT(message.IsEvent()); // Ignore unsupported event types. if (message.GetEvent() != v8::AfterCompile && message.GetEvent() != v8::Break && message.GetEvent() != v8::Exception) return; v8::Handle context = message.GetEventContext(); // If the context is from one of the inpected tabs it should have its context // data. if (context.IsEmpty()) { // Unknown context, skip the event. return; } // If the context is from one of the inpected tabs or injected extension // scripts it must have hostId in the data field. int hostId = WebCore::V8Proxy::contextDebugId(context); if (hostId != -1) { DebuggerAgentImpl* agent = debuggerAgentForHostId(hostId); if (agent) { if (agent->autoContinueOnException() && message.GetEvent() == v8::Exception) { sendContinueCommandToV8(); return; } agent->debuggerOutput(out); return; } } if (!message.WillStartRunning()) { // Autocontinue execution on break and exception events if there is no // handler. sendContinueCommandToV8(); } } void DebuggerAgentManager::pauseScript() { v8::Debug::DebugBreak(); } void DebuggerAgentManager::executeDebuggerCommand(const WTF::String& command, int callerId) { sendCommandToV8(command, new CallerIdWrapper(callerId)); } void DebuggerAgentManager::setMessageLoopDispatchHandler(WebDevToolsAgent::MessageLoopDispatchHandler handler) { s_messageLoopDispatchHandler = handler; } void DebuggerAgentManager::setExposeV8DebuggerProtocol(bool value) { s_exposeV8DebuggerProtocol = value; WebCore::ScriptDebugServer::shared().setEnabled(!s_exposeV8DebuggerProtocol); } void DebuggerAgentManager::setHostId(WebFrameImpl* webframe, int hostId) { ASSERT(hostId > 0); WebCore::V8Proxy* proxy = WebCore::V8Proxy::retrieve(webframe->frame()); if (proxy) proxy->setContextDebugId(hostId); } void DebuggerAgentManager::onWebViewClosed(WebViewImpl* webview) { if (s_pageDeferrers.contains(webview)) { delete s_pageDeferrers.get(webview); s_pageDeferrers.remove(webview); } } void DebuggerAgentManager::onNavigate() { if (s_inHostDispatchHandler) DebuggerAgentManager::sendContinueCommandToV8(); } void DebuggerAgentManager::sendCommandToV8(const WTF::String& cmd, v8::Debug::ClientData* data) { v8::Debug::SendCommand(reinterpret_cast(cmd.characters()), cmd.length(), data); } void DebuggerAgentManager::sendContinueCommandToV8() { WTF::String continueCmd("{\"seq\":1,\"type\":\"request\",\"command\":\"continue\"}"); sendCommandToV8(continueCmd, new CallerIdWrapper()); } DebuggerAgentImpl* DebuggerAgentManager::findAgentForCurrentV8Context() { if (!s_attachedAgentsMap) return 0; ASSERT(!s_attachedAgentsMap->isEmpty()); WebCore::Frame* frame = WebCore::V8Proxy::retrieveFrameForEnteredContext(); if (!frame) return 0; WebCore::Page* page = frame->page(); for (AttachedAgentsMap::iterator it = s_attachedAgentsMap->begin(); it != s_attachedAgentsMap->end(); ++it) { if (it->second->page() == page) return it->second; } return 0; } DebuggerAgentImpl* DebuggerAgentManager::debuggerAgentForHostId(int hostId) { if (!s_attachedAgentsMap) return 0; return s_attachedAgentsMap->get(hostId); } } // namespace WebKit