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 #include "core/inspector/InspectorDOMDebuggerAgent.h" 33 34 #include "InspectorFrontend.h" 35 #include "core/dom/Event.h" 36 #include "core/inspector/InspectorDOMAgent.h" 37 #include "core/inspector/InspectorDebuggerAgent.h" 38 #include "core/inspector/InspectorState.h" 39 #include "core/inspector/InstrumentingAgents.h" 40 #include "core/platform/JSONValues.h" 41 #include "wtf/text/WTFString.h" 42 43 namespace { 44 45 enum DOMBreakpointType { 46 SubtreeModified = 0, 47 AttributeModified, 48 NodeRemoved, 49 DOMBreakpointTypesCount 50 }; 51 52 static const char* const listenerEventCategoryType = "listener:"; 53 static const char* const instrumentationEventCategoryType = "instrumentation:"; 54 55 const uint32_t inheritableDOMBreakpointTypesMask = (1 << SubtreeModified); 56 const int domBreakpointDerivedTypeShift = 16; 57 58 } 59 60 namespace WebCore { 61 62 static const char* const requestAnimationFrameEventName = "requestAnimationFrame"; 63 static const char* const cancelAnimationFrameEventName = "cancelAnimationFrame"; 64 static const char* const animationFrameFiredEventName = "animationFrameFired"; 65 static const char* const setTimerEventName = "setTimer"; 66 static const char* const clearTimerEventName = "clearTimer"; 67 static const char* const timerFiredEventName = "timerFired"; 68 static const char* const webglErrorFiredEventName = "webglErrorFired"; 69 static const char* const webglWarningFiredEventName = "webglWarningFired"; 70 static const char* const webglErrorNameProperty = "webglErrorName"; 71 72 namespace DOMDebuggerAgentState { 73 static const char eventListenerBreakpoints[] = "eventListenerBreakpoints"; 74 static const char pauseOnAllXHRs[] = "pauseOnAllXHRs"; 75 static const char xhrBreakpoints[] = "xhrBreakpoints"; 76 } 77 78 PassOwnPtr<InspectorDOMDebuggerAgent> InspectorDOMDebuggerAgent::create(InstrumentingAgents* instrumentingAgents, InspectorCompositeState* inspectorState, InspectorDOMAgent* domAgent, InspectorDebuggerAgent* debuggerAgent) 79 { 80 return adoptPtr(new InspectorDOMDebuggerAgent(instrumentingAgents, inspectorState, domAgent, debuggerAgent)); 81 } 82 83 InspectorDOMDebuggerAgent::InspectorDOMDebuggerAgent(InstrumentingAgents* instrumentingAgents, InspectorCompositeState* inspectorState, InspectorDOMAgent* domAgent, InspectorDebuggerAgent* debuggerAgent) 84 : InspectorBaseAgent<InspectorDOMDebuggerAgent>("DOMDebugger", instrumentingAgents, inspectorState) 85 , m_domAgent(domAgent) 86 , m_debuggerAgent(debuggerAgent) 87 , m_pauseInNextEventListener(false) 88 { 89 m_debuggerAgent->setListener(this); 90 } 91 92 InspectorDOMDebuggerAgent::~InspectorDOMDebuggerAgent() 93 { 94 ASSERT(!m_debuggerAgent); 95 ASSERT(!m_instrumentingAgents->inspectorDOMDebuggerAgent()); 96 } 97 98 // Browser debugger agent enabled only when JS debugger is enabled. 99 void InspectorDOMDebuggerAgent::debuggerWasEnabled() 100 { 101 m_instrumentingAgents->setInspectorDOMDebuggerAgent(this); 102 } 103 104 void InspectorDOMDebuggerAgent::debuggerWasDisabled() 105 { 106 disable(); 107 } 108 109 void InspectorDOMDebuggerAgent::stepInto() 110 { 111 m_pauseInNextEventListener = true; 112 } 113 114 void InspectorDOMDebuggerAgent::didPause() 115 { 116 m_pauseInNextEventListener = false; 117 } 118 119 void InspectorDOMDebuggerAgent::didProcessTask() 120 { 121 if (!m_pauseInNextEventListener) 122 return; 123 if (m_debuggerAgent && m_debuggerAgent->runningNestedMessageLoop()) 124 return; 125 m_pauseInNextEventListener = false; 126 } 127 128 void InspectorDOMDebuggerAgent::disable() 129 { 130 m_instrumentingAgents->setInspectorDOMDebuggerAgent(0); 131 clear(); 132 } 133 134 void InspectorDOMDebuggerAgent::clearFrontend() 135 { 136 disable(); 137 } 138 139 void InspectorDOMDebuggerAgent::discardAgent() 140 { 141 m_debuggerAgent->setListener(0); 142 m_debuggerAgent = 0; 143 } 144 145 void InspectorDOMDebuggerAgent::discardBindings() 146 { 147 m_domBreakpoints.clear(); 148 } 149 150 void InspectorDOMDebuggerAgent::setEventListenerBreakpoint(ErrorString* error, const String& eventName) 151 { 152 setBreakpoint(error, String(listenerEventCategoryType) + eventName); 153 } 154 155 void InspectorDOMDebuggerAgent::setInstrumentationBreakpoint(ErrorString* error, const String& eventName) 156 { 157 setBreakpoint(error, String(instrumentationEventCategoryType) + eventName); 158 } 159 160 void InspectorDOMDebuggerAgent::setBreakpoint(ErrorString* error, const String& eventName) 161 { 162 if (eventName.isEmpty()) { 163 *error = "Event name is empty"; 164 return; 165 } 166 167 RefPtr<JSONObject> eventListenerBreakpoints = m_state->getObject(DOMDebuggerAgentState::eventListenerBreakpoints); 168 eventListenerBreakpoints->setBoolean(eventName, true); 169 m_state->setObject(DOMDebuggerAgentState::eventListenerBreakpoints, eventListenerBreakpoints); 170 } 171 172 void InspectorDOMDebuggerAgent::removeEventListenerBreakpoint(ErrorString* error, const String& eventName) 173 { 174 removeBreakpoint(error, String(listenerEventCategoryType) + eventName); 175 } 176 177 void InspectorDOMDebuggerAgent::removeInstrumentationBreakpoint(ErrorString* error, const String& eventName) 178 { 179 removeBreakpoint(error, String(instrumentationEventCategoryType) + eventName); 180 } 181 182 void InspectorDOMDebuggerAgent::removeBreakpoint(ErrorString* error, const String& eventName) 183 { 184 if (eventName.isEmpty()) { 185 *error = "Event name is empty"; 186 return; 187 } 188 189 RefPtr<JSONObject> eventListenerBreakpoints = m_state->getObject(DOMDebuggerAgentState::eventListenerBreakpoints); 190 eventListenerBreakpoints->remove(eventName); 191 m_state->setObject(DOMDebuggerAgentState::eventListenerBreakpoints, eventListenerBreakpoints); 192 } 193 194 void InspectorDOMDebuggerAgent::didInvalidateStyleAttr(Node* node) 195 { 196 if (hasBreakpoint(node, AttributeModified)) { 197 RefPtr<JSONObject> eventData = JSONObject::create(); 198 descriptionForDOMEvent(node, AttributeModified, false, eventData.get()); 199 m_debuggerAgent->breakProgram(InspectorFrontend::Debugger::Reason::DOM, eventData.release()); 200 } 201 } 202 203 void InspectorDOMDebuggerAgent::didInsertDOMNode(Node* node) 204 { 205 if (m_domBreakpoints.size()) { 206 uint32_t mask = m_domBreakpoints.get(InspectorDOMAgent::innerParentNode(node)); 207 uint32_t inheritableTypesMask = (mask | (mask >> domBreakpointDerivedTypeShift)) & inheritableDOMBreakpointTypesMask; 208 if (inheritableTypesMask) 209 updateSubtreeBreakpoints(node, inheritableTypesMask, true); 210 } 211 } 212 213 void InspectorDOMDebuggerAgent::didRemoveDOMNode(Node* node) 214 { 215 if (m_domBreakpoints.size()) { 216 // Remove subtree breakpoints. 217 m_domBreakpoints.remove(node); 218 Vector<Node*> stack(1, InspectorDOMAgent::innerFirstChild(node)); 219 do { 220 Node* node = stack.last(); 221 stack.removeLast(); 222 if (!node) 223 continue; 224 m_domBreakpoints.remove(node); 225 stack.append(InspectorDOMAgent::innerFirstChild(node)); 226 stack.append(InspectorDOMAgent::innerNextSibling(node)); 227 } while (!stack.isEmpty()); 228 } 229 } 230 231 static int domTypeForName(ErrorString* errorString, const String& typeString) 232 { 233 if (typeString == "subtree-modified") 234 return SubtreeModified; 235 if (typeString == "attribute-modified") 236 return AttributeModified; 237 if (typeString == "node-removed") 238 return NodeRemoved; 239 *errorString = "Unknown DOM breakpoint type: " + typeString; 240 return -1; 241 } 242 243 static String domTypeName(int type) 244 { 245 switch (type) { 246 case SubtreeModified: return "subtree-modified"; 247 case AttributeModified: return "attribute-modified"; 248 case NodeRemoved: return "node-removed"; 249 default: break; 250 } 251 return ""; 252 } 253 254 void InspectorDOMDebuggerAgent::setDOMBreakpoint(ErrorString* errorString, int nodeId, const String& typeString) 255 { 256 Node* node = m_domAgent->assertNode(errorString, nodeId); 257 if (!node) 258 return; 259 260 int type = domTypeForName(errorString, typeString); 261 if (type == -1) 262 return; 263 264 uint32_t rootBit = 1 << type; 265 m_domBreakpoints.set(node, m_domBreakpoints.get(node) | rootBit); 266 if (rootBit & inheritableDOMBreakpointTypesMask) { 267 for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child)) 268 updateSubtreeBreakpoints(child, rootBit, true); 269 } 270 } 271 272 void InspectorDOMDebuggerAgent::removeDOMBreakpoint(ErrorString* errorString, int nodeId, const String& typeString) 273 { 274 Node* node = m_domAgent->assertNode(errorString, nodeId); 275 if (!node) 276 return; 277 int type = domTypeForName(errorString, typeString); 278 if (type == -1) 279 return; 280 281 uint32_t rootBit = 1 << type; 282 uint32_t mask = m_domBreakpoints.get(node) & ~rootBit; 283 if (mask) 284 m_domBreakpoints.set(node, mask); 285 else 286 m_domBreakpoints.remove(node); 287 288 if ((rootBit & inheritableDOMBreakpointTypesMask) && !(mask & (rootBit << domBreakpointDerivedTypeShift))) { 289 for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child)) 290 updateSubtreeBreakpoints(child, rootBit, false); 291 } 292 } 293 294 void InspectorDOMDebuggerAgent::willInsertDOMNode(Node* parent) 295 { 296 if (hasBreakpoint(parent, SubtreeModified)) { 297 RefPtr<JSONObject> eventData = JSONObject::create(); 298 descriptionForDOMEvent(parent, SubtreeModified, true, eventData.get()); 299 m_debuggerAgent->breakProgram(InspectorFrontend::Debugger::Reason::DOM, eventData.release()); 300 } 301 } 302 303 void InspectorDOMDebuggerAgent::willRemoveDOMNode(Node* node) 304 { 305 Node* parentNode = InspectorDOMAgent::innerParentNode(node); 306 if (hasBreakpoint(node, NodeRemoved)) { 307 RefPtr<JSONObject> eventData = JSONObject::create(); 308 descriptionForDOMEvent(node, NodeRemoved, false, eventData.get()); 309 m_debuggerAgent->breakProgram(InspectorFrontend::Debugger::Reason::DOM, eventData.release()); 310 } else if (parentNode && hasBreakpoint(parentNode, SubtreeModified)) { 311 RefPtr<JSONObject> eventData = JSONObject::create(); 312 descriptionForDOMEvent(node, SubtreeModified, false, eventData.get()); 313 m_debuggerAgent->breakProgram(InspectorFrontend::Debugger::Reason::DOM, eventData.release()); 314 } 315 didRemoveDOMNode(node); 316 } 317 318 void InspectorDOMDebuggerAgent::willModifyDOMAttr(Element* element, const AtomicString&, const AtomicString&) 319 { 320 if (hasBreakpoint(element, AttributeModified)) { 321 RefPtr<JSONObject> eventData = JSONObject::create(); 322 descriptionForDOMEvent(element, AttributeModified, false, eventData.get()); 323 m_debuggerAgent->breakProgram(InspectorFrontend::Debugger::Reason::DOM, eventData.release()); 324 } 325 } 326 327 void InspectorDOMDebuggerAgent::descriptionForDOMEvent(Node* target, int breakpointType, bool insertion, JSONObject* description) 328 { 329 ASSERT(hasBreakpoint(target, breakpointType)); 330 331 Node* breakpointOwner = target; 332 if ((1 << breakpointType) & inheritableDOMBreakpointTypesMask) { 333 // For inheritable breakpoint types, target node isn't always the same as the node that owns a breakpoint. 334 // Target node may be unknown to frontend, so we need to push it first. 335 RefPtr<TypeBuilder::Runtime::RemoteObject> targetNodeObject = m_domAgent->resolveNode(target, InspectorDebuggerAgent::backtraceObjectGroup); 336 description->setValue("targetNode", targetNodeObject); 337 338 // Find breakpoint owner node. 339 if (!insertion) 340 breakpointOwner = InspectorDOMAgent::innerParentNode(target); 341 ASSERT(breakpointOwner); 342 while (!(m_domBreakpoints.get(breakpointOwner) & (1 << breakpointType))) { 343 Node* parentNode = InspectorDOMAgent::innerParentNode(breakpointOwner); 344 if (!parentNode) 345 break; 346 breakpointOwner = parentNode; 347 } 348 349 if (breakpointType == SubtreeModified) 350 description->setBoolean("insertion", insertion); 351 } 352 353 int breakpointOwnerNodeId = m_domAgent->boundNodeId(breakpointOwner); 354 ASSERT(breakpointOwnerNodeId); 355 description->setNumber("nodeId", breakpointOwnerNodeId); 356 description->setString("type", domTypeName(breakpointType)); 357 } 358 359 bool InspectorDOMDebuggerAgent::hasBreakpoint(Node* node, int type) 360 { 361 uint32_t rootBit = 1 << type; 362 uint32_t derivedBit = rootBit << domBreakpointDerivedTypeShift; 363 return m_domBreakpoints.get(node) & (rootBit | derivedBit); 364 } 365 366 void InspectorDOMDebuggerAgent::updateSubtreeBreakpoints(Node* node, uint32_t rootMask, bool set) 367 { 368 uint32_t oldMask = m_domBreakpoints.get(node); 369 uint32_t derivedMask = rootMask << domBreakpointDerivedTypeShift; 370 uint32_t newMask = set ? oldMask | derivedMask : oldMask & ~derivedMask; 371 if (newMask) 372 m_domBreakpoints.set(node, newMask); 373 else 374 m_domBreakpoints.remove(node); 375 376 uint32_t newRootMask = rootMask & ~newMask; 377 if (!newRootMask) 378 return; 379 380 for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child)) 381 updateSubtreeBreakpoints(child, newRootMask, set); 382 } 383 384 void InspectorDOMDebuggerAgent::pauseOnNativeEventIfNeeded(PassRefPtr<JSONObject> eventData, bool synchronous) 385 { 386 if (!eventData) 387 return; 388 if (synchronous) 389 m_debuggerAgent->breakProgram(InspectorFrontend::Debugger::Reason::EventListener, eventData); 390 else 391 m_debuggerAgent->schedulePauseOnNextStatement(InspectorFrontend::Debugger::Reason::EventListener, eventData); 392 } 393 394 PassRefPtr<JSONObject> InspectorDOMDebuggerAgent::preparePauseOnNativeEventData(bool isDOMEvent, const String& eventName) 395 { 396 String fullEventName = (isDOMEvent ? listenerEventCategoryType : instrumentationEventCategoryType) + eventName; 397 if (m_pauseInNextEventListener) 398 m_pauseInNextEventListener = false; 399 else { 400 RefPtr<JSONObject> eventListenerBreakpoints = m_state->getObject(DOMDebuggerAgentState::eventListenerBreakpoints); 401 if (eventListenerBreakpoints->find(fullEventName) == eventListenerBreakpoints->end()) 402 return 0; 403 } 404 405 RefPtr<JSONObject> eventData = JSONObject::create(); 406 eventData->setString("eventName", fullEventName); 407 return eventData.release(); 408 } 409 410 void InspectorDOMDebuggerAgent::didInstallTimer(ScriptExecutionContext* context, int timerId, int timeout, bool singleShot) 411 { 412 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(false, setTimerEventName), true); 413 } 414 415 void InspectorDOMDebuggerAgent::didRemoveTimer(ScriptExecutionContext* context, int timerId) 416 { 417 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(false, clearTimerEventName), true); 418 } 419 420 void InspectorDOMDebuggerAgent::willFireTimer(ScriptExecutionContext* context, int timerId) 421 { 422 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(false, timerFiredEventName), false); 423 } 424 425 void InspectorDOMDebuggerAgent::didRequestAnimationFrame(Document* document, int callbackId) 426 { 427 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(false, requestAnimationFrameEventName), true); 428 } 429 430 void InspectorDOMDebuggerAgent::didCancelAnimationFrame(Document* document, int callbackId) 431 { 432 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(false, cancelAnimationFrameEventName), true); 433 } 434 435 void InspectorDOMDebuggerAgent::willFireAnimationFrame(Document* document, int callbackId) 436 { 437 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(false, animationFrameFiredEventName), false); 438 } 439 440 void InspectorDOMDebuggerAgent::willHandleEvent(Event* event) 441 { 442 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(true, event->type()), false); 443 } 444 445 void InspectorDOMDebuggerAgent::didFireWebGLError(const String& errorName) 446 { 447 RefPtr<JSONObject> eventData = preparePauseOnNativeEventData(false, webglErrorFiredEventName); 448 if (!eventData) 449 return; 450 if (!errorName.isEmpty()) 451 eventData->setString(webglErrorNameProperty, errorName); 452 pauseOnNativeEventIfNeeded(eventData.release(), m_debuggerAgent->canBreakProgram()); 453 } 454 455 void InspectorDOMDebuggerAgent::didFireWebGLWarning() 456 { 457 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(false, webglWarningFiredEventName), m_debuggerAgent->canBreakProgram()); 458 } 459 460 void InspectorDOMDebuggerAgent::didFireWebGLErrorOrWarning(const String& message) 461 { 462 if (message.findIgnoringCase("error") != WTF::notFound) 463 didFireWebGLError(String()); 464 else 465 didFireWebGLWarning(); 466 } 467 468 void InspectorDOMDebuggerAgent::setXHRBreakpoint(ErrorString*, const String& url) 469 { 470 if (url.isEmpty()) { 471 m_state->setBoolean(DOMDebuggerAgentState::pauseOnAllXHRs, true); 472 return; 473 } 474 475 RefPtr<JSONObject> xhrBreakpoints = m_state->getObject(DOMDebuggerAgentState::xhrBreakpoints); 476 xhrBreakpoints->setBoolean(url, true); 477 m_state->setObject(DOMDebuggerAgentState::xhrBreakpoints, xhrBreakpoints); 478 } 479 480 void InspectorDOMDebuggerAgent::removeXHRBreakpoint(ErrorString*, const String& url) 481 { 482 if (url.isEmpty()) { 483 m_state->setBoolean(DOMDebuggerAgentState::pauseOnAllXHRs, false); 484 return; 485 } 486 487 RefPtr<JSONObject> xhrBreakpoints = m_state->getObject(DOMDebuggerAgentState::xhrBreakpoints); 488 xhrBreakpoints->remove(url); 489 m_state->setObject(DOMDebuggerAgentState::xhrBreakpoints, xhrBreakpoints); 490 } 491 492 void InspectorDOMDebuggerAgent::willSendXMLHttpRequest(const String& url) 493 { 494 String breakpointURL; 495 if (m_state->getBoolean(DOMDebuggerAgentState::pauseOnAllXHRs)) 496 breakpointURL = ""; 497 else { 498 RefPtr<JSONObject> xhrBreakpoints = m_state->getObject(DOMDebuggerAgentState::xhrBreakpoints); 499 for (JSONObject::iterator it = xhrBreakpoints->begin(); it != xhrBreakpoints->end(); ++it) { 500 if (url.contains(it->key)) { 501 breakpointURL = it->key; 502 break; 503 } 504 } 505 } 506 507 if (breakpointURL.isNull()) 508 return; 509 510 RefPtr<JSONObject> eventData = JSONObject::create(); 511 eventData->setString("breakpointURL", breakpointURL); 512 eventData->setString("url", url); 513 m_debuggerAgent->breakProgram(InspectorFrontend::Debugger::Reason::XHR, eventData.release()); 514 } 515 516 void InspectorDOMDebuggerAgent::clear() 517 { 518 m_domBreakpoints.clear(); 519 m_pauseInNextEventListener = false; 520 } 521 522 } // namespace WebCore 523 524