3f7d9c81e1422c40587033dbe894a0458b7b1635
[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 "JSEvent.h"
41 #include "RegisteredEventListener.h"
42 #include <JavaScriptCore/ContentSearchUtilities.h>
43 #include <JavaScriptCore/InjectedScript.h>
44 #include <JavaScriptCore/InjectedScriptManager.h>
45 #include <JavaScriptCore/InspectorFrontendDispatchers.h>
46 #include <JavaScriptCore/RegularExpression.h>
47 #include <wtf/JSONValues.h>
48
49 namespace {
50
51 enum DOMBreakpointType {
52     SubtreeModified,
53     AttributeModified,
54     NodeRemoved,
55     DOMBreakpointTypesCount
56 };
57
58 const uint32_t inheritableDOMBreakpointTypesMask = (1 << SubtreeModified);
59 const int domBreakpointDerivedTypeShift = 16;
60
61 }
62
63
64 namespace WebCore {
65
66 using namespace Inspector;
67
68 InspectorDOMDebuggerAgent::InspectorDOMDebuggerAgent(WebAgentContext& context, InspectorDebuggerAgent* debuggerAgent)
69     : InspectorAgentBase("DOMDebugger"_s, context)
70     , m_backendDispatcher(Inspector::DOMDebuggerBackendDispatcher::create(context.backendDispatcher, this))
71     , m_injectedScriptManager(context.injectedScriptManager)
72     , m_debuggerAgent(debuggerAgent)
73 {
74     m_debuggerAgent->addListener(*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::disable()
95 {
96     m_instrumentingAgents.setInspectorDOMDebuggerAgent(nullptr);
97     discardBindings();
98     m_eventBreakpoints.clear();
99     m_urlBreakpoints.clear();
100     m_pauseOnAllURLsEnabled = false;
101 }
102
103 void InspectorDOMDebuggerAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*)
104 {
105 }
106
107 void InspectorDOMDebuggerAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason)
108 {
109     disable();
110 }
111
112 void InspectorDOMDebuggerAgent::discardAgent()
113 {
114     m_debuggerAgent->removeListener(*this);
115     m_debuggerAgent = nullptr;
116 }
117
118 void InspectorDOMDebuggerAgent::frameDocumentUpdated(Frame& frame)
119 {
120     if (!frame.isMainFrame())
121         return;
122
123     discardBindings();
124 }
125
126 void InspectorDOMDebuggerAgent::discardBindings()
127 {
128     m_domBreakpoints.clear();
129 }
130
131 void InspectorDOMDebuggerAgent::setEventBreakpoint(ErrorString& error, const String& breakpointTypeString, const String& eventName)
132 {
133     if (breakpointTypeString.isEmpty()) {
134         error = "Event breakpoint type is empty"_s;
135         return;
136     }
137
138     auto breakpointType = Inspector::Protocol::InspectorHelpers::parseEnumValueFromString<Inspector::Protocol::DOMDebugger::EventBreakpointType>(breakpointTypeString);
139     if (!breakpointType) {
140         error = makeString("Unknown event breakpoint type: "_s, breakpointTypeString);
141         return;
142     }
143
144     if (eventName.isEmpty()) {
145         error = "Event name is empty"_s;
146         return;
147     }
148
149     m_eventBreakpoints.add(std::make_pair(*breakpointType, eventName));
150 }
151
152 void InspectorDOMDebuggerAgent::removeEventBreakpoint(ErrorString& error, const String& breakpointTypeString, const String& eventName)
153 {
154     if (breakpointTypeString.isEmpty()) {
155         error = "Event breakpoint type is empty"_s;
156         return;
157     }
158
159     auto breakpointType = Inspector::Protocol::InspectorHelpers::parseEnumValueFromString<Inspector::Protocol::DOMDebugger::EventBreakpointType>(breakpointTypeString);
160     if (!breakpointType) {
161         error = makeString("Unknown event breakpoint type: "_s, breakpointTypeString);
162         return;
163     }
164
165     if (eventName.isEmpty()) {
166         error = "Event name is empty"_s;
167         return;
168     }
169
170     m_eventBreakpoints.remove(std::make_pair(*breakpointType, eventName));
171 }
172
173 void InspectorDOMDebuggerAgent::didInvalidateStyleAttr(Node& node)
174 {
175     if (!m_debuggerAgent->breakpointsActive())
176         return;
177
178     if (hasBreakpoint(&node, AttributeModified)) {
179         Ref<JSON::Object> eventData = JSON::Object::create();
180         descriptionForDOMEvent(node, AttributeModified, false, eventData.get());
181         m_debuggerAgent->breakProgram(Inspector::DebuggerFrontendDispatcher::Reason::DOM, WTFMove(eventData));
182     }
183 }
184
185 void InspectorDOMDebuggerAgent::didInsertDOMNode(Node& node)
186 {
187     if (m_domBreakpoints.size()) {
188         uint32_t mask = m_domBreakpoints.get(InspectorDOMAgent::innerParentNode(&node));
189         uint32_t inheritableTypesMask = (mask | (mask >> domBreakpointDerivedTypeShift)) & inheritableDOMBreakpointTypesMask;
190         if (inheritableTypesMask)
191             updateSubtreeBreakpoints(&node, inheritableTypesMask, true);
192     }
193 }
194
195 void InspectorDOMDebuggerAgent::didRemoveDOMNode(Node& node)
196 {
197     if (m_domBreakpoints.size()) {
198         // Remove subtree breakpoints.
199         m_domBreakpoints.remove(&node);
200         Vector<Node*> stack(1, InspectorDOMAgent::innerFirstChild(&node));
201         do {
202             Node* node = stack.last();
203             stack.removeLast();
204             if (!node)
205                 continue;
206             m_domBreakpoints.remove(node);
207             stack.append(InspectorDOMAgent::innerFirstChild(node));
208             stack.append(InspectorDOMAgent::innerNextSibling(node));
209         } while (!stack.isEmpty());
210     }
211 }
212
213 static int domTypeForName(ErrorString& errorString, const String& typeString)
214 {
215     if (typeString == "subtree-modified")
216         return SubtreeModified;
217     if (typeString == "attribute-modified")
218         return AttributeModified;
219     if (typeString == "node-removed")
220         return NodeRemoved;
221     errorString = makeString("Unknown DOM breakpoint type: ", typeString);
222     return -1;
223 }
224
225 static String domTypeName(int type)
226 {
227     switch (type) {
228     case SubtreeModified: return "subtree-modified"_s;
229     case AttributeModified: return "attribute-modified"_s;
230     case NodeRemoved: return "node-removed"_s;
231     default: break;
232     }
233     return emptyString();
234 }
235
236 void InspectorDOMDebuggerAgent::setDOMBreakpoint(ErrorString& errorString, int nodeId, const String& typeString)
237 {
238     auto* domAgent = m_instrumentingAgents.inspectorDOMAgent();
239     if (!domAgent) {
240         errorString = "Missing DOM agent"_s;
241         return;
242     }
243
244     Node* node = domAgent->assertNode(errorString, nodeId);
245     if (!node)
246         return;
247
248     int type = domTypeForName(errorString, typeString);
249     if (type == -1)
250         return;
251
252     uint32_t rootBit = 1 << type;
253     m_domBreakpoints.set(node, m_domBreakpoints.get(node) | rootBit);
254     if (rootBit & inheritableDOMBreakpointTypesMask) {
255         for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child))
256             updateSubtreeBreakpoints(child, rootBit, true);
257     }
258 }
259
260 void InspectorDOMDebuggerAgent::removeDOMBreakpoint(ErrorString& errorString, int nodeId, const String& typeString)
261 {
262     auto* domAgent = m_instrumentingAgents.inspectorDOMAgent();
263     if (!domAgent) {
264         errorString = "Missing DOM agent"_s;
265         return;
266     }
267
268     Node* node = domAgent->assertNode(errorString, nodeId);
269     if (!node)
270         return;
271
272     int type = domTypeForName(errorString, typeString);
273     if (type == -1)
274         return;
275
276     uint32_t rootBit = 1 << type;
277     uint32_t mask = m_domBreakpoints.get(node) & ~rootBit;
278     if (mask)
279         m_domBreakpoints.set(node, mask);
280     else
281         m_domBreakpoints.remove(node);
282
283     if ((rootBit & inheritableDOMBreakpointTypesMask) && !(mask & (rootBit << domBreakpointDerivedTypeShift))) {
284         for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child))
285             updateSubtreeBreakpoints(child, rootBit, false);
286     }
287 }
288
289 void InspectorDOMDebuggerAgent::willInsertDOMNode(Node& parent)
290 {
291     if (!m_debuggerAgent->breakpointsActive())
292         return;
293
294     if (hasBreakpoint(&parent, SubtreeModified)) {
295         Ref<JSON::Object> eventData = JSON::Object::create();
296         descriptionForDOMEvent(parent, SubtreeModified, true, eventData.get());
297         m_debuggerAgent->breakProgram(Inspector::DebuggerFrontendDispatcher::Reason::DOM, WTFMove(eventData));
298     }
299 }
300
301 void InspectorDOMDebuggerAgent::willRemoveDOMNode(Node& node)
302 {
303     if (!m_debuggerAgent->breakpointsActive())
304         return;
305
306     Node* parentNode = InspectorDOMAgent::innerParentNode(&node);
307     if (hasBreakpoint(&node, NodeRemoved)) {
308         Ref<JSON::Object> eventData = JSON::Object::create();
309         descriptionForDOMEvent(node, NodeRemoved, false, eventData.get());
310         m_debuggerAgent->breakProgram(Inspector::DebuggerFrontendDispatcher::Reason::DOM, WTFMove(eventData));
311     } else if (parentNode && hasBreakpoint(parentNode, SubtreeModified)) {
312         Ref<JSON::Object> eventData = JSON::Object::create();
313         descriptionForDOMEvent(node, SubtreeModified, false, eventData.get());
314         m_debuggerAgent->breakProgram(Inspector::DebuggerFrontendDispatcher::Reason::DOM, WTFMove(eventData));
315     }
316 }
317
318 void InspectorDOMDebuggerAgent::willModifyDOMAttr(Element& element)
319 {
320     if (!m_debuggerAgent->breakpointsActive())
321         return;
322
323     if (hasBreakpoint(&element, AttributeModified)) {
324         Ref<JSON::Object> eventData = JSON::Object::create();
325         descriptionForDOMEvent(element, AttributeModified, false, eventData.get());
326         m_debuggerAgent->breakProgram(Inspector::DebuggerFrontendDispatcher::Reason::DOM, WTFMove(eventData));
327     }
328 }
329
330 void InspectorDOMDebuggerAgent::descriptionForDOMEvent(Node& target, int breakpointType, bool insertion, JSON::Object& description)
331 {
332     ASSERT(m_debuggerAgent->breakpointsActive());
333     ASSERT(hasBreakpoint(&target, breakpointType));
334
335     auto* domAgent = m_instrumentingAgents.inspectorDOMAgent();
336
337     Node* breakpointOwner = &target;
338     if ((1 << breakpointType) & inheritableDOMBreakpointTypesMask) {
339         if (domAgent) {
340             // For inheritable breakpoint types, target node isn't always the same as the node that owns a breakpoint.
341             // Target node may be unknown to frontend, so we need to push it first.
342             RefPtr<Inspector::Protocol::Runtime::RemoteObject> targetNodeObject = domAgent->resolveNode(&target, InspectorDebuggerAgent::backtraceObjectGroup);
343             description.setValue("targetNode", targetNodeObject);
344         }
345
346         // Find breakpoint owner node.
347         if (!insertion)
348             breakpointOwner = InspectorDOMAgent::innerParentNode(&target);
349         ASSERT(breakpointOwner);
350         while (!(m_domBreakpoints.get(breakpointOwner) & (1 << breakpointType))) {
351             Node* parentNode = InspectorDOMAgent::innerParentNode(breakpointOwner);
352             if (!parentNode)
353                 break;
354             breakpointOwner = parentNode;
355         }
356
357         if (breakpointType == SubtreeModified)
358             description.setBoolean("insertion", insertion);
359     }
360
361     if (domAgent) {
362         int breakpointOwnerNodeId = domAgent->boundNodeId(breakpointOwner);
363         ASSERT(breakpointOwnerNodeId);
364         description.setInteger("nodeId", breakpointOwnerNodeId);
365     }
366
367     description.setString("type", domTypeName(breakpointType));
368 }
369
370 bool InspectorDOMDebuggerAgent::hasBreakpoint(Node* node, int type)
371 {
372     uint32_t rootBit = 1 << type;
373     uint32_t derivedBit = rootBit << domBreakpointDerivedTypeShift;
374     return m_domBreakpoints.get(node) & (rootBit | derivedBit);
375 }
376
377 void InspectorDOMDebuggerAgent::updateSubtreeBreakpoints(Node* node, uint32_t rootMask, bool set)
378 {
379     uint32_t oldMask = m_domBreakpoints.get(node);
380     uint32_t derivedMask = rootMask << domBreakpointDerivedTypeShift;
381     uint32_t newMask = set ? oldMask | derivedMask : oldMask & ~derivedMask;
382     if (newMask)
383         m_domBreakpoints.set(node, newMask);
384     else
385         m_domBreakpoints.remove(node);
386
387     uint32_t newRootMask = rootMask & ~newMask;
388     if (!newRootMask)
389         return;
390
391     for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child))
392         updateSubtreeBreakpoints(child, newRootMask, set);
393 }
394
395 void InspectorDOMDebuggerAgent::willHandleEvent(Event& event, const RegisteredEventListener& registeredEventListener)
396 {
397     if (!m_debuggerAgent->breakpointsActive())
398         return;
399
400     auto state = event.target()->scriptExecutionContext()->execState();
401     auto injectedScript = m_injectedScriptManager.injectedScriptFor(state);
402     ASSERT(!injectedScript.hasNoValue());
403     {
404         JSC::JSLockHolder lock(state);
405
406         injectedScript.setEventValue(toJS(state, deprecatedGlobalObjectForPrototype(state), event));
407     }
408
409     auto* domAgent = m_instrumentingAgents.inspectorDOMAgent();
410
411     bool shouldPause = m_debuggerAgent->pauseOnNextStatementEnabled() || m_eventBreakpoints.contains(std::make_pair(Inspector::Protocol::DOMDebugger::EventBreakpointType::Listener, event.type()));
412
413     if (!shouldPause && domAgent)
414         shouldPause = domAgent->hasBreakpointForEventListener(*event.currentTarget(), event.type(), registeredEventListener.callback(), registeredEventListener.useCapture());
415
416     if (!shouldPause)
417         return;
418
419     Ref<JSON::Object> eventData = JSON::Object::create();
420     eventData->setString("eventName"_s, event.type());
421     if (domAgent) {
422         int eventListenerId = domAgent->idForEventListener(*event.currentTarget(), event.type(), registeredEventListener.callback(), registeredEventListener.useCapture());
423         if (eventListenerId)
424             eventData->setInteger("eventListenerId"_s, eventListenerId);
425     }
426
427     m_debuggerAgent->schedulePauseOnNextStatement(Inspector::DebuggerFrontendDispatcher::Reason::EventListener, WTFMove(eventData));
428 }
429
430 void InspectorDOMDebuggerAgent::didHandleEvent()
431 {
432     m_injectedScriptManager.clearEventValue();
433 }
434
435 void InspectorDOMDebuggerAgent::willFireTimer(bool oneShot)
436 {
437     if (!m_debuggerAgent->breakpointsActive())
438         return;
439
440     String eventName = oneShot ? "setTimeout"_s : "setInterval"_s;
441     bool shouldPause = m_debuggerAgent->pauseOnNextStatementEnabled() || m_eventBreakpoints.contains(std::make_pair(Inspector::Protocol::DOMDebugger::EventBreakpointType::Timer, eventName));
442     if (!shouldPause)
443         return;
444
445     Ref<JSON::Object> eventData = JSON::Object::create();
446     eventData->setString("eventName"_s, eventName);
447     m_debuggerAgent->schedulePauseOnNextStatement(Inspector::DebuggerFrontendDispatcher::Reason::Timer, WTFMove(eventData));
448 }
449
450 void InspectorDOMDebuggerAgent::willFireAnimationFrame()
451 {
452     if (!m_debuggerAgent->breakpointsActive())
453         return;
454
455     String eventName = "requestAnimationFrame"_s;
456     bool shouldPause = m_debuggerAgent->pauseOnNextStatementEnabled() || m_eventBreakpoints.contains(std::make_pair(Inspector::Protocol::DOMDebugger::EventBreakpointType::AnimationFrame, eventName));
457     if (!shouldPause)
458         return;
459
460     Ref<JSON::Object> eventData = JSON::Object::create();
461     eventData->setString("eventName"_s, eventName);
462     m_debuggerAgent->schedulePauseOnNextStatement(Inspector::DebuggerFrontendDispatcher::Reason::AnimationFrame, WTFMove(eventData));
463 }
464
465 void InspectorDOMDebuggerAgent::setURLBreakpoint(ErrorString&, const String& url, const bool* optionalIsRegex)
466 {
467     if (url.isEmpty()) {
468         m_pauseOnAllURLsEnabled = true;
469         return;
470     }
471
472     bool isRegex = optionalIsRegex ? *optionalIsRegex : false;
473     m_urlBreakpoints.set(url, isRegex ? URLBreakpointType::RegularExpression : URLBreakpointType::Text);
474 }
475
476 void InspectorDOMDebuggerAgent::removeURLBreakpoint(ErrorString&, const String& url)
477 {
478     if (url.isEmpty()) {
479         m_pauseOnAllURLsEnabled = false;
480         return;
481     }
482
483     m_urlBreakpoints.remove(url);
484 }
485
486 void InspectorDOMDebuggerAgent::breakOnURLIfNeeded(const String& url, URLBreakpointSource source)
487 {
488     if (!m_debuggerAgent->breakpointsActive())
489         return;
490
491     String breakpointURL;
492     if (m_pauseOnAllURLsEnabled)
493         breakpointURL = emptyString();
494     else {
495         for (auto& entry : m_urlBreakpoints) {
496             const auto& query = entry.key;
497             bool isRegex = entry.value == URLBreakpointType::RegularExpression;
498             auto regex = ContentSearchUtilities::createSearchRegex(query, false, isRegex);
499             if (regex.match(url) != -1) {
500                 breakpointURL = query;
501                 break;
502             }
503         }
504     }
505
506     if (breakpointURL.isNull())
507         return;
508
509     Inspector::DebuggerFrontendDispatcher::Reason breakReason;
510     if (source == URLBreakpointSource::Fetch)
511         breakReason = Inspector::DebuggerFrontendDispatcher::Reason::Fetch;
512     else if (source == URLBreakpointSource::XHR)
513         breakReason = Inspector::DebuggerFrontendDispatcher::Reason::XHR;
514     else {
515         ASSERT_NOT_REACHED();
516         breakReason = Inspector::DebuggerFrontendDispatcher::Reason::Other;
517     }
518
519     Ref<JSON::Object> eventData = JSON::Object::create();
520     eventData->setString("breakpointURL", breakpointURL);
521     eventData->setString("url", url);
522     m_debuggerAgent->breakProgram(breakReason, WTFMove(eventData));
523 }
524
525 void InspectorDOMDebuggerAgent::willSendXMLHttpRequest(const String& url)
526 {
527     breakOnURLIfNeeded(url, URLBreakpointSource::XHR);
528 }
529
530 void InspectorDOMDebuggerAgent::willFetch(const String& url)
531 {
532     breakOnURLIfNeeded(url, URLBreakpointSource::Fetch);
533 }
534
535 } // namespace WebCore