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