Web Inspector: document Debugger.paused, introduce types for ids in Debugger domain.
[WebKit.git] / Source / WebCore / inspector / front-end / DOMBreakpointsSidebarPane.js
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 WebInspector.DOMBreakpointsSidebarPane = function()
32 {
33     WebInspector.NativeBreakpointsSidebarPane.call(this, WebInspector.UIString("DOM Breakpoints"));
34
35     this._breakpointElements = {};
36
37     this._breakpointTypes = {
38         SubtreeModified: "subtree-modified",
39         AttributeModified: "attribute-modified",
40         NodeRemoved: "node-removed"
41     };
42     this._breakpointTypeLabels = {};
43     this._breakpointTypeLabels[this._breakpointTypes.SubtreeModified] = WebInspector.UIString("Subtree Modified");
44     this._breakpointTypeLabels[this._breakpointTypes.AttributeModified] = WebInspector.UIString("Attribute Modified");
45     this._breakpointTypeLabels[this._breakpointTypes.NodeRemoved] = WebInspector.UIString("Node Removed");
46
47     this._contextMenuLabels = {};
48     this._contextMenuLabels[this._breakpointTypes.SubtreeModified] = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Break on subtree modifications" : "Break on Subtree Modifications");
49     this._contextMenuLabels[this._breakpointTypes.AttributeModified] = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Break on attributes modifications" : "Break on Attributes Modifications");
50     this._contextMenuLabels[this._breakpointTypes.NodeRemoved] = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Break on node removal" : "Break on Node Removal");
51
52     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._inspectedURLChanged, this);
53     WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.NodeRemoved, this._nodeRemoved, this);
54 }
55
56 WebInspector.DOMBreakpointsSidebarPane.prototype = {
57     _inspectedURLChanged: function(event)
58     {
59         this._reset();
60         var url = event.data;
61         this._inspectedURL = url.removeURLFragment();
62     },
63
64     populateNodeContextMenu: function(node, contextMenu)
65     {
66         var nodeBreakpoints = {};
67         for (var id in this._breakpointElements) {
68             var element = this._breakpointElements[id];
69             if (element._node === node)
70                 nodeBreakpoints[element._type] = true;
71         }
72
73         function toggleBreakpoint(type)
74         {
75             if (!nodeBreakpoints[type])
76                 this._setBreakpoint(node, type, true);
77             else
78                 this._removeBreakpoint(node, type);
79             this._saveBreakpoints();
80         }
81
82         for (var key in this._breakpointTypes) {
83             var type = this._breakpointTypes[key];
84             var label = this._contextMenuLabels[type];
85             contextMenu.appendCheckboxItem(label, toggleBreakpoint.bind(this, type), nodeBreakpoints[type]);
86         }
87     },
88
89     createBreakpointHitStatusMessage: function(auxData, callback)
90     {
91         if (auxData.type === this._breakpointTypes.SubtreeModified) {
92             var targetNodeObject = WebInspector.RemoteObject.fromPayload(auxData.targetNode);
93             function didPushNodeToFrontend(targetNodeId)
94             {
95                 if (targetNodeId)
96                     targetNodeObject.release();
97                 this._doCreateBreakpointHitStatusMessage(auxData, targetNodeId, callback);
98             }
99             targetNodeObject.pushNodeToFrontend(didPushNodeToFrontend.bind(this));
100         } else
101             this._doCreateBreakpointHitStatusMessage(auxData, null, callback);
102     },
103
104     _doCreateBreakpointHitStatusMessage: function (auxData, targetNodeId, callback)
105     {
106         var message;
107         var typeLabel = this._breakpointTypeLabels[auxData.type];
108         var linkifiedNode = WebInspector.panels.elements.linkifyNodeById(auxData.nodeId);
109         var substitutions = [typeLabel, linkifiedNode];
110         var targetNode = "";
111         if (targetNodeId)
112             targetNode = WebInspector.panels.elements.linkifyNodeById(targetNodeId);
113
114         if (auxData.type === this._breakpointTypes.SubtreeModified) {
115             if (auxData.insertion) {
116                 if (targetNodeId !== auxData.nodeId) {
117                     message = "Paused on a \"%s\" breakpoint set on %s, because a new child was added to its descendant %s.";
118                     substitutions.push(targetNode);
119                 } else
120                     message = "Paused on a \"%s\" breakpoint set on %s, because a new child was added to that node.";
121             } else {
122                 message = "Paused on a \"%s\" breakpoint set on %s, because its descendant %s was removed.";
123                 substitutions.push(targetNode);
124             }
125         } else
126             message = "Paused on a \"%s\" breakpoint set on %s.";
127
128         var element = document.createElement("span");
129         var formatters = {
130             s: function(substitution)
131             {
132                 return substitution;
133             }
134         };
135         function append(a, b)
136         {
137             if (typeof b === "string")
138                 b = document.createTextNode(b);
139             element.appendChild(b);
140         }
141         WebInspector.formatLocalized(message, substitutions, formatters, "", append);
142
143         callback(element);
144     },
145
146     _nodeRemoved: function(event)
147     {
148         var node = event.data.node;
149         this._removeBreakpointsForNode(event.data.node);
150         if (!node.children)
151             return;
152         for (var i = 0; i < node.children.length; ++i)
153             this._removeBreakpointsForNode(node.children[i]);
154         this._saveBreakpoints();
155     },
156
157     _removeBreakpointsForNode: function(node)
158     {
159         for (var id in this._breakpointElements) {
160             var element = this._breakpointElements[id];
161             if (element._node === node)
162                 this._removeBreakpoint(element._node, element._type);
163         }
164     },
165
166     _setBreakpoint: function(node, type, enabled)
167     {
168         var breakpointId = this._createBreakpointId(node.id, type);
169         if (breakpointId in this._breakpointElements)
170             return;
171
172         var element = document.createElement("li");
173         element._node = node;
174         element._type = type;
175         element.addEventListener("contextmenu", this._contextMenu.bind(this, node, type), true);
176
177         var checkboxElement = document.createElement("input");
178         checkboxElement.className = "checkbox-elem";
179         checkboxElement.type = "checkbox";
180         checkboxElement.checked = enabled;
181         checkboxElement.addEventListener("click", this._checkboxClicked.bind(this, node, type), false);
182         element._checkboxElement = checkboxElement;
183         element.appendChild(checkboxElement);
184
185         var labelElement = document.createElement("span");
186         element.appendChild(labelElement);
187
188         var linkifiedNode = WebInspector.panels.elements.linkifyNodeById(node.id);
189         linkifiedNode.addStyleClass("monospace");
190         labelElement.appendChild(linkifiedNode);
191
192         var description = document.createElement("div");
193         description.className = "source-text";
194         description.textContent = this._breakpointTypeLabels[type];
195         labelElement.appendChild(description);
196
197         var currentElement = this.listElement.firstChild;
198         while (currentElement) {
199             if (currentElement._type && currentElement._type < element._type)
200                 break;
201             currentElement = currentElement.nextSibling;
202         }
203         this._addListElement(element, currentElement);
204         this._breakpointElements[breakpointId] = element;
205         if (enabled)
206             DOMDebuggerAgent.setDOMBreakpoint(node.id, type);
207     },
208
209     _removeBreakpoint: function(node, type)
210     {
211         var breakpointId = this._createBreakpointId(node.id, type);
212         var element = this._breakpointElements[breakpointId];
213         if (!element)
214             return;
215
216         this._removeListElement(element);
217         delete this._breakpointElements[breakpointId];
218         if (element._checkboxElement.checked)
219             DOMDebuggerAgent.removeDOMBreakpoint(node.id, type);
220     },
221
222     _contextMenu: function(node, type, event)
223     {
224         var contextMenu = new WebInspector.ContextMenu();
225         function removeBreakpoint()
226         {
227             this._removeBreakpoint(node, type);
228             this._saveBreakpoints();
229         }
230         contextMenu.appendItem(WebInspector.UIString("Remove Breakpoint"), removeBreakpoint.bind(this));
231         contextMenu.show(event);
232     },
233
234     _checkboxClicked: function(node, type, event)
235     {
236         if (event.target.checked)
237             DOMDebuggerAgent.setDOMBreakpoint(node.id, type);
238         else
239             DOMDebuggerAgent.removeDOMBreakpoint(node.id, type);
240         this._saveBreakpoints();
241     },
242
243     highlightBreakpoint: function(auxData)
244     {
245         var breakpointId = this._createBreakpointId(auxData.nodeId, auxData.type);
246         var element = this._breakpointElements[breakpointId];
247         if (!element)
248             return;
249         this.expanded = true;
250         element.addStyleClass("breakpoint-hit");
251         this._highlightedElement = element;
252     },
253
254     clearBreakpointHighlight: function()
255     {
256         if (this._highlightedElement) {
257             this._highlightedElement.removeStyleClass("breakpoint-hit");
258             delete this._highlightedElement;
259         }
260     },
261
262     _createBreakpointId: function(nodeId, type)
263     {
264         return nodeId + ":" + type;
265     },
266
267     _saveBreakpoints: function()
268     {
269         var breakpoints = [];
270         var storedBreakpoints = WebInspector.settings.domBreakpoints.get();
271         for (var i = 0; i < storedBreakpoints.length; ++i) {
272             var breakpoint = storedBreakpoints[i];
273             if (breakpoint.url !== this._inspectedURL)
274                 breakpoints.push(breakpoint);
275         }
276         for (var id in this._breakpointElements) {
277             var element = this._breakpointElements[id];
278             breakpoints.push({ url: this._inspectedURL, path: element._node.path(), type: element._type, enabled: element._checkboxElement.checked });
279         }
280         WebInspector.settings.domBreakpoints.set(breakpoints);
281     },
282
283     restoreBreakpoints: function()
284     {
285         var pathToBreakpoints = {};
286
287         function didPushNodeByPathToFrontend(path, nodeId)
288         {
289             var node = WebInspector.domAgent.nodeForId(nodeId);
290             if (!node)
291                 return;
292
293             var breakpoints = pathToBreakpoints[path];
294             for (var i = 0; i < breakpoints.length; ++i)
295                 this._setBreakpoint(node, breakpoints[i].type, breakpoints[i].enabled);
296         }
297
298         var breakpoints = WebInspector.settings.domBreakpoints.get();
299         for (var i = 0; i < breakpoints.length; ++i) {
300             var breakpoint = breakpoints[i];
301             if (breakpoint.url !== this._inspectedURL)
302                 continue;
303             var path = breakpoint.path;
304             if (!pathToBreakpoints[path]) {
305                 pathToBreakpoints[path] = [];
306                 WebInspector.domAgent.pushNodeByPathToFrontend(path, didPushNodeByPathToFrontend.bind(this, path));
307             }
308             pathToBreakpoints[path].push(breakpoint);
309         }
310     }
311 }
312
313 WebInspector.DOMBreakpointsSidebarPane.prototype.__proto__ = WebInspector.NativeBreakpointsSidebarPane.prototype;