5213bdd2856d24cac72fb57845c3fd93303bf609
[WebKit-https.git] / Source / WebCore / inspector / agents / InspectorDOMDebuggerAgent.cpp
1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  * Copyright (C) 2015 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include "config.h"
33 #include "InspectorDOMDebuggerAgent.h"
34
35 #include "Event.h"
36 #include "Frame.h"
37 #include "HTMLElement.h"
38 #include "InspectorDOMAgent.h"
39 #include "InstrumentingAgents.h"
40 #include "RegisteredEventListener.h"
41 #include <JavaScriptCore/ContentSearchUtilities.h>
42 #include <JavaScriptCore/InspectorFrontendDispatchers.h>
43 #include <JavaScriptCore/RegularExpression.h>
44 #include <wtf/JSONValues.h>
45
46 namespace {
47
48 enum DOMBreakpointType {
49     SubtreeModified,
50     AttributeModified,
51     NodeRemoved,
52     DOMBreakpointTypesCount
53 };
54
55 const uint32_t inheritableDOMBreakpointTypesMask = (1 << SubtreeModified);
56 const int domBreakpointDerivedTypeShift = 16;
57
58 }
59
60
61 namespace WebCore {
62
63 using namespace Inspector;
64
65 InspectorDOMDebuggerAgent::InspectorDOMDebuggerAgent(WebAgentContext& context, InspectorDOMAgent* domAgent, InspectorDebuggerAgent* debuggerAgent)
66     : InspectorAgentBase("DOMDebugger"_s, context)
67     , m_backendDispatcher(Inspector::DOMDebuggerBackendDispatcher::create(context.backendDispatcher, this))
68     , m_domAgent(domAgent)
69     , m_debuggerAgent(debuggerAgent)
70 {
71     m_debuggerAgent->setListener(this);
72 }
73
74 InspectorDOMDebuggerAgent::~InspectorDOMDebuggerAgent()
75 {
76     ASSERT(!m_debuggerAgent);
77     ASSERT(!m_instrumentingAgents.inspectorDOMDebuggerAgent());
78 }
79
80 // Browser debugger agent enabled only when JS debugger is enabled.
81 void InspectorDOMDebuggerAgent::debuggerWasEnabled()
82 {
83     m_instrumentingAgents.setInspectorDOMDebuggerAgent(this);
84 }
85
86 void InspectorDOMDebuggerAgent::debuggerWasDisabled()
87 {
88     disable();
89 }
90
91 void InspectorDOMDebuggerAgent::disable()
92 {
93     m_instrumentingAgents.setInspectorDOMDebuggerAgent(nullptr);
94     discardBindings();
95     m_eventBreakpoints.clear();
96     m_urlBreakpoints.clear();
97     m_pauseOnAllURLsEnabled = false;
98 }
99
100 void InspectorDOMDebuggerAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*)
101 {
102 }
103
104 void InspectorDOMDebuggerAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason)
105 {
106     disable();
107 }
108
109 void InspectorDOMDebuggerAgent::discardAgent()
110 {
111     m_debuggerAgent->setListener(nullptr);
112     m_debuggerAgent = nullptr;
113 }
114
115 void InspectorDOMDebuggerAgent::frameDocumentUpdated(Frame& frame)
116 {
117     if (!frame.isMainFrame())
118         return;
119
120     discardBindings();
121 }
122
123 void InspectorDOMDebuggerAgent::discardBindings()
124 {
125     m_domBreakpoints.clear();
126 }
127
128 void InspectorDOMDebuggerAgent::setEventBreakpoint(ErrorString& error, const String& breakpointTypeString, const String& eventName)
129 {
130     if (breakpointTypeString.isEmpty()) {
131         error = "Event breakpoint type is empty"_s;
132         return;
133     }
134
135     auto breakpointType = Inspector::Protocol::InspectorHelpers::parseEnumValueFromString<Inspector::Protocol::DOMDebugger::EventBreakpointType>(breakpointTypeString);
136     if (!breakpointType) {
137         error = makeString("Unknown event breakpoint type: "_s, breakpointTypeString);
138         return;
139     }
140
141     if (eventName.isEmpty()) {
142         error = "Event name is empty"_s;
143         return;
144     }
145
146     m_eventBreakpoints.add(std::make_pair(*breakpointType, eventName));
147 }
148
149 void InspectorDOMDebuggerAgent::removeEventBreakpoint(ErrorString& error, const String& breakpointTypeString, const String& eventName)
150 {
151     if (breakpointTypeString.isEmpty()) {
152         error = "Event breakpoint type is empty"_s;
153         return;
154     }
155
156     auto breakpointType = Inspector::Protocol::InspectorHelpers::parseEnumValueFromString<Inspector::Protocol::DOMDebugger::EventBreakpointType>(breakpointTypeString);
157     if (!breakpointType) {
158         error = makeString("Unknown event breakpoint type: "_s, breakpointTypeString);
159         return;
160     }
161
162     if (eventName.isEmpty()) {
163         error = "Event name is empty"_s;
164         return;
165     }
166
167     m_eventBreakpoints.remove(std::make_pair(*breakpointType, eventName));
168 }
169
170 void InspectorDOMDebuggerAgent::didInvalidateStyleAttr(Node& node)
171 {
172     if (hasBreakpoint(&node, AttributeModified)) {
173         Ref<JSON::Object> eventData = JSON::Object::create();
174         descriptionForDOMEvent(node, AttributeModified, false, eventData.get());
175         m_debuggerAgent->breakProgram(Inspector::DebuggerFrontendDispatcher::Reason::DOM, WTFMove(eventData));
176     }
177 }
178
179 void InspectorDOMDebuggerAgent::didInsertDOMNode(Node& node)
180 {
181     if (m_domBreakpoints.size()) {
182         uint32_t mask = m_domBreakpoints.get(InspectorDOMAgent::innerParentNode(&node));
183         uint32_t inheritableTypesMask = (mask | (mask >> domBreakpointDerivedTypeShift)) & inheritableDOMBreakpointTypesMask;
184         if (inheritableTypesMask)
185             updateSubtreeBreakpoints(&node, inheritableTypesMask, true);
186     }
187 }
188
189 void InspectorDOMDebuggerAgent::didRemoveDOMNode(Node& node)
190 {
191     if (m_domBreakpoints.size()) {
192         // Remove subtree breakpoints.
193         m_domBreakpoints.remove(&node);
194         Vector<Node*> stack(1, InspectorDOMAgent::innerFirstChild(&node));
195         do {
196             Node* node = stack.last();
197             stack.removeLast();
198             if (!node)
199                 continue;
200             m_domBreakpoints.remove(node);
201             stack.append(InspectorDOMAgent::innerFirstChild(node));
202             stack.append(InspectorDOMAgent::innerNextSibling(node));
203         } while (!stack.isEmpty());
204     }
205 }
206
207 static int domTypeForName(ErrorString& errorString, const String& typeString)
208 {
209     if (typeString == "subtree-modified")
210         return SubtreeModified;
211     if (typeString == "attribute-modified")
212         return AttributeModified;
213     if (typeString == "node-removed")
214         return NodeRemoved;
215     errorString = makeString("Unknown DOM breakpoint type: ", typeString);
216     return -1;
217 }
218
219 static String domTypeName(int type)
220 {
221     switch (type) {
222     case SubtreeModified: return "subtree-modified"_s;
223     case AttributeModified: return "attribute-modified"_s;
224     case NodeRemoved: return "node-removed"_s;
225     default: break;
226     }
227     return emptyString();
228 }
229
230 void InspectorDOMDebuggerAgent::setDOMBreakpoint(ErrorString& errorString, int nodeId, const String& typeString)
231 {
232     Node* node = m_domAgent->assertNode(errorString, nodeId);
233     if (!node)
234         return;
235
236     int type = domTypeForName(errorString, typeString);
237     if (type == -1)
238         return;
239
240     uint32_t rootBit = 1 << type;
241     m_domBreakpoints.set(node, m_domBreakpoints.get(node) | rootBit);
242     if (rootBit & inheritableDOMBreakpointTypesMask) {
243         for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child))
244             updateSubtreeBreakpoints(child, rootBit, true);
245     }
246 }
247
248 void InspectorDOMDebuggerAgent::removeDOMBreakpoint(ErrorString& errorString, int nodeId, const String& typeString)
249 {
250     Node* node = m_domAgent->assertNode(errorString, nodeId);
251     if (!node)
252         return;
253     int type = domTypeForName(errorString, typeString);
254     if (type == -1)
255         return;
256
257     uint32_t rootBit = 1 << type;
258     uint32_t mask = m_domBreakpoints.get(node) & ~rootBit;
259     if (mask)
260         m_domBreakpoints.set(node, mask);
261     else
262         m_domBreakpoints.remove(node);
263
264     if ((rootBit & inheritableDOMBreakpointTypesMask) && !(mask & (rootBit << domBreakpointDerivedTypeShift))) {
265         for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child))
266             updateSubtreeBreakpoints(child, rootBit, false);
267     }
268 }
269
270 void InspectorDOMDebuggerAgent::willInsertDOMNode(Node& parent)
271 {
272     if (!m_debuggerAgent->breakpointsActive())
273         return;
274
275     if (hasBreakpoint(&parent, SubtreeModified)) {
276         Ref<JSON::Object> eventData = JSON::Object::create();
277         descriptionForDOMEvent(parent, SubtreeModified, true, eventData.get());
278         m_debuggerAgent->breakProgram(Inspector::DebuggerFrontendDispatcher::Reason::DOM, WTFMove(eventData));
279     }
280 }
281
282 void InspectorDOMDebuggerAgent::willRemoveDOMNode(Node& node)
283 {
284     if (!m_debuggerAgent->breakpointsActive())
285         return;
286
287     Node* parentNode = InspectorDOMAgent::innerParentNode(&node);
288     if (hasBreakpoint(&node, NodeRemoved)) {
289         Ref<JSON::Object> eventData = JSON::Object::create();
290         descriptionForDOMEvent(node, NodeRemoved, false, eventData.get());
291         m_debuggerAgent->breakProgram(Inspector::DebuggerFrontendDispatcher::Reason::DOM, WTFMove(eventData));
292     } else if (parentNode && hasBreakpoint(parentNode, SubtreeModified)) {
293         Ref<JSON::Object> eventData = JSON::Object::create();
294         descriptionForDOMEvent(node, SubtreeModified, false, eventData.get());
295         m_debuggerAgent->breakProgram(Inspector::DebuggerFrontendDispatcher::Reason::DOM, WTFMove(eventData));
296     }
297 }
298
299 void InspectorDOMDebuggerAgent::willModifyDOMAttr(Element& element)
300 {
301     if (!m_debuggerAgent->breakpointsActive())
302         return;
303
304     if (hasBreakpoint(&element, AttributeModified)) {
305         Ref<JSON::Object> eventData = JSON::Object::create();
306         descriptionForDOMEvent(element, AttributeModified, false, eventData.get());
307         m_debuggerAgent->breakProgram(Inspector::DebuggerFrontendDispatcher::Reason::DOM, WTFMove(eventData));
308     }
309 }
310
311 void InspectorDOMDebuggerAgent::descriptionForDOMEvent(Node& target, int breakpointType, bool insertion, JSON::Object& description)
312 {
313     ASSERT(hasBreakpoint(&target, breakpointType));
314
315     Node* breakpointOwner = &target;
316     if ((1 << breakpointType) & inheritableDOMBreakpointTypesMask) {
317         // For inheritable breakpoint types, target node isn't always the same as the node that owns a breakpoint.
318         // Target node may be unknown to frontend, so we need to push it first.
319         RefPtr<Inspector::Protocol::Runtime::RemoteObject> targetNodeObject = m_domAgent->resolveNode(&target, InspectorDebuggerAgent::backtraceObjectGroup);
320         description.setValue("targetNode", targetNodeObject);
321
322         // Find breakpoint owner node.
323         if (!insertion)
324             breakpointOwner = InspectorDOMAgent::innerParentNode(&target);
325         ASSERT(breakpointOwner);
326         while (!(m_domBreakpoints.get(breakpointOwner) & (1 << breakpointType))) {
327             Node* parentNode = InspectorDOMAgent::innerParentNode(breakpointOwner);
328             if (!parentNode)
329                 break;
330             breakpointOwner = parentNode;
331         }
332
333         if (breakpointType == SubtreeModified)
334             description.setBoolean("insertion", insertion);
335     }
336
337     int breakpointOwnerNodeId = m_domAgent->boundNodeId(breakpointOwner);
338     ASSERT(breakpointOwnerNodeId);
339     description.setInteger("nodeId", breakpointOwnerNodeId);
340     description.setString("type", domTypeName(breakpointType));
341 }
342
343 bool InspectorDOMDebuggerAgent::hasBreakpoint(Node* node, int type)
344 {
345     uint32_t rootBit = 1 << type;
346     uint32_t derivedBit = rootBit << domBreakpointDerivedTypeShift;
347     return m_domBreakpoints.get(node) & (rootBit | derivedBit);
348 }
349
350 void InspectorDOMDebuggerAgent::updateSubtreeBreakpoints(Node* node, uint32_t rootMask, bool set)
351 {
352     uint32_t oldMask = m_domBreakpoints.get(node);
353     uint32_t derivedMask = rootMask << domBreakpointDerivedTypeShift;
354     uint32_t newMask = set ? oldMask | derivedMask : oldMask & ~derivedMask;
355     if (newMask)
356         m_domBreakpoints.set(node, newMask);
357     else
358         m_domBreakpoints.remove(node);
359
360     uint32_t newRootMask = rootMask & ~newMask;
361     if (!newRootMask)
362         return;
363
364     for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child))
365         updateSubtreeBreakpoints(child, newRootMask, set);
366 }
367
368 void InspectorDOMDebuggerAgent::willHandleEvent(const Event& event, const RegisteredEventListener& registeredEventListener)
369 {
370     if (!m_debuggerAgent->breakpointsActive())
371         return;
372
373     bool shouldPause = m_debuggerAgent->pauseOnNextStatementEnabled() || m_eventBreakpoints.contains(std::make_pair(Inspector::Protocol::DOMDebugger::EventBreakpointType::Listener, event.type()));
374
375     if (!shouldPause && m_domAgent)
376         shouldPause = m_domAgent->hasBreakpointForEventListener(*event.currentTarget(), event.type(), registeredEventListener.callback(), registeredEventListener.useCapture());
377
378     if (!shouldPause)
379         return;
380
381     Ref<JSON::Object> eventData = JSON::Object::create();
382     eventData->setString("eventName"_s, event.type());
383     if (m_domAgent) {
384         int eventListenerId = m_domAgent->idForEventListener(*event.currentTarget(), event.type(), registeredEventListener.callback(), registeredEventListener.useCapture());
385         if (eventListenerId)
386             eventData->setInteger("eventListenerId"_s, eventListenerId);
387     }
388
389     m_debuggerAgent->schedulePauseOnNextStatement(Inspector::DebuggerFrontendDispatcher::Reason::EventListener, WTFMove(eventData));
390 }
391
392 void InspectorDOMDebuggerAgent::willFireTimer(bool oneShot)
393 {
394     if (!m_debuggerAgent->breakpointsActive())
395         return;
396
397     String eventName = oneShot ? "setTimeout"_s : "setInterval"_s;
398     bool shouldPause = m_debuggerAgent->pauseOnNextStatementEnabled() || m_eventBreakpoints.contains(std::make_pair(Inspector::Protocol::DOMDebugger::EventBreakpointType::Timer, eventName));
399     if (!shouldPause)
400         return;
401
402     Ref<JSON::Object> eventData = JSON::Object::create();
403     eventData->setString("eventName"_s, eventName);
404     m_debuggerAgent->schedulePauseOnNextStatement(Inspector::DebuggerFrontendDispatcher::Reason::Timer, WTFMove(eventData));
405 }
406
407 void InspectorDOMDebuggerAgent::willFireAnimationFrame()
408 {
409     if (!m_debuggerAgent->breakpointsActive())
410         return;
411
412     String eventName = "requestAnimationFrame"_s;
413     bool shouldPause = m_debuggerAgent->pauseOnNextStatementEnabled() || m_eventBreakpoints.contains(std::make_pair(Inspector::Protocol::DOMDebugger::EventBreakpointType::AnimationFrame, eventName));
414     if (!shouldPause)
415         return;
416
417     Ref<JSON::Object> eventData = JSON::Object::create();
418     eventData->setString("eventName"_s, eventName);
419     m_debuggerAgent->schedulePauseOnNextStatement(Inspector::DebuggerFrontendDispatcher::Reason::AnimationFrame, WTFMove(eventData));
420 }
421
422 void InspectorDOMDebuggerAgent::setURLBreakpoint(ErrorString&, const String& url, const bool* optionalIsRegex)
423 {
424     if (url.isEmpty()) {
425         m_pauseOnAllURLsEnabled = true;
426         return;
427     }
428
429     bool isRegex = optionalIsRegex ? *optionalIsRegex : false;
430     m_urlBreakpoints.set(url, isRegex ? URLBreakpointType::RegularExpression : URLBreakpointType::Text);
431 }
432
433 void InspectorDOMDebuggerAgent::removeURLBreakpoint(ErrorString&, const String& url)
434 {
435     if (url.isEmpty()) {
436         m_pauseOnAllURLsEnabled = false;
437         return;
438     }
439
440     m_urlBreakpoints.remove(url);
441 }
442
443 void InspectorDOMDebuggerAgent::breakOnURLIfNeeded(const String& url, URLBreakpointSource source)
444 {
445     if (!m_debuggerAgent->breakpointsActive())
446         return;
447
448     String breakpointURL;
449     if (m_pauseOnAllURLsEnabled)
450         breakpointURL = emptyString();
451     else {
452         for (auto& entry : m_urlBreakpoints) {
453             const auto& query = entry.key;
454             bool isRegex = entry.value == URLBreakpointType::RegularExpression;
455             auto regex = ContentSearchUtilities::createSearchRegex(query, false, isRegex);
456             if (regex.match(url) != -1) {
457                 breakpointURL = query;
458                 break;
459             }
460         }
461     }
462
463     if (breakpointURL.isNull())
464         return;
465
466     Inspector::DebuggerFrontendDispatcher::Reason breakReason;
467     if (source == URLBreakpointSource::Fetch)
468         breakReason = Inspector::DebuggerFrontendDispatcher::Reason::Fetch;
469     else if (source == URLBreakpointSource::XHR)
470         breakReason = Inspector::DebuggerFrontendDispatcher::Reason::XHR;
471     else {
472         ASSERT_NOT_REACHED();
473         breakReason = Inspector::DebuggerFrontendDispatcher::Reason::Other;
474     }
475
476     Ref<JSON::Object> eventData = JSON::Object::create();
477     eventData->setString("breakpointURL", breakpointURL);
478     eventData->setString("url", url);
479     m_debuggerAgent->breakProgram(breakReason, WTFMove(eventData));
480 }
481
482 void InspectorDOMDebuggerAgent::willSendXMLHttpRequest(const String& url)
483 {
484     breakOnURLIfNeeded(url, URLBreakpointSource::XHR);
485 }
486
487 void InspectorDOMDebuggerAgent::willFetch(const String& url)
488 {
489     breakOnURLIfNeeded(url, URLBreakpointSource::Fetch);
490 }
491
492 } // namespace WebCore