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