|
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 (!s_exposeV8DebuggerProtocol) |
|
123 return; |
|
124 if (!s_attachedAgentsMap) { |
|
125 s_attachedAgentsMap = new AttachedAgentsMap(); |
|
126 v8::Debug::SetMessageHandler2(&DebuggerAgentManager::onV8DebugMessage); |
|
127 v8::Debug::SetHostDispatchHandler(&DebuggerAgentManager::debugHostDispatchHandler, 100 /* ms */); |
|
128 } |
|
129 int hostId = debuggerAgent->webdevtoolsAgent()->hostId(); |
|
130 ASSERT(hostId); |
|
131 s_attachedAgentsMap->set(hostId, debuggerAgent); |
|
132 } |
|
133 |
|
134 void DebuggerAgentManager::debugDetach(DebuggerAgentImpl* debuggerAgent) |
|
135 { |
|
136 if (!s_exposeV8DebuggerProtocol) |
|
137 return; |
|
138 if (!s_attachedAgentsMap) { |
|
139 ASSERT_NOT_REACHED(); |
|
140 return; |
|
141 } |
|
142 int hostId = debuggerAgent->webdevtoolsAgent()->hostId(); |
|
143 ASSERT(s_attachedAgentsMap->get(hostId) == debuggerAgent); |
|
144 bool isOnBreakpoint = (findAgentForCurrentV8Context() == debuggerAgent); |
|
145 s_attachedAgentsMap->remove(hostId); |
|
146 |
|
147 if (s_attachedAgentsMap->isEmpty()) { |
|
148 delete s_attachedAgentsMap; |
|
149 s_attachedAgentsMap = 0; |
|
150 // Note that we do not empty handlers while in dispatch - we schedule |
|
151 // continue and do removal once we are out of the dispatch. Also there is |
|
152 // no need to send continue command in this case since removing message |
|
153 // handler will cause debugger unload and all breakpoints will be cleared. |
|
154 if (!s_inHostDispatchHandler) { |
|
155 v8::Debug::SetMessageHandler2(0); |
|
156 v8::Debug::SetHostDispatchHandler(0); |
|
157 } |
|
158 } else { |
|
159 // Remove all breakpoints set by the agent. |
|
160 String clearBreakpointGroupCmd = String::format( |
|
161 "{\"seq\":1,\"type\":\"request\",\"command\":\"clearbreakpointgroup\"," |
|
162 "\"arguments\":{\"groupId\":%d}}", |
|
163 hostId); |
|
164 sendCommandToV8(clearBreakpointGroupCmd, new CallerIdWrapper()); |
|
165 |
|
166 if (isOnBreakpoint) { |
|
167 // Force continue if detach happened in nessted message loop while |
|
168 // debugger was paused on a breakpoint(as long as there are other |
|
169 // attached agents v8 will wait for explicit'continue' message). |
|
170 sendContinueCommandToV8(); |
|
171 } |
|
172 } |
|
173 } |
|
174 |
|
175 void DebuggerAgentManager::onV8DebugMessage(const v8::Debug::Message& message) |
|
176 { |
|
177 v8::HandleScope scope; |
|
178 v8::String::Value value(message.GetJSON()); |
|
179 String out(reinterpret_cast<const UChar*>(*value), value.length()); |
|
180 |
|
181 // If callerData is not 0 the message is a response to a debugger command. |
|
182 if (v8::Debug::ClientData* callerData = message.GetClientData()) { |
|
183 CallerIdWrapper* wrapper = static_cast<CallerIdWrapper*>(callerData); |
|
184 if (wrapper->callerIsMananager()) { |
|
185 // Just ignore messages sent by this manager. |
|
186 return; |
|
187 } |
|
188 DebuggerAgentImpl* debuggerAgent = debuggerAgentForHostId(wrapper->callerId()); |
|
189 if (debuggerAgent) |
|
190 debuggerAgent->debuggerOutput(out); |
|
191 else if (!message.WillStartRunning()) { |
|
192 // Autocontinue execution if there is no handler. |
|
193 sendContinueCommandToV8(); |
|
194 } |
|
195 return; |
|
196 } // Otherwise it's an event message. |
|
197 ASSERT(message.IsEvent()); |
|
198 |
|
199 // Ignore unsupported event types. |
|
200 if (message.GetEvent() != v8::AfterCompile && message.GetEvent() != v8::Break && message.GetEvent() != v8::Exception) |
|
201 return; |
|
202 |
|
203 v8::Handle<v8::Context> context = message.GetEventContext(); |
|
204 // If the context is from one of the inpected tabs it should have its context |
|
205 // data. |
|
206 if (context.IsEmpty()) { |
|
207 // Unknown context, skip the event. |
|
208 return; |
|
209 } |
|
210 |
|
211 if (s_inUtilityContext && message.GetEvent() == v8::Break) { |
|
212 // This may happen when two tabs are being debugged in the same process. |
|
213 // Suppose that first debugger is pauesed on an exception. It will run |
|
214 // nested MessageLoop which may process Break request from the second |
|
215 // debugger. |
|
216 s_debugBreakDelayed = true; |
|
217 } else { |
|
218 // If the context is from one of the inpected tabs or injected extension |
|
219 // scripts it must have hostId in the data field. |
|
220 int hostId = WebCore::V8Proxy::contextDebugId(context); |
|
221 if (hostId != -1) { |
|
222 DebuggerAgentImpl* agent = debuggerAgentForHostId(hostId); |
|
223 if (agent) { |
|
224 if (agent->autoContinueOnException() |
|
225 && message.GetEvent() == v8::Exception) { |
|
226 sendContinueCommandToV8(); |
|
227 return; |
|
228 } |
|
229 |
|
230 agent->debuggerOutput(out); |
|
231 return; |
|
232 } |
|
233 } |
|
234 } |
|
235 |
|
236 if (!message.WillStartRunning()) { |
|
237 // Autocontinue execution on break and exception events if there is no |
|
238 // handler. |
|
239 sendContinueCommandToV8(); |
|
240 } |
|
241 } |
|
242 |
|
243 void DebuggerAgentManager::pauseScript() |
|
244 { |
|
245 if (s_inUtilityContext) |
|
246 s_debugBreakDelayed = true; |
|
247 else |
|
248 v8::Debug::DebugBreak(); |
|
249 } |
|
250 |
|
251 void DebuggerAgentManager::executeDebuggerCommand(const String& command, int callerId) |
|
252 { |
|
253 sendCommandToV8(command, new CallerIdWrapper(callerId)); |
|
254 } |
|
255 |
|
256 void DebuggerAgentManager::setMessageLoopDispatchHandler(WebDevToolsAgent::MessageLoopDispatchHandler handler) |
|
257 { |
|
258 s_messageLoopDispatchHandler = handler; |
|
259 } |
|
260 |
|
261 void DebuggerAgentManager::setExposeV8DebuggerProtocol(bool value) |
|
262 { |
|
263 s_exposeV8DebuggerProtocol = value; |
|
264 WebCore::ScriptDebugServer::shared().setEnabled(!s_exposeV8DebuggerProtocol); |
|
265 } |
|
266 |
|
267 void DebuggerAgentManager::setHostId(WebFrameImpl* webframe, int hostId) |
|
268 { |
|
269 ASSERT(hostId > 0); |
|
270 WebCore::V8Proxy* proxy = WebCore::V8Proxy::retrieve(webframe->frame()); |
|
271 if (proxy) |
|
272 proxy->setContextDebugId(hostId); |
|
273 } |
|
274 |
|
275 void DebuggerAgentManager::onWebViewClosed(WebViewImpl* webview) |
|
276 { |
|
277 if (s_pageDeferrers.contains(webview)) { |
|
278 delete s_pageDeferrers.get(webview); |
|
279 s_pageDeferrers.remove(webview); |
|
280 } |
|
281 } |
|
282 |
|
283 void DebuggerAgentManager::onNavigate() |
|
284 { |
|
285 if (s_inHostDispatchHandler) |
|
286 DebuggerAgentManager::sendContinueCommandToV8(); |
|
287 } |
|
288 |
|
289 void DebuggerAgentManager::sendCommandToV8(const String& cmd, v8::Debug::ClientData* data) |
|
290 { |
|
291 v8::Debug::SendCommand(reinterpret_cast<const uint16_t*>(cmd.characters()), cmd.length(), data); |
|
292 } |
|
293 |
|
294 void DebuggerAgentManager::sendContinueCommandToV8() |
|
295 { |
|
296 String continueCmd("{\"seq\":1,\"type\":\"request\",\"command\":\"continue\"}"); |
|
297 sendCommandToV8(continueCmd, new CallerIdWrapper()); |
|
298 } |
|
299 |
|
300 DebuggerAgentImpl* DebuggerAgentManager::findAgentForCurrentV8Context() |
|
301 { |
|
302 if (!s_attachedAgentsMap) |
|
303 return 0; |
|
304 ASSERT(!s_attachedAgentsMap->isEmpty()); |
|
305 |
|
306 WebCore::Frame* frame = WebCore::V8Proxy::retrieveFrameForEnteredContext(); |
|
307 if (!frame) |
|
308 return 0; |
|
309 WebCore::Page* page = frame->page(); |
|
310 for (AttachedAgentsMap::iterator it = s_attachedAgentsMap->begin(); it != s_attachedAgentsMap->end(); ++it) { |
|
311 if (it->second->page() == page) |
|
312 return it->second; |
|
313 } |
|
314 return 0; |
|
315 } |
|
316 |
|
317 DebuggerAgentImpl* DebuggerAgentManager::debuggerAgentForHostId(int hostId) |
|
318 { |
|
319 if (!s_attachedAgentsMap) |
|
320 return 0; |
|
321 return s_attachedAgentsMap->get(hostId); |
|
322 } |
|
323 |
|
324 } // namespace WebKit |