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