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
49namespace {
50
51enum DOMBreakpointType {
52 SubtreeModified,
53 AttributeModified,
54 NodeRemoved,
55 DOMBreakpointTypesCount
56};
57
58const uint32_t inheritableDOMBreakpointTypesMask = (1 << SubtreeModified);
59const int domBreakpointDerivedTypeShift = 16;
60
61}
62
63
64namespace WebCore {
65
66using namespace Inspector;
67
68InspectorDOMDebuggerAgent::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
77InspectorDOMDebuggerAgent::~InspectorDOMDebuggerAgent()
78{
79 ASSERT(!m_debuggerAgent);
80 ASSERT(!m_instrumentingAgents.inspectorDOMDebuggerAgent());
81}
82
83// Browser debugger agent enabled only when JS debugger is enabled.
84void InspectorDOMDebuggerAgent::debuggerWasEnabled()
85{
86 m_instrumentingAgents.setInspectorDOMDebuggerAgent(this);
87}
88
89void InspectorDOMDebuggerAgent::debuggerWasDisabled()
90{
91 disable();
92}
93
94void InspectorDOMDebuggerAgent::disable()
95{
96 m_instrumentingAgents.setInspectorDOMDebuggerAgent(nullptr);
97 discardBindings();
98 m_eventBreakpoints.clear();
99 m_urlBreakpoints.clear();
100 m_pauseOnAllURLsEnabled = false;
101}
102
103void InspectorDOMDebuggerAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*)
104{
105}
106
107void InspectorDOMDebuggerAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason)
108{
109 disable();
110}
111
112void InspectorDOMDebuggerAgent::discardAgent()
113{
114 m_debuggerAgent->removeListener(*this);
115 m_debuggerAgent = nullptr;
116}
117
118void InspectorDOMDebuggerAgent::frameDocumentUpdated(Frame& frame)
119{
120 if (!frame.isMainFrame())
121 return;
122
123 discardBindings();
124}
125
126void InspectorDOMDebuggerAgent::discardBindings()
127{
128 m_domBreakpoints.clear();
129}
130
131void 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
152void 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
173void InspectorDOMDebuggerAgent::willInvalidateStyleAttr(Element& element)
174{
175 if (!m_debuggerAgent->breakpointsActive())
176 return;
177
178 if (hasBreakpoint(&element, AttributeModified)) {
179 Ref<JSON::Object> eventData = JSON::Object::create();
180 descriptionForDOMEvent(element, AttributeModified, false, eventData.get());
181 m_debuggerAgent->breakProgram(Inspector::DebuggerFrontendDispatcher::Reason::DOM, WTFMove(eventData));
182 }
183}
184
185void 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
195void 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
213static 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
225static 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
236void 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
260void 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
289void 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
301void 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
318void 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
330void 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
370bool 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
377void 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
395void 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
430void InspectorDOMDebuggerAgent::didHandleEvent()
431{
432 m_injectedScriptManager.clearEventValue();
433}
434
435void 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
450void 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
465void 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
476void 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
486void 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
525void InspectorDOMDebuggerAgent::willSendXMLHttpRequest(const String& url)
526{
527 breakOnURLIfNeeded(url, URLBreakpointSource::XHR);
528}
529
530void InspectorDOMDebuggerAgent::willFetch(const String& url)
531{
532 breakOnURLIfNeeded(url, URLBreakpointSource::Fetch);
533}
534
535} // namespace WebCore
536