906181e7b2ca5531bed2c89709b6a8800b04427b
[WebKit-https.git] / Source / WebCore / inspector / InspectorBrowserDebuggerAgent.cpp
1 /*
2  * Copyright (C) 2011 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
33 #include "InspectorBrowserDebuggerAgent.h"
34
35 #if ENABLE(INSPECTOR) && ENABLE(JAVASCRIPT_DEBUGGER)
36
37 #include "HTMLElement.h"
38 #include "InspectorController.h"
39 #include "InspectorDOMAgent.h"
40 #include "InspectorDebuggerAgent.h"
41 #include "InspectorState.h"
42 #include <wtf/text/CString.h>
43
44 namespace {
45
46 enum DOMBreakpointType {
47     SubtreeModified = 0,
48     AttributeModified,
49     NodeRemoved,
50     DOMBreakpointTypesCount
51 };
52
53 static const char* const domNativeBreakpointType = "DOM";
54 static const char* const eventListenerNativeBreakpointType = "EventListener";
55 static const char* const xhrNativeBreakpointType = "XHR";
56
57 const uint32_t inheritableDOMBreakpointTypesMask = (1 << SubtreeModified);
58 const int domBreakpointDerivedTypeShift = 16;
59
60 }
61
62 namespace WebCore {
63
64 InspectorBrowserDebuggerAgent::InspectorBrowserDebuggerAgent(InspectorController* inspectorController)
65     : m_inspectorController(inspectorController)
66     , m_hasXHRBreakpointWithEmptyURL(false)
67 {
68 }
69
70 InspectorBrowserDebuggerAgent::~InspectorBrowserDebuggerAgent()
71 {
72 }
73
74 void InspectorBrowserDebuggerAgent::discardBindings()
75 {
76     m_breakpoints.clear();
77 }
78
79 void InspectorBrowserDebuggerAgent::setEventListenerBreakpoint(const String& eventName)
80 {
81     m_eventListenerBreakpoints.add(eventName);
82 }
83
84 void InspectorBrowserDebuggerAgent::removeEventListenerBreakpoint(const String& eventName)
85 {
86     m_eventListenerBreakpoints.remove(eventName);
87 }
88
89 void InspectorBrowserDebuggerAgent::didInsertDOMNode(Node* node)
90 {
91     if (m_breakpoints.size()) {
92         uint32_t mask = m_breakpoints.get(InspectorDOMAgent::innerParentNode(node));
93         uint32_t inheritableTypesMask = (mask | (mask >> domBreakpointDerivedTypeShift)) & inheritableDOMBreakpointTypesMask;
94         if (inheritableTypesMask)
95             updateSubtreeBreakpoints(node, inheritableTypesMask, true);
96     }
97 }
98
99 void InspectorBrowserDebuggerAgent::didRemoveDOMNode(Node* node)
100 {
101     if (m_breakpoints.size()) {
102         // Remove subtree breakpoints.
103         m_breakpoints.remove(node);
104         Vector<Node*> stack(1, InspectorDOMAgent::innerFirstChild(node));
105         do {
106             Node* node = stack.last();
107             stack.removeLast();
108             if (!node)
109                 continue;
110             m_breakpoints.remove(node);
111             stack.append(InspectorDOMAgent::innerFirstChild(node));
112             stack.append(InspectorDOMAgent::innerNextSibling(node));
113         } while (!stack.isEmpty());
114     }
115 }
116
117 void InspectorBrowserDebuggerAgent::setDOMBreakpoint(long nodeId, long type)
118 {
119     Node* node = m_inspectorController->domAgent()->nodeForId(nodeId);
120     if (!node)
121         return;
122
123     uint32_t rootBit = 1 << type;
124     m_breakpoints.set(node, m_breakpoints.get(node) | rootBit);
125     if (rootBit & inheritableDOMBreakpointTypesMask) {
126         for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child))
127             updateSubtreeBreakpoints(child, rootBit, true);
128     }
129 }
130
131 void InspectorBrowserDebuggerAgent::removeDOMBreakpoint(long nodeId, long type)
132 {
133     Node* node = m_inspectorController->domAgent()->nodeForId(nodeId);
134     if (!node)
135         return;
136
137     uint32_t rootBit = 1 << type;
138     uint32_t mask = m_breakpoints.get(node) & ~rootBit;
139     if (mask)
140         m_breakpoints.set(node, mask);
141     else
142         m_breakpoints.remove(node);
143
144     if ((rootBit & inheritableDOMBreakpointTypesMask) && !(mask & (rootBit << domBreakpointDerivedTypeShift))) {
145         for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child))
146             updateSubtreeBreakpoints(child, rootBit, false);
147     }
148 }
149
150 void InspectorBrowserDebuggerAgent::willInsertDOMNode(Node*, Node* parent)
151 {
152     InspectorDebuggerAgent* debuggerAgent = m_inspectorController->debuggerAgent();
153     if (!debuggerAgent)
154         return;
155
156     if (hasBreakpoint(parent, SubtreeModified)) {
157         RefPtr<InspectorObject> eventData = InspectorObject::create();
158         descriptionForDOMEvent(parent, SubtreeModified, true, eventData.get());
159         eventData->setString("breakpointType", domNativeBreakpointType);
160         debuggerAgent->breakProgram(NativeBreakpointDebuggerEventType, eventData.release());
161     }
162 }
163
164 void InspectorBrowserDebuggerAgent::willRemoveDOMNode(Node* node)
165 {
166     InspectorDebuggerAgent* debuggerAgent = m_inspectorController->debuggerAgent();
167     if (!debuggerAgent)
168         return;
169
170     if (hasBreakpoint(node, NodeRemoved)) {
171         RefPtr<InspectorObject> eventData = InspectorObject::create();
172         descriptionForDOMEvent(node, NodeRemoved, false, eventData.get());
173         eventData->setString("breakpointType", domNativeBreakpointType);
174         debuggerAgent->breakProgram(NativeBreakpointDebuggerEventType, eventData.release());
175     } else if (hasBreakpoint(InspectorDOMAgent::innerParentNode(node), SubtreeModified)) {
176         RefPtr<InspectorObject> eventData = InspectorObject::create();
177         descriptionForDOMEvent(node, SubtreeModified, false, eventData.get());
178         eventData->setString("breakpointType", domNativeBreakpointType);
179         debuggerAgent->breakProgram(NativeBreakpointDebuggerEventType, eventData.release());
180     }
181 }
182
183 void InspectorBrowserDebuggerAgent::willModifyDOMAttr(Element* element)
184 {
185     InspectorDebuggerAgent* debuggerAgent = m_inspectorController->debuggerAgent();
186     if (!debuggerAgent)
187         return;
188
189     if (hasBreakpoint(element, AttributeModified)) {
190         RefPtr<InspectorObject> eventData = InspectorObject::create();
191         descriptionForDOMEvent(element, AttributeModified, false, eventData.get());
192         eventData->setString("breakpointType", domNativeBreakpointType);
193         debuggerAgent->breakProgram(NativeBreakpointDebuggerEventType, eventData.release());
194     }
195 }
196
197 void InspectorBrowserDebuggerAgent::descriptionForDOMEvent(Node* target, long breakpointType, bool insertion, InspectorObject* description)
198 {
199     ASSERT(hasBreakpoint(target, breakpointType));
200
201     Node* breakpointOwner = target;
202     if ((1 << breakpointType) & inheritableDOMBreakpointTypesMask) {
203         // For inheritable breakpoint types, target node isn't always the same as the node that owns a breakpoint.
204         // Target node may be unknown to frontend, so we need to push it first.
205         long targetNodeId = m_inspectorController->domAgent()->pushNodePathToFrontend(target);
206         ASSERT(targetNodeId);
207         description->setNumber("targetNodeId", targetNodeId);
208
209         // Find breakpoint owner node.
210         if (!insertion)
211             breakpointOwner = InspectorDOMAgent::innerParentNode(target);
212         ASSERT(breakpointOwner);
213         while (!(m_breakpoints.get(breakpointOwner) & (1 << breakpointType))) {
214             breakpointOwner = InspectorDOMAgent::innerParentNode(breakpointOwner);
215             ASSERT(breakpointOwner);
216         }
217
218         if (breakpointType == SubtreeModified)
219             description->setBoolean("insertion", insertion);
220     }
221
222     long breakpointOwnerNodeId = m_inspectorController->domAgent()->pushNodePathToFrontend(breakpointOwner);
223     ASSERT(breakpointOwnerNodeId);
224     description->setNumber("nodeId", breakpointOwnerNodeId);
225     description->setNumber("type", breakpointType);
226 }
227
228 bool InspectorBrowserDebuggerAgent::hasBreakpoint(Node* node, long type)
229 {
230     uint32_t rootBit = 1 << type;
231     uint32_t derivedBit = rootBit << domBreakpointDerivedTypeShift;
232     return m_breakpoints.get(node) & (rootBit | derivedBit);
233 }
234
235 void InspectorBrowserDebuggerAgent::updateSubtreeBreakpoints(Node* node, uint32_t rootMask, bool set)
236 {
237     uint32_t oldMask = m_breakpoints.get(node);
238     uint32_t derivedMask = rootMask << domBreakpointDerivedTypeShift;
239     uint32_t newMask = set ? oldMask | derivedMask : oldMask & ~derivedMask;
240     if (newMask)
241         m_breakpoints.set(node, newMask);
242     else
243         m_breakpoints.remove(node);
244
245     uint32_t newRootMask = rootMask & ~newMask;
246     if (!newRootMask)
247         return;
248
249     for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child))
250         updateSubtreeBreakpoints(child, newRootMask, set);
251 }
252
253 void InspectorBrowserDebuggerAgent::pauseOnNativeEventIfNeeded(const String& categoryType, const String& eventName, bool synchronous)
254 {
255     InspectorDebuggerAgent* debuggerAgent = m_inspectorController->debuggerAgent();
256     if (!debuggerAgent)
257         return;
258
259     String fullEventName = String::format("%s:%s", categoryType.utf8().data(), eventName.utf8().data());
260     if (!m_eventListenerBreakpoints.contains(fullEventName))
261         return;
262
263     RefPtr<InspectorObject> eventData = InspectorObject::create();
264     eventData->setString("breakpointType", eventListenerNativeBreakpointType);
265     eventData->setString("eventName", fullEventName);
266     if (synchronous)
267         debuggerAgent->breakProgram(NativeBreakpointDebuggerEventType, eventData.release());
268     else
269         debuggerAgent->schedulePauseOnNextStatement(NativeBreakpointDebuggerEventType, eventData.release());
270 }
271
272 void InspectorBrowserDebuggerAgent::setXHRBreakpoint(const String& url)
273 {
274     if (url.isEmpty())
275         m_hasXHRBreakpointWithEmptyURL = true;
276     else
277         m_XHRBreakpoints.add(url);
278 }
279
280 void InspectorBrowserDebuggerAgent::removeXHRBreakpoint(const String& url)
281 {
282     if (url.isEmpty())
283         m_hasXHRBreakpointWithEmptyURL = false;
284     else
285         m_XHRBreakpoints.remove(url);
286 }
287
288 void InspectorBrowserDebuggerAgent::willSendXMLHttpRequest(const String& url)
289 {
290     InspectorDebuggerAgent* debuggerAgent = m_inspectorController->debuggerAgent();
291     if (!debuggerAgent)
292         return;
293
294     String breakpointURL;
295     if (m_hasXHRBreakpointWithEmptyURL)
296         breakpointURL = "";
297     else {
298         for (HashSet<String>::iterator it = m_XHRBreakpoints.begin(); it != m_XHRBreakpoints.end(); ++it) {
299             if (url.contains(*it)) {
300                 breakpointURL = *it;
301                 break;
302             }
303         }
304     }
305
306     if (breakpointURL.isNull())
307         return;
308
309     RefPtr<InspectorObject> eventData = InspectorObject::create();
310     eventData->setString("breakpointType", xhrNativeBreakpointType);
311     eventData->setString("breakpointURL", breakpointURL);
312     eventData->setString("url", url);
313     debuggerAgent->breakProgram(NativeBreakpointDebuggerEventType, eventData.release());
314 }
315
316 void InspectorBrowserDebuggerAgent::clearForPageNavigation()
317 {
318     m_eventListenerBreakpoints.clear();
319     m_XHRBreakpoints.clear();
320     m_hasXHRBreakpointWithEmptyURL = false;
321 }
322
323 } // namespace WebCore
324
325 #endif // ENABLE(INSPECTOR) && ENABLE(JAVASCRIPT_DEBUGGER)