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