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