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