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