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
46namespace WebKit {
47
48WebDevToolsAgent::MessageLoopDispatchHandler DebuggerAgentManager::s_messageLoopDispatchHandler = 0;
49
50bool DebuggerAgentManager::s_inHostDispatchHandler = false;
51
52DebuggerAgentManager::DeferrersMap DebuggerAgentManager::s_pageDeferrers;
53
54bool DebuggerAgentManager::s_exposeV8DebuggerProtocol = false;
55
56namespace {
57
58class CallerIdWrapper : public v8::Debug::ClientData {
59    WTF_MAKE_NONCOPYABLE(CallerIdWrapper);
60public:
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; }
68private:
69    bool m_callerIsMananager;
70    int m_callerId;
71};
72
73} // namespace
74
75
76void 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
116DebuggerAgentManager::AttachedAgentsMap* DebuggerAgentManager::s_attachedAgentsMap = 0;
117
118void 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
132void 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
172void 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
232void DebuggerAgentManager::pauseScript()
233{
234    v8::Debug::DebugBreak();
235}
236
237void DebuggerAgentManager::executeDebuggerCommand(const WTF::String& command, int callerId)
238{
239    sendCommandToV8(command, new CallerIdWrapper(callerId));
240}
241
242void DebuggerAgentManager::setMessageLoopDispatchHandler(WebDevToolsAgent::MessageLoopDispatchHandler handler)
243{
244    s_messageLoopDispatchHandler = handler;
245}
246
247void DebuggerAgentManager::setExposeV8DebuggerProtocol(bool value)
248{
249    s_exposeV8DebuggerProtocol = value;
250    WebCore::PageScriptDebugServer::shared().setEnabled(!s_exposeV8DebuggerProtocol);
251}
252
253void 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
261void 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
269void DebuggerAgentManager::onNavigate()
270{
271    if (s_inHostDispatchHandler)
272        DebuggerAgentManager::sendContinueCommandToV8();
273}
274
275void 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
280void DebuggerAgentManager::sendContinueCommandToV8()
281{
282    WTF::String continueCmd("{\"seq\":1,\"type\":\"request\",\"command\":\"continue\"}");
283    sendCommandToV8(continueCmd, new CallerIdWrapper());
284}
285
286DebuggerAgentImpl* 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
303DebuggerAgentImpl* DebuggerAgentManager::debuggerAgentForHostId(int hostId)
304{
305    if (!s_attachedAgentsMap)
306        return 0;
307    return s_attachedAgentsMap->get(hostId);
308}
309
310} // namespace WebKit
311