7f921a35a2240cbe08327fdfc3ba43894f3cead6
[WebKit-https.git] / Source / WebCore / inspector / InspectorDOMDebuggerAgent.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 #if ENABLE(INSPECTOR) && ENABLE(JAVASCRIPT_DEBUGGER)
34
35 #include "InspectorDOMDebuggerAgent.h"
36
37 #include "HTMLElement.h"
38 #include "InspectorDOMAgent.h"
39 #include "InspectorDebuggerAgent.h"
40 #include "InspectorInstrumentation.h"
41 #include "InspectorWebFrontendDispatchers.h"
42 #include "InstrumentingAgents.h"
43 #include <inspector/InspectorValues.h>
44 #include <wtf/text/WTFString.h>
45
46 namespace {
47
48 enum DOMBreakpointType {
49     SubtreeModified = 0,
50     AttributeModified,
51     NodeRemoved,
52     DOMBreakpointTypesCount
53 };
54
55 static const char* const listenerEventCategoryType = "listener:";
56 static const char* const instrumentationEventCategoryType = "instrumentation:";
57
58 const uint32_t inheritableDOMBreakpointTypesMask = (1 << SubtreeModified);
59 const int domBreakpointDerivedTypeShift = 16;
60
61 }
62
63 using namespace Inspector;
64
65 namespace WebCore {
66
67 InspectorDOMDebuggerAgent::InspectorDOMDebuggerAgent(InstrumentingAgents* instrumentingAgents, InspectorDOMAgent* domAgent, InspectorDebuggerAgent* debuggerAgent)
68     : InspectorAgentBase(ASCIILiteral("DOMDebugger"), instrumentingAgents)
69     , m_domAgent(domAgent)
70     , m_debuggerAgent(debuggerAgent)
71     , m_pauseInNextEventListener(false)
72     , m_pauseOnAllXHRsEnabled(false)
73 {
74     m_debuggerAgent->setListener(this);
75 }
76
77 InspectorDOMDebuggerAgent::~InspectorDOMDebuggerAgent()
78 {
79     ASSERT(!m_debuggerAgent);
80     ASSERT(!m_instrumentingAgents->inspectorDOMDebuggerAgent());
81 }
82
83 // Browser debugger agent enabled only when JS debugger is enabled.
84 void InspectorDOMDebuggerAgent::debuggerWasEnabled()
85 {
86     m_instrumentingAgents->setInspectorDOMDebuggerAgent(this);
87 }
88
89 void InspectorDOMDebuggerAgent::debuggerWasDisabled()
90 {
91     disable();
92 }
93
94 void InspectorDOMDebuggerAgent::stepInto()
95 {
96     m_pauseInNextEventListener = true;
97 }
98
99 void InspectorDOMDebuggerAgent::didPause()
100 {
101     m_pauseInNextEventListener = false;
102 }
103
104 void InspectorDOMDebuggerAgent::disable()
105 {
106     m_instrumentingAgents->setInspectorDOMDebuggerAgent(nullptr);
107     clear();
108 }
109
110 void InspectorDOMDebuggerAgent::didCreateFrontendAndBackend(Inspector::InspectorFrontendChannel*, InspectorBackendDispatcher* backendDispatcher)
111 {
112     m_backendDispatcher = InspectorDOMDebuggerBackendDispatcher::create(backendDispatcher, this);
113 }
114
115 void InspectorDOMDebuggerAgent::willDestroyFrontendAndBackend()
116 {
117     m_backendDispatcher.clear();
118
119     disable();
120 }
121
122 void InspectorDOMDebuggerAgent::discardAgent()
123 {
124     m_debuggerAgent->setListener(nullptr);
125     m_debuggerAgent = nullptr;
126 }
127
128 void InspectorDOMDebuggerAgent::discardBindings()
129 {
130     m_domBreakpoints.clear();
131 }
132
133 void InspectorDOMDebuggerAgent::setEventListenerBreakpoint(ErrorString* error, const String& eventName)
134 {
135     setBreakpoint(error, String(listenerEventCategoryType) + eventName);
136 }
137
138 void InspectorDOMDebuggerAgent::setInstrumentationBreakpoint(ErrorString* error, const String& eventName)
139 {
140     setBreakpoint(error, String(instrumentationEventCategoryType) + eventName);
141 }
142
143 void InspectorDOMDebuggerAgent::setBreakpoint(ErrorString* error, const String& eventName)
144 {
145     if (eventName.isEmpty()) {
146         *error = "Event name is empty";
147         return;
148     }
149
150     m_eventListenerBreakpoints.add(eventName);
151 }
152
153 void InspectorDOMDebuggerAgent::removeEventListenerBreakpoint(ErrorString* error, const String& eventName)
154 {
155     removeBreakpoint(error, String(listenerEventCategoryType) + eventName);
156 }
157
158 void InspectorDOMDebuggerAgent::removeInstrumentationBreakpoint(ErrorString* error, const String& eventName)
159 {
160     removeBreakpoint(error, String(instrumentationEventCategoryType) + eventName);
161 }
162
163 void InspectorDOMDebuggerAgent::removeBreakpoint(ErrorString* error, const String& eventName)
164 {
165     if (eventName.isEmpty()) {
166         *error = "Event name is empty";
167         return;
168     }
169
170     m_eventListenerBreakpoints.remove(eventName);
171 }
172
173 void InspectorDOMDebuggerAgent::didInvalidateStyleAttr(Node* node)
174 {
175     if (hasBreakpoint(node, AttributeModified)) {
176         RefPtr<InspectorObject> eventData = InspectorObject::create();
177         descriptionForDOMEvent(node, AttributeModified, false, eventData.get());
178         m_debuggerAgent->breakProgram(InspectorDebuggerFrontendDispatcher::Reason::DOM, eventData.release());
179     }
180 }
181
182 void InspectorDOMDebuggerAgent::didInsertDOMNode(Node* node)
183 {
184     if (m_domBreakpoints.size()) {
185         uint32_t mask = m_domBreakpoints.get(InspectorDOMAgent::innerParentNode(node));
186         uint32_t inheritableTypesMask = (mask | (mask >> domBreakpointDerivedTypeShift)) & inheritableDOMBreakpointTypesMask;
187         if (inheritableTypesMask)
188             updateSubtreeBreakpoints(node, inheritableTypesMask, true);
189     }
190 }
191
192 void InspectorDOMDebuggerAgent::didRemoveDOMNode(Node* node)
193 {
194     if (m_domBreakpoints.size()) {
195         // Remove subtree breakpoints.
196         m_domBreakpoints.remove(node);
197         Vector<Node*> stack(1, InspectorDOMAgent::innerFirstChild(node));
198         do {
199             Node* node = stack.last();
200             stack.removeLast();
201             if (!node)
202                 continue;
203             m_domBreakpoints.remove(node);
204             stack.append(InspectorDOMAgent::innerFirstChild(node));
205             stack.append(InspectorDOMAgent::innerNextSibling(node));
206         } while (!stack.isEmpty());
207     }
208 }
209
210 static int domTypeForName(ErrorString* errorString, const String& typeString)
211 {
212     if (typeString == "subtree-modified")
213         return SubtreeModified;
214     if (typeString == "attribute-modified")
215         return AttributeModified;
216     if (typeString == "node-removed")
217         return NodeRemoved;
218     *errorString = makeString("Unknown DOM breakpoint type: ", typeString);
219     return -1;
220 }
221
222 static String domTypeName(int type)
223 {
224     switch (type) {
225     case SubtreeModified: return "subtree-modified";
226     case AttributeModified: return "attribute-modified";
227     case NodeRemoved: return "node-removed";
228     default: break;
229     }
230     return "";
231 }
232
233 void InspectorDOMDebuggerAgent::setDOMBreakpoint(ErrorString* errorString, int nodeId, const String& typeString)
234 {
235     Node* node = m_domAgent->assertNode(errorString, nodeId);
236     if (!node)
237         return;
238
239     int type = domTypeForName(errorString, typeString);
240     if (type == -1)
241         return;
242
243     uint32_t rootBit = 1 << type;
244     m_domBreakpoints.set(node, m_domBreakpoints.get(node) | rootBit);
245     if (rootBit & inheritableDOMBreakpointTypesMask) {
246         for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child))
247             updateSubtreeBreakpoints(child, rootBit, true);
248     }
249 }
250
251 void InspectorDOMDebuggerAgent::removeDOMBreakpoint(ErrorString* errorString, int nodeId, const String& typeString)
252 {
253     Node* node = m_domAgent->assertNode(errorString, nodeId);
254     if (!node)
255         return;
256     int type = domTypeForName(errorString, typeString);
257     if (type == -1)
258         return;
259
260     uint32_t rootBit = 1 << type;
261     uint32_t mask = m_domBreakpoints.get(node) & ~rootBit;
262     if (mask)
263         m_domBreakpoints.set(node, mask);
264     else
265         m_domBreakpoints.remove(node);
266
267     if ((rootBit & inheritableDOMBreakpointTypesMask) && !(mask & (rootBit << domBreakpointDerivedTypeShift))) {
268         for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child))
269             updateSubtreeBreakpoints(child, rootBit, false);
270     }
271 }
272
273 void InspectorDOMDebuggerAgent::willInsertDOMNode(Node* parent)
274 {
275     if (hasBreakpoint(parent, SubtreeModified)) {
276         RefPtr<InspectorObject> eventData = InspectorObject::create();
277         descriptionForDOMEvent(parent, SubtreeModified, true, eventData.get());
278         m_debuggerAgent->breakProgram(InspectorDebuggerFrontendDispatcher::Reason::DOM, eventData.release());
279     }
280 }
281
282 void InspectorDOMDebuggerAgent::willRemoveDOMNode(Node* node)
283 {
284     Node* parentNode = InspectorDOMAgent::innerParentNode(node);
285     if (hasBreakpoint(node, NodeRemoved)) {
286         RefPtr<InspectorObject> eventData = InspectorObject::create();
287         descriptionForDOMEvent(node, NodeRemoved, false, eventData.get());
288         m_debuggerAgent->breakProgram(InspectorDebuggerFrontendDispatcher::Reason::DOM, eventData.release());
289     } else if (parentNode && hasBreakpoint(parentNode, SubtreeModified)) {
290         RefPtr<InspectorObject> eventData = InspectorObject::create();
291         descriptionForDOMEvent(node, SubtreeModified, false, eventData.get());
292         m_debuggerAgent->breakProgram(InspectorDebuggerFrontendDispatcher::Reason::DOM, eventData.release());
293     }
294 }
295
296 void InspectorDOMDebuggerAgent::willModifyDOMAttr(Element* element)
297 {
298     if (hasBreakpoint(element, AttributeModified)) {
299         RefPtr<InspectorObject> eventData = InspectorObject::create();
300         descriptionForDOMEvent(element, AttributeModified, false, eventData.get());
301         m_debuggerAgent->breakProgram(InspectorDebuggerFrontendDispatcher::Reason::DOM, eventData.release());
302     }
303 }
304
305 void InspectorDOMDebuggerAgent::descriptionForDOMEvent(Node* target, int breakpointType, bool insertion, InspectorObject* description)
306 {
307     ASSERT(hasBreakpoint(target, breakpointType));
308
309     Node* breakpointOwner = target;
310     if ((1 << breakpointType) & inheritableDOMBreakpointTypesMask) {
311         // For inheritable breakpoint types, target node isn't always the same as the node that owns a breakpoint.
312         // Target node may be unknown to frontend, so we need to push it first.
313         RefPtr<Inspector::TypeBuilder::Runtime::RemoteObject> targetNodeObject = m_domAgent->resolveNode(target, InspectorDebuggerAgent::backtraceObjectGroup);
314         description->setValue("targetNode", targetNodeObject);
315
316         // Find breakpoint owner node.
317         if (!insertion)
318             breakpointOwner = InspectorDOMAgent::innerParentNode(target);
319         ASSERT(breakpointOwner);
320         while (!(m_domBreakpoints.get(breakpointOwner) & (1 << breakpointType))) {
321             Node* parentNode = InspectorDOMAgent::innerParentNode(breakpointOwner);
322             if (!parentNode)
323                 break;
324             breakpointOwner = parentNode;
325         }
326
327         if (breakpointType == SubtreeModified)
328             description->setBoolean("insertion", insertion);
329     }
330
331     int breakpointOwnerNodeId = m_domAgent->boundNodeId(breakpointOwner);
332     ASSERT(breakpointOwnerNodeId);
333     description->setNumber("nodeId", breakpointOwnerNodeId);
334     description->setString("type", domTypeName(breakpointType));
335 }
336
337 bool InspectorDOMDebuggerAgent::hasBreakpoint(Node* node, int type)
338 {
339     uint32_t rootBit = 1 << type;
340     uint32_t derivedBit = rootBit << domBreakpointDerivedTypeShift;
341     return m_domBreakpoints.get(node) & (rootBit | derivedBit);
342 }
343
344 void InspectorDOMDebuggerAgent::updateSubtreeBreakpoints(Node* node, uint32_t rootMask, bool set)
345 {
346     uint32_t oldMask = m_domBreakpoints.get(node);
347     uint32_t derivedMask = rootMask << domBreakpointDerivedTypeShift;
348     uint32_t newMask = set ? oldMask | derivedMask : oldMask & ~derivedMask;
349     if (newMask)
350         m_domBreakpoints.set(node, newMask);
351     else
352         m_domBreakpoints.remove(node);
353
354     uint32_t newRootMask = rootMask & ~newMask;
355     if (!newRootMask)
356         return;
357
358     for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child))
359         updateSubtreeBreakpoints(child, newRootMask, set);
360 }
361
362 void InspectorDOMDebuggerAgent::pauseOnNativeEventIfNeeded(bool isDOMEvent, const String& eventName, bool synchronous)
363 {
364     String fullEventName = (isDOMEvent ? listenerEventCategoryType : instrumentationEventCategoryType) + eventName;
365     if (m_pauseInNextEventListener)
366         m_pauseInNextEventListener = false;
367     else {
368         if (!m_eventListenerBreakpoints.contains(fullEventName))
369             return;
370     }
371
372     RefPtr<InspectorObject> eventData = InspectorObject::create();
373     eventData->setString("eventName", fullEventName);
374     if (synchronous)
375         m_debuggerAgent->breakProgram(InspectorDebuggerFrontendDispatcher::Reason::EventListener, eventData.release());
376     else
377         m_debuggerAgent->schedulePauseOnNextStatement(InspectorDebuggerFrontendDispatcher::Reason::EventListener, eventData.release());
378 }
379
380 void InspectorDOMDebuggerAgent::setXHRBreakpoint(ErrorString*, const String& url)
381 {
382     if (url.isEmpty()) {
383         m_pauseOnAllXHRsEnabled = true;
384         return;
385     }
386
387     m_xhrBreakpoints.add(url);
388 }
389
390 void InspectorDOMDebuggerAgent::removeXHRBreakpoint(ErrorString*, const String& url)
391 {
392     if (url.isEmpty()) {
393         m_pauseOnAllXHRsEnabled = false;
394         return;
395     }
396
397     m_xhrBreakpoints.remove(url);
398 }
399
400 void InspectorDOMDebuggerAgent::willSendXMLHttpRequest(const String& url)
401 {
402     String breakpointURL;
403     if (m_pauseOnAllXHRsEnabled)
404         breakpointURL = "";
405     else {
406         for (auto it = m_xhrBreakpoints.begin(), end = m_xhrBreakpoints.end(); it != end; ++it) {
407             if (url.contains(*it)) {
408                 breakpointURL = *it;
409                 break;
410             }
411         }
412     }
413
414     if (breakpointURL.isNull())
415         return;
416
417     RefPtr<InspectorObject> eventData = InspectorObject::create();
418     eventData->setString("breakpointURL", breakpointURL);
419     eventData->setString("url", url);
420     m_debuggerAgent->breakProgram(InspectorDebuggerFrontendDispatcher::Reason::XHR, eventData.release());
421 }
422
423 void InspectorDOMDebuggerAgent::clear()
424 {
425     m_domBreakpoints.clear();
426     m_pauseInNextEventListener = false;
427 }
428
429 } // namespace WebCore
430
431 #endif // ENABLE(INSPECTOR) && ENABLE(JAVASCRIPT_DEBUGGER)