Home | History | Annotate | Download | only in src
      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 "DebuggerAgentManager.h"
     33 
     34 #include "DebuggerAgentImpl.h"
     35 #include "Frame.h"
     36 #include "PageGroupLoadDeferrer.h"
     37 #include "PageScriptDebugServer.h"
     38 #include "V8Proxy.h"
     39 #include "WebDevToolsAgentImpl.h"
     40 #include "WebFrameImpl.h"
     41 #include "WebViewImpl.h"
     42 #include <wtf/HashSet.h>
     43 #include <wtf/Noncopyable.h>
     44 #include <wtf/text/StringConcatenate.h>
     45 
     46 namespace WebKit {
     47 
     48 WebDevToolsAgent::MessageLoopDispatchHandler DebuggerAgentManager::s_messageLoopDispatchHandler = 0;
     49 
     50 bool DebuggerAgentManager::s_inHostDispatchHandler = false;
     51 
     52 DebuggerAgentManager::DeferrersMap DebuggerAgentManager::s_pageDeferrers;
     53 
     54 bool DebuggerAgentManager::s_exposeV8DebuggerProtocol = false;
     55 
     56 namespace {
     57 
     58 class CallerIdWrapper : public v8::Debug::ClientData {
     59     WTF_MAKE_NONCOPYABLE(CallerIdWrapper);
     60 public:
     61     CallerIdWrapper() : m_callerIsMananager(true), m_callerId(0) { }
     62     explicit CallerIdWrapper(int callerId)
     63         : m_callerIsMananager(false)
     64         , m_callerId(callerId) { }
     65     ~CallerIdWrapper() { }
     66     bool callerIsMananager() const { return m_callerIsMananager; }
     67     int callerId() const { return m_callerId; }
     68 private:
     69     bool m_callerIsMananager;
     70     int m_callerId;
     71 };
     72 
     73 } // namespace
     74 
     75 
     76 void DebuggerAgentManager::debugHostDispatchHandler()
     77 {
     78     if (!s_messageLoopDispatchHandler || !s_attachedAgentsMap)
     79         return;
     80 
     81     if (s_inHostDispatchHandler)
     82         return;
     83 
     84     s_inHostDispatchHandler = true;
     85 
     86     Vector<WebViewImpl*> views;
     87     // 1. Disable active objects and input events.
     88     for (AttachedAgentsMap::iterator it = s_attachedAgentsMap->begin(); it != s_attachedAgentsMap->end(); ++it) {
     89         DebuggerAgentImpl* agent = it->second;
     90         s_pageDeferrers.set(agent->webView(), new WebCore::PageGroupLoadDeferrer(agent->page(), true));
     91         views.append(agent->webView());
     92         agent->webView()->setIgnoreInputEvents(true);
     93     }
     94 
     95     // 2. Process messages.
     96     s_messageLoopDispatchHandler();
     97 
     98     // 3. Bring things back.
     99     for (Vector<WebViewImpl*>::iterator it = views.begin(); it != views.end(); ++it) {
    100         if (s_pageDeferrers.contains(*it)) {
    101             // The view was not closed during the dispatch.
    102             (*it)->setIgnoreInputEvents(false);
    103         }
    104     }
    105     deleteAllValues(s_pageDeferrers);
    106     s_pageDeferrers.clear();
    107 
    108     s_inHostDispatchHandler = false;
    109     if (!s_attachedAgentsMap) {
    110         // Remove handlers if all agents were detached within host dispatch.
    111         v8::Debug::SetMessageHandler(0);
    112         v8::Debug::SetHostDispatchHandler(0);
    113     }
    114 }
    115 
    116 DebuggerAgentManager::AttachedAgentsMap* DebuggerAgentManager::s_attachedAgentsMap = 0;
    117 
    118 void DebuggerAgentManager::debugAttach(DebuggerAgentImpl* debuggerAgent)
    119 {
    120     if (!s_exposeV8DebuggerProtocol)
    121         return;
    122     if (!s_attachedAgentsMap) {
    123         s_attachedAgentsMap = new AttachedAgentsMap();
    124         v8::Debug::SetMessageHandler2(&DebuggerAgentManager::onV8DebugMessage);
    125         v8::Debug::SetHostDispatchHandler(&DebuggerAgentManager::debugHostDispatchHandler, 100 /* ms */);
    126     }
    127     int hostId = debuggerAgent->webdevtoolsAgent()->hostId();
    128     ASSERT(hostId);
    129     s_attachedAgentsMap->set(hostId, debuggerAgent);
    130 }
    131 
    132 void DebuggerAgentManager::debugDetach(DebuggerAgentImpl* debuggerAgent)
    133 {
    134     if (!s_exposeV8DebuggerProtocol)
    135         return;
    136     if (!s_attachedAgentsMap) {
    137         ASSERT_NOT_REACHED();
    138         return;
    139     }
    140     int hostId = debuggerAgent->webdevtoolsAgent()->hostId();
    141     ASSERT(s_attachedAgentsMap->get(hostId) == debuggerAgent);
    142     bool isOnBreakpoint = (findAgentForCurrentV8Context() == debuggerAgent);
    143     s_attachedAgentsMap->remove(hostId);
    144 
    145     if (s_attachedAgentsMap->isEmpty()) {
    146         delete s_attachedAgentsMap;
    147         s_attachedAgentsMap = 0;
    148         // Note that we do not empty handlers while in dispatch - we schedule
    149         // continue and do removal once we are out of the dispatch. Also there is
    150         // no need to send continue command in this case since removing message
    151         // handler will cause debugger unload and all breakpoints will be cleared.
    152         if (!s_inHostDispatchHandler) {
    153             v8::Debug::SetMessageHandler2(0);
    154             v8::Debug::SetHostDispatchHandler(0);
    155         }
    156     } else {
    157       // Remove all breakpoints set by the agent.
    158       String clearBreakpointGroupCmd = makeString(
    159           "{\"seq\":1,\"type\":\"request\",\"command\":\"clearbreakpointgroup\","
    160               "\"arguments\":{\"groupId\":", String::number(hostId), "}}");
    161       sendCommandToV8(clearBreakpointGroupCmd, new CallerIdWrapper());
    162 
    163       if (isOnBreakpoint) {
    164           // Force continue if detach happened in nessted message loop while
    165           // debugger was paused on a breakpoint(as long as there are other
    166           // attached agents v8 will wait for explicit'continue' message).
    167           sendContinueCommandToV8();
    168       }
    169     }
    170 }
    171 
    172 void DebuggerAgentManager::onV8DebugMessage(const v8::Debug::Message& message)
    173 {
    174     v8::HandleScope scope;
    175     v8::String::Value value(message.GetJSON());
    176     WTF::String out(reinterpret_cast<const UChar*>(*value), value.length());
    177 
    178     // If callerData is not 0 the message is a response to a debugger command.
    179     if (v8::Debug::ClientData* callerData = message.GetClientData()) {
    180         CallerIdWrapper* wrapper = static_cast<CallerIdWrapper*>(callerData);
    181         if (wrapper->callerIsMananager()) {
    182             // Just ignore messages sent by this manager.
    183             return;
    184         }
    185         DebuggerAgentImpl* debuggerAgent = debuggerAgentForHostId(wrapper->callerId());
    186         if (debuggerAgent)
    187             debuggerAgent->debuggerOutput(out);
    188         else if (!message.WillStartRunning()) {
    189             // Autocontinue execution if there is no handler.
    190             sendContinueCommandToV8();
    191         }
    192         return;
    193     } // Otherwise it's an event message.
    194     ASSERT(message.IsEvent());
    195 
    196     // Ignore unsupported event types.
    197     if (message.GetEvent() != v8::AfterCompile && message.GetEvent() != v8::Break && message.GetEvent() != v8::Exception)
    198         return;
    199 
    200     v8::Handle<v8::Context> context = message.GetEventContext();
    201     // If the context is from one of the inpected tabs it should have its context
    202     // data.
    203     if (context.IsEmpty()) {
    204         // Unknown context, skip the event.
    205         return;
    206     }
    207 
    208     // If the context is from one of the inpected tabs or injected extension
    209     // scripts it must have hostId in the data field.
    210     int hostId = WebCore::V8Proxy::contextDebugId(context);
    211     if (hostId != -1) {
    212         DebuggerAgentImpl* agent = debuggerAgentForHostId(hostId);
    213         if (agent) {
    214             if (agent->autoContinueOnException()
    215                 && message.GetEvent() == v8::Exception) {
    216                 sendContinueCommandToV8();
    217                 return;
    218             }
    219 
    220             agent->debuggerOutput(out);
    221             return;
    222         }
    223     }
    224 
    225     if (!message.WillStartRunning()) {
    226         // Autocontinue execution on break and exception  events if there is no
    227         // handler.
    228         sendContinueCommandToV8();
    229     }
    230 }
    231 
    232 void DebuggerAgentManager::pauseScript()
    233 {
    234     v8::Debug::DebugBreak();
    235 }
    236 
    237 void DebuggerAgentManager::executeDebuggerCommand(const WTF::String& command, int callerId)
    238 {
    239     sendCommandToV8(command, new CallerIdWrapper(callerId));
    240 }
    241 
    242 void DebuggerAgentManager::setMessageLoopDispatchHandler(WebDevToolsAgent::MessageLoopDispatchHandler handler)
    243 {
    244     s_messageLoopDispatchHandler = handler;
    245 }
    246 
    247 void DebuggerAgentManager::setExposeV8DebuggerProtocol(bool value)
    248 {
    249     s_exposeV8DebuggerProtocol = value;
    250     WebCore::PageScriptDebugServer::shared().setEnabled(!s_exposeV8DebuggerProtocol);
    251 }
    252 
    253 void DebuggerAgentManager::setHostId(WebFrameImpl* webframe, int hostId)
    254 {
    255     ASSERT(hostId > 0);
    256     WebCore::V8Proxy* proxy = WebCore::V8Proxy::retrieve(webframe->frame());
    257     if (proxy)
    258         proxy->setContextDebugId(hostId);
    259 }
    260 
    261 void DebuggerAgentManager::onWebViewClosed(WebViewImpl* webview)
    262 {
    263     if (s_pageDeferrers.contains(webview)) {
    264         delete s_pageDeferrers.get(webview);
    265         s_pageDeferrers.remove(webview);
    266     }
    267 }
    268 
    269 void DebuggerAgentManager::onNavigate()
    270 {
    271     if (s_inHostDispatchHandler)
    272         DebuggerAgentManager::sendContinueCommandToV8();
    273 }
    274 
    275 void DebuggerAgentManager::sendCommandToV8(const WTF::String& cmd, v8::Debug::ClientData* data)
    276 {
    277     v8::Debug::SendCommand(reinterpret_cast<const uint16_t*>(cmd.characters()), cmd.length(), data);
    278 }
    279 
    280 void DebuggerAgentManager::sendContinueCommandToV8()
    281 {
    282     WTF::String continueCmd("{\"seq\":1,\"type\":\"request\",\"command\":\"continue\"}");
    283     sendCommandToV8(continueCmd, new CallerIdWrapper());
    284 }
    285 
    286 DebuggerAgentImpl* DebuggerAgentManager::findAgentForCurrentV8Context()
    287 {
    288     if (!s_attachedAgentsMap)
    289         return 0;
    290     ASSERT(!s_attachedAgentsMap->isEmpty());
    291 
    292     WebCore::Frame* frame = WebCore::V8Proxy::retrieveFrameForEnteredContext();
    293     if (!frame)
    294         return 0;
    295     WebCore::Page* page = frame->page();
    296     for (AttachedAgentsMap::iterator it = s_attachedAgentsMap->begin(); it != s_attachedAgentsMap->end(); ++it) {
    297         if (it->second->page() == page)
    298             return it->second;
    299     }
    300     return 0;
    301 }
    302 
    303 DebuggerAgentImpl* DebuggerAgentManager::debuggerAgentForHostId(int hostId)
    304 {
    305     if (!s_attachedAgentsMap)
    306         return 0;
    307     return s_attachedAgentsMap->get(hostId);
    308 }
    309 
    310 } // namespace WebKit
    311