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