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#include "InspectorBrowserDebuggerAgent.h"
34
35#if ENABLE(INSPECTOR) && ENABLE(JAVASCRIPT_DEBUGGER)
36
37#include "HTMLElement.h"
38#include "InspectorAgent.h"
39#include "InspectorDOMAgent.h"
40#include "InspectorDebuggerAgent.h"
41#include "InspectorState.h"
42#include "InspectorValues.h"
43#include "InstrumentingAgents.h"
44#include <wtf/text/StringConcatenate.h>
45
46namespace {
47
48enum DOMBreakpointType {
49    SubtreeModified = 0,
50    AttributeModified,
51    NodeRemoved,
52    DOMBreakpointTypesCount
53};
54
55static const char* const domNativeBreakpointType = "DOM";
56static const char* const eventListenerNativeBreakpointType = "EventListener";
57static const char* const xhrNativeBreakpointType = "XHR";
58
59const uint32_t inheritableDOMBreakpointTypesMask = (1 << SubtreeModified);
60const int domBreakpointDerivedTypeShift = 16;
61
62}
63
64namespace WebCore {
65
66namespace BrowserDebuggerAgentState {
67static const char eventListenerBreakpoints[] = "eventListenerBreakpoints";
68static const char pauseOnAllXHRs[] = "pauseOnAllXHRs";
69static const char xhrBreakpoints[] = "xhrBreakpoints";
70}
71
72PassOwnPtr<InspectorBrowserDebuggerAgent> InspectorBrowserDebuggerAgent::create(InstrumentingAgents* instrumentingAgents, InspectorState* inspectorState, InspectorDOMAgent* domAgent, InspectorDebuggerAgent* debuggerAgent, InspectorAgent* inspectorAgent)
73{
74    return adoptPtr(new InspectorBrowserDebuggerAgent(instrumentingAgents, inspectorState, domAgent, debuggerAgent, inspectorAgent));
75}
76
77InspectorBrowserDebuggerAgent::InspectorBrowserDebuggerAgent(InstrumentingAgents* instrumentingAgents, InspectorState* inspectorState, InspectorDOMAgent* domAgent, InspectorDebuggerAgent* debuggerAgent, InspectorAgent* inspectorAgent)
78    : m_instrumentingAgents(instrumentingAgents)
79    , m_inspectorState(inspectorState)
80    , m_domAgent(domAgent)
81    , m_debuggerAgent(debuggerAgent)
82    , m_inspectorAgent(inspectorAgent)
83{
84    m_debuggerAgent->setListener(this);
85}
86
87InspectorBrowserDebuggerAgent::~InspectorBrowserDebuggerAgent()
88{
89    m_debuggerAgent->setListener(0);
90    ASSERT(!m_instrumentingAgents->inspectorBrowserDebuggerAgent());
91}
92
93// Browser debugger agent enabled only when JS debugger is enabled.
94void InspectorBrowserDebuggerAgent::debuggerWasEnabled()
95{
96    m_instrumentingAgents->setInspectorBrowserDebuggerAgent(this);
97}
98
99void InspectorBrowserDebuggerAgent::debuggerWasDisabled()
100{
101    disable();
102}
103
104void InspectorBrowserDebuggerAgent::disable()
105{
106    m_instrumentingAgents->setInspectorBrowserDebuggerAgent(0);
107    clear();
108}
109
110void InspectorBrowserDebuggerAgent::clearFrontend()
111{
112    disable();
113}
114
115void InspectorBrowserDebuggerAgent::discardBindings()
116{
117    m_domBreakpoints.clear();
118}
119
120void InspectorBrowserDebuggerAgent::setEventListenerBreakpoint(ErrorString* error, const String& eventName)
121{
122    if (eventName.isEmpty()) {
123        *error = "Event name is empty";
124        return;
125    }
126
127    RefPtr<InspectorObject> eventListenerBreakpoints = m_inspectorState->getObject(BrowserDebuggerAgentState::eventListenerBreakpoints);
128    eventListenerBreakpoints->setBoolean(eventName, true);
129    m_inspectorState->setObject(BrowserDebuggerAgentState::eventListenerBreakpoints, eventListenerBreakpoints);
130}
131
132void InspectorBrowserDebuggerAgent::removeEventListenerBreakpoint(ErrorString* error, const String& eventName)
133{
134    if (eventName.isEmpty()) {
135        *error = "Event name is empty";
136        return;
137    }
138
139    RefPtr<InspectorObject> eventListenerBreakpoints = m_inspectorState->getObject(BrowserDebuggerAgentState::eventListenerBreakpoints);
140    eventListenerBreakpoints->remove(eventName);
141    m_inspectorState->setObject(BrowserDebuggerAgentState::eventListenerBreakpoints, eventListenerBreakpoints);
142}
143
144void InspectorBrowserDebuggerAgent::didInsertDOMNode(Node* node)
145{
146    if (m_domBreakpoints.size()) {
147        uint32_t mask = m_domBreakpoints.get(InspectorDOMAgent::innerParentNode(node));
148        uint32_t inheritableTypesMask = (mask | (mask >> domBreakpointDerivedTypeShift)) & inheritableDOMBreakpointTypesMask;
149        if (inheritableTypesMask)
150            updateSubtreeBreakpoints(node, inheritableTypesMask, true);
151    }
152}
153
154void InspectorBrowserDebuggerAgent::didRemoveDOMNode(Node* node)
155{
156    if (m_domBreakpoints.size()) {
157        // Remove subtree breakpoints.
158        m_domBreakpoints.remove(node);
159        Vector<Node*> stack(1, InspectorDOMAgent::innerFirstChild(node));
160        do {
161            Node* node = stack.last();
162            stack.removeLast();
163            if (!node)
164                continue;
165            m_domBreakpoints.remove(node);
166            stack.append(InspectorDOMAgent::innerFirstChild(node));
167            stack.append(InspectorDOMAgent::innerNextSibling(node));
168        } while (!stack.isEmpty());
169    }
170}
171
172void InspectorBrowserDebuggerAgent::setDOMBreakpoint(ErrorString*, int nodeId, int type)
173{
174    Node* node = m_domAgent->nodeForId(nodeId);
175    if (!node)
176        return;
177
178    uint32_t rootBit = 1 << type;
179    m_domBreakpoints.set(node, m_domBreakpoints.get(node) | rootBit);
180    if (rootBit & inheritableDOMBreakpointTypesMask) {
181        for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child))
182            updateSubtreeBreakpoints(child, rootBit, true);
183    }
184}
185
186void InspectorBrowserDebuggerAgent::removeDOMBreakpoint(ErrorString*, int nodeId, int type)
187{
188    Node* node = m_domAgent->nodeForId(nodeId);
189    if (!node)
190        return;
191
192    uint32_t rootBit = 1 << type;
193    uint32_t mask = m_domBreakpoints.get(node) & ~rootBit;
194    if (mask)
195        m_domBreakpoints.set(node, mask);
196    else
197        m_domBreakpoints.remove(node);
198
199    if ((rootBit & inheritableDOMBreakpointTypesMask) && !(mask & (rootBit << domBreakpointDerivedTypeShift))) {
200        for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child))
201            updateSubtreeBreakpoints(child, rootBit, false);
202    }
203}
204
205void InspectorBrowserDebuggerAgent::willInsertDOMNode(Node*, Node* parent)
206{
207    InspectorDebuggerAgent* debuggerAgent = m_debuggerAgent;
208    if (!debuggerAgent)
209        return;
210
211    if (hasBreakpoint(parent, SubtreeModified)) {
212        RefPtr<InspectorObject> eventData = InspectorObject::create();
213        descriptionForDOMEvent(parent, SubtreeModified, true, eventData.get());
214        eventData->setString("breakpointType", domNativeBreakpointType);
215        debuggerAgent->breakProgram(NativeBreakpointDebuggerEventType, eventData.release());
216    }
217}
218
219void InspectorBrowserDebuggerAgent::willRemoveDOMNode(Node* node)
220{
221    InspectorDebuggerAgent* debuggerAgent = m_debuggerAgent;
222    if (!debuggerAgent)
223        return;
224
225    Node* parentNode = InspectorDOMAgent::innerParentNode(node);
226    if (hasBreakpoint(node, NodeRemoved)) {
227        RefPtr<InspectorObject> eventData = InspectorObject::create();
228        descriptionForDOMEvent(node, NodeRemoved, false, eventData.get());
229        eventData->setString("breakpointType", domNativeBreakpointType);
230        debuggerAgent->breakProgram(NativeBreakpointDebuggerEventType, eventData.release());
231    } else if (parentNode && hasBreakpoint(parentNode, SubtreeModified)) {
232        RefPtr<InspectorObject> eventData = InspectorObject::create();
233        descriptionForDOMEvent(node, SubtreeModified, false, eventData.get());
234        eventData->setString("breakpointType", domNativeBreakpointType);
235        debuggerAgent->breakProgram(NativeBreakpointDebuggerEventType, eventData.release());
236    }
237}
238
239void InspectorBrowserDebuggerAgent::willModifyDOMAttr(Element* element)
240{
241    InspectorDebuggerAgent* debuggerAgent = m_debuggerAgent;
242    if (!debuggerAgent)
243        return;
244
245    if (hasBreakpoint(element, AttributeModified)) {
246        RefPtr<InspectorObject> eventData = InspectorObject::create();
247        descriptionForDOMEvent(element, AttributeModified, false, eventData.get());
248        eventData->setString("breakpointType", domNativeBreakpointType);
249        debuggerAgent->breakProgram(NativeBreakpointDebuggerEventType, eventData.release());
250    }
251}
252
253void InspectorBrowserDebuggerAgent::descriptionForDOMEvent(Node* target, int breakpointType, bool insertion, InspectorObject* description)
254{
255    ASSERT(hasBreakpoint(target, breakpointType));
256
257    Node* breakpointOwner = target;
258    if ((1 << breakpointType) & inheritableDOMBreakpointTypesMask) {
259        // For inheritable breakpoint types, target node isn't always the same as the node that owns a breakpoint.
260        // Target node may be unknown to frontend, so we need to push it first.
261        RefPtr<InspectorObject> targetNodeObject = m_domAgent->resolveNode(target);
262        description->setObject("targetNode", targetNodeObject);
263
264        // Find breakpoint owner node.
265        if (!insertion)
266            breakpointOwner = InspectorDOMAgent::innerParentNode(target);
267        ASSERT(breakpointOwner);
268        while (!(m_domBreakpoints.get(breakpointOwner) & (1 << breakpointType))) {
269            breakpointOwner = InspectorDOMAgent::innerParentNode(breakpointOwner);
270            ASSERT(breakpointOwner);
271        }
272
273        if (breakpointType == SubtreeModified)
274            description->setBoolean("insertion", insertion);
275    }
276
277    int breakpointOwnerNodeId = m_domAgent->boundNodeId(breakpointOwner);
278    ASSERT(breakpointOwnerNodeId);
279    description->setNumber("nodeId", breakpointOwnerNodeId);
280    description->setNumber("type", breakpointType);
281}
282
283bool InspectorBrowserDebuggerAgent::hasBreakpoint(Node* node, int type)
284{
285    uint32_t rootBit = 1 << type;
286    uint32_t derivedBit = rootBit << domBreakpointDerivedTypeShift;
287    return m_domBreakpoints.get(node) & (rootBit | derivedBit);
288}
289
290void InspectorBrowserDebuggerAgent::updateSubtreeBreakpoints(Node* node, uint32_t rootMask, bool set)
291{
292    uint32_t oldMask = m_domBreakpoints.get(node);
293    uint32_t derivedMask = rootMask << domBreakpointDerivedTypeShift;
294    uint32_t newMask = set ? oldMask | derivedMask : oldMask & ~derivedMask;
295    if (newMask)
296        m_domBreakpoints.set(node, newMask);
297    else
298        m_domBreakpoints.remove(node);
299
300    uint32_t newRootMask = rootMask & ~newMask;
301    if (!newRootMask)
302        return;
303
304    for (Node* child = InspectorDOMAgent::innerFirstChild(node); child; child = InspectorDOMAgent::innerNextSibling(child))
305        updateSubtreeBreakpoints(child, newRootMask, set);
306}
307
308void InspectorBrowserDebuggerAgent::pauseOnNativeEventIfNeeded(const String& categoryType, const String& eventName, bool synchronous)
309{
310    InspectorDebuggerAgent* debuggerAgent = m_debuggerAgent;
311    if (!debuggerAgent)
312        return;
313
314    String fullEventName = makeString(categoryType, ":", eventName);
315    RefPtr<InspectorObject> eventListenerBreakpoints = m_inspectorState->getObject(BrowserDebuggerAgentState::eventListenerBreakpoints);
316    if (eventListenerBreakpoints->find(fullEventName) == eventListenerBreakpoints->end())
317        return;
318
319    RefPtr<InspectorObject> eventData = InspectorObject::create();
320    eventData->setString("breakpointType", eventListenerNativeBreakpointType);
321    eventData->setString("eventName", fullEventName);
322    if (synchronous)
323        debuggerAgent->breakProgram(NativeBreakpointDebuggerEventType, eventData.release());
324    else
325        debuggerAgent->schedulePauseOnNextStatement(NativeBreakpointDebuggerEventType, eventData.release());
326}
327
328void InspectorBrowserDebuggerAgent::setXHRBreakpoint(ErrorString*, const String& url)
329{
330    if (url.isEmpty()) {
331        m_inspectorState->setBoolean(BrowserDebuggerAgentState::pauseOnAllXHRs, true);
332        return;
333    }
334
335    RefPtr<InspectorObject> xhrBreakpoints = m_inspectorState->getObject(BrowserDebuggerAgentState::xhrBreakpoints);
336    xhrBreakpoints->setBoolean(url, true);
337    m_inspectorState->setObject(BrowserDebuggerAgentState::xhrBreakpoints, xhrBreakpoints);
338}
339
340void InspectorBrowserDebuggerAgent::removeXHRBreakpoint(ErrorString*, const String& url)
341{
342    if (url.isEmpty()) {
343        m_inspectorState->setBoolean(BrowserDebuggerAgentState::pauseOnAllXHRs, false);
344        return;
345    }
346
347    RefPtr<InspectorObject> xhrBreakpoints = m_inspectorState->getObject(BrowserDebuggerAgentState::xhrBreakpoints);
348    xhrBreakpoints->remove(url);
349    m_inspectorState->setObject(BrowserDebuggerAgentState::xhrBreakpoints, xhrBreakpoints);
350}
351
352void InspectorBrowserDebuggerAgent::willSendXMLHttpRequest(const String& url)
353{
354    InspectorDebuggerAgent* debuggerAgent = m_debuggerAgent;
355    if (!debuggerAgent)
356        return;
357
358    String breakpointURL;
359    if (m_inspectorState->getBoolean(BrowserDebuggerAgentState::pauseOnAllXHRs))
360        breakpointURL = "";
361    else {
362        RefPtr<InspectorObject> xhrBreakpoints = m_inspectorState->getObject(BrowserDebuggerAgentState::xhrBreakpoints);
363        for (InspectorObject::iterator it = xhrBreakpoints->begin(); it != xhrBreakpoints->end(); ++it) {
364            if (url.contains(it->first)) {
365                breakpointURL = it->first;
366                break;
367            }
368        }
369    }
370
371    if (breakpointURL.isNull())
372        return;
373
374    RefPtr<InspectorObject> eventData = InspectorObject::create();
375    eventData->setString("breakpointType", xhrNativeBreakpointType);
376    eventData->setString("breakpointURL", breakpointURL);
377    eventData->setString("url", url);
378    debuggerAgent->breakProgram(NativeBreakpointDebuggerEventType, eventData.release());
379}
380
381void InspectorBrowserDebuggerAgent::clear()
382{
383    m_domBreakpoints.clear();
384}
385
386} // namespace WebCore
387
388#endif // ENABLE(INSPECTOR) && ENABLE(JAVASCRIPT_DEBUGGER)
389