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