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 41namespace { 42 43enum DOMBreakpointType { 44 SubtreeModified = 0, 45 AttributeModified, 46 NodeRemoved, 47 DOMBreakpointTypesCount 48}; 49 50static const char listenerEventCategoryType[] = "listener:"; 51static const char instrumentationEventCategoryType[] = "instrumentation:"; 52 53const uint32_t inheritableDOMBreakpointTypesMask = (1 << SubtreeModified); 54const int domBreakpointDerivedTypeShift = 16; 55 56} 57 58namespace WebCore { 59 60static const char requestAnimationFrameEventName[] = "requestAnimationFrame"; 61static const char cancelAnimationFrameEventName[] = "cancelAnimationFrame"; 62static const char animationFrameFiredEventName[] = "animationFrameFired"; 63static const char setTimerEventName[] = "setTimer"; 64static const char clearTimerEventName[] = "clearTimer"; 65static const char timerFiredEventName[] = "timerFired"; 66static const char webglErrorFiredEventName[] = "webglErrorFired"; 67static const char webglWarningFiredEventName[] = "webglWarningFired"; 68static const char webglErrorNameProperty[] = "webglErrorName"; 69 70namespace DOMDebuggerAgentState { 71static const char eventListenerBreakpoints[] = "eventListenerBreakpoints"; 72static const char pauseOnAllXHRs[] = "pauseOnAllXHRs"; 73static const char xhrBreakpoints[] = "xhrBreakpoints"; 74} 75 76PassOwnPtr<InspectorDOMDebuggerAgent> InspectorDOMDebuggerAgent::create(InstrumentingAgents* instrumentingAgents, InspectorCompositeState* inspectorState, InspectorDOMAgent* domAgent, InspectorDebuggerAgent* debuggerAgent) 77{ 78 return adoptPtr(new InspectorDOMDebuggerAgent(instrumentingAgents, inspectorState, domAgent, debuggerAgent)); 79} 80 81InspectorDOMDebuggerAgent::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 90InspectorDOMDebuggerAgent::~InspectorDOMDebuggerAgent() 91{ 92 ASSERT(!m_debuggerAgent); 93 ASSERT(!m_instrumentingAgents->inspectorDOMDebuggerAgent()); 94} 95 96// Browser debugger agent enabled only when JS debugger is enabled. 97void InspectorDOMDebuggerAgent::debuggerWasEnabled() 98{ 99 m_instrumentingAgents->setInspectorDOMDebuggerAgent(this); 100} 101 102void InspectorDOMDebuggerAgent::debuggerWasDisabled() 103{ 104 disable(); 105} 106 107void InspectorDOMDebuggerAgent::stepInto() 108{ 109 m_pauseInNextEventListener = true; 110} 111 112void InspectorDOMDebuggerAgent::didPause() 113{ 114 m_pauseInNextEventListener = false; 115} 116 117void InspectorDOMDebuggerAgent::didProcessTask() 118{ 119 if (!m_pauseInNextEventListener) 120 return; 121 if (m_debuggerAgent && m_debuggerAgent->runningNestedMessageLoop()) 122 return; 123 m_pauseInNextEventListener = false; 124} 125 126void InspectorDOMDebuggerAgent::disable() 127{ 128 m_instrumentingAgents->setInspectorDOMDebuggerAgent(0); 129 clear(); 130} 131 132void InspectorDOMDebuggerAgent::clearFrontend() 133{ 134 disable(); 135} 136 137void InspectorDOMDebuggerAgent::discardAgent() 138{ 139 m_debuggerAgent->setListener(0); 140 m_debuggerAgent = 0; 141} 142 143void InspectorDOMDebuggerAgent::discardBindings() 144{ 145 m_domBreakpoints.clear(); 146} 147 148void InspectorDOMDebuggerAgent::setEventListenerBreakpoint(ErrorString* error, const String& eventName) 149{ 150 setBreakpoint(error, String(listenerEventCategoryType) + eventName); 151} 152 153void InspectorDOMDebuggerAgent::setInstrumentationBreakpoint(ErrorString* error, const String& eventName) 154{ 155 setBreakpoint(error, String(instrumentationEventCategoryType) + eventName); 156} 157 158void 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 170void InspectorDOMDebuggerAgent::removeEventListenerBreakpoint(ErrorString* error, const String& eventName) 171{ 172 removeBreakpoint(error, String(listenerEventCategoryType) + eventName); 173} 174 175void InspectorDOMDebuggerAgent::removeInstrumentationBreakpoint(ErrorString* error, const String& eventName) 176{ 177 removeBreakpoint(error, String(instrumentationEventCategoryType) + eventName); 178} 179 180void 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 192void 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 201void 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 211void 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 229static 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 241static 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 252void 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 270void 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 292void 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 301void 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 316void 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 325void 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 357bool 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 364void 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 382void 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 392PassRefPtr<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 408void InspectorDOMDebuggerAgent::didInstallTimer(ExecutionContext* context, int timerId, int timeout, bool singleShot) 409{ 410 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(false, setTimerEventName), true); 411} 412 413void InspectorDOMDebuggerAgent::didRemoveTimer(ExecutionContext* context, int timerId) 414{ 415 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(false, clearTimerEventName), true); 416} 417 418void InspectorDOMDebuggerAgent::willFireTimer(ExecutionContext* context, int timerId) 419{ 420 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(false, timerFiredEventName), false); 421} 422 423void InspectorDOMDebuggerAgent::didRequestAnimationFrame(Document* document, int callbackId) 424{ 425 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(false, requestAnimationFrameEventName), true); 426} 427 428void InspectorDOMDebuggerAgent::didCancelAnimationFrame(Document* document, int callbackId) 429{ 430 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(false, cancelAnimationFrameEventName), true); 431} 432 433void InspectorDOMDebuggerAgent::willFireAnimationFrame(Document* document, int callbackId) 434{ 435 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(false, animationFrameFiredEventName), false); 436} 437 438void InspectorDOMDebuggerAgent::willHandleEvent(Event* event) 439{ 440 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(true, event->type()), false); 441} 442 443void 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 453void InspectorDOMDebuggerAgent::didFireWebGLWarning() 454{ 455 pauseOnNativeEventIfNeeded(preparePauseOnNativeEventData(false, webglWarningFiredEventName), m_debuggerAgent->canBreakProgram()); 456} 457 458void InspectorDOMDebuggerAgent::didFireWebGLErrorOrWarning(const String& message) 459{ 460 if (message.findIgnoringCase("error") != WTF::kNotFound) 461 didFireWebGLError(String()); 462 else 463 didFireWebGLWarning(); 464} 465 466void 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 478void 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 490void 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 514void InspectorDOMDebuggerAgent::clear() 515{ 516 m_domBreakpoints.clear(); 517 m_pauseInNextEventListener = false; 518} 519 520} // namespace WebCore 521 522