• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/**
32 * @constructor
33 * @extends {WebInspector.NativeBreakpointsSidebarPane}
34 * @implements {WebInspector.TargetManager.Observer}
35 */
36WebInspector.DOMBreakpointsSidebarPane = function()
37{
38    WebInspector.NativeBreakpointsSidebarPane.call(this, WebInspector.UIString("DOM Breakpoints"));
39
40    this._breakpointElements = {};
41
42    this._breakpointTypes = {
43        SubtreeModified: "subtree-modified",
44        AttributeModified: "attribute-modified",
45        NodeRemoved: "node-removed"
46    };
47    this._breakpointTypeLabels = {};
48    this._breakpointTypeLabels[this._breakpointTypes.SubtreeModified] = WebInspector.UIString("Subtree Modified");
49    this._breakpointTypeLabels[this._breakpointTypes.AttributeModified] = WebInspector.UIString("Attribute Modified");
50    this._breakpointTypeLabels[this._breakpointTypes.NodeRemoved] = WebInspector.UIString("Node Removed");
51
52    this._contextMenuLabels = {};
53    this._contextMenuLabels[this._breakpointTypes.SubtreeModified] = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Subtree modifications" : "Subtree Modifications");
54    this._contextMenuLabels[this._breakpointTypes.AttributeModified] = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Attributes modifications" : "Attributes Modifications");
55    this._contextMenuLabels[this._breakpointTypes.NodeRemoved] = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Node removal" : "Node Removal");
56
57    WebInspector.targetManager.observeTargets(this);
58}
59
60WebInspector.DOMBreakpointsSidebarPane.prototype = {
61    /**
62     * @param {!WebInspector.Target} target
63     */
64    targetAdded: function(target)
65    {
66        target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._inspectedURLChanged, this);
67        target.domModel.addEventListener(WebInspector.DOMModel.Events.NodeRemoved, this._nodeRemoved, this);
68    },
69
70    /**
71     * @param {!WebInspector.Target} target
72     */
73    targetRemoved: function(target)
74    {
75        target.resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._inspectedURLChanged, this);
76        target.domModel.removeEventListener(WebInspector.DOMModel.Events.NodeRemoved, this._nodeRemoved, this);
77    },
78
79    _inspectedURLChanged: function(event)
80    {
81        this._breakpointElements = {};
82        this._reset();
83        var url = /** @type {string} */ (event.data);
84        this._inspectedURL = url.removeURLFragment();
85    },
86
87    /**
88     * @param {!WebInspector.DOMNode} node
89     * @param {!WebInspector.ContextMenu} contextMenu
90     */
91    populateNodeContextMenu: function(node, contextMenu)
92    {
93        if (node.pseudoType())
94            return;
95
96        var nodeBreakpoints = {};
97        for (var id in this._breakpointElements) {
98            var element = this._breakpointElements[id];
99            if (element._node === node)
100                nodeBreakpoints[element._type] = true;
101        }
102
103        /**
104         * @param {string} type
105         * @this {WebInspector.DOMBreakpointsSidebarPane}
106         */
107        function toggleBreakpoint(type)
108        {
109            if (!nodeBreakpoints[type])
110                this._setBreakpoint(node, type, true);
111            else
112                this._removeBreakpoint(node, type);
113            this._saveBreakpoints();
114        }
115
116        var breakPointSubMenu = contextMenu.appendSubMenuItem(WebInspector.UIString("Break on..."));
117        for (var key in this._breakpointTypes) {
118            var type = this._breakpointTypes[key];
119            var label = this._contextMenuLabels[type];
120            breakPointSubMenu.appendCheckboxItem(label, toggleBreakpoint.bind(this, type), nodeBreakpoints[type]);
121        }
122    },
123
124    /**
125     * @param {!WebInspector.DebuggerPausedDetails} details
126     * @param {function(!Element)} callback
127     */
128    createBreakpointHitStatusMessage: function(details, callback)
129    {
130        var auxData = /** @type {!Object} */ (details.auxData);
131        var domModel = details.target().domModel;
132        if (auxData.type === this._breakpointTypes.SubtreeModified) {
133            var targetNodeObject = details.target().runtimeModel.createRemoteObject(auxData["targetNode"]);
134            targetNodeObject.pushNodeToFrontend(didPushNodeToFrontend.bind(this));
135        } else {
136            this._doCreateBreakpointHitStatusMessage(auxData, domModel.nodeForId(auxData.nodeId), null, callback);
137        }
138
139        /**
140         * @param {?WebInspector.DOMNode} targetNode
141         * @this {WebInspector.DOMBreakpointsSidebarPane}
142         */
143        function didPushNodeToFrontend(targetNode)
144        {
145            if (targetNode)
146                targetNodeObject.release();
147            this._doCreateBreakpointHitStatusMessage(auxData, domModel.nodeForId(auxData.nodeId), targetNode, callback);
148        }
149    },
150
151    /**
152     * @param {!Object} auxData
153     * @param {?WebInspector.DOMNode} node
154     * @param {?WebInspector.DOMNode} targetNode
155     * @param {function(!Element)} callback
156     */
157    _doCreateBreakpointHitStatusMessage: function(auxData, node, targetNode, callback)
158    {
159        var message;
160        var typeLabel = this._breakpointTypeLabels[auxData.type];
161        var linkifiedNode = WebInspector.DOMPresentationUtils.linkifyNodeReference(node);
162        var substitutions = [typeLabel, linkifiedNode];
163        var targetNodeLink = "";
164        if (targetNode)
165            targetNodeLink = WebInspector.DOMPresentationUtils.linkifyNodeReference(targetNode);
166
167        if (auxData.type === this._breakpointTypes.SubtreeModified) {
168            if (auxData.insertion) {
169                if (targetNode !== node) {
170                    message = "Paused on a \"%s\" breakpoint set on %s, because a new child was added to its descendant %s.";
171                    substitutions.push(targetNodeLink);
172                } else
173                    message = "Paused on a \"%s\" breakpoint set on %s, because a new child was added to that node.";
174            } else {
175                message = "Paused on a \"%s\" breakpoint set on %s, because its descendant %s was removed.";
176                substitutions.push(targetNodeLink);
177            }
178        } else
179            message = "Paused on a \"%s\" breakpoint set on %s.";
180
181        var element = document.createElement("span");
182        var formatters = {
183            s: function(substitution)
184            {
185                return substitution;
186            }
187        };
188        function append(a, b)
189        {
190            if (typeof b === "string")
191                b = document.createTextNode(b);
192            element.appendChild(b);
193        }
194        WebInspector.formatLocalized(message, substitutions, formatters, "", append);
195
196        callback(element);
197    },
198
199    _nodeRemoved: function(event)
200    {
201        var node = event.data.node;
202        this._removeBreakpointsForNode(event.data.node);
203        var children = node.children();
204        if (!children)
205            return;
206        for (var i = 0; i < children.length; ++i)
207            this._removeBreakpointsForNode(children[i]);
208        this._saveBreakpoints();
209    },
210
211    _removeBreakpointsForNode: function(node)
212    {
213        for (var id in this._breakpointElements) {
214            var element = this._breakpointElements[id];
215            if (element._node === node)
216                this._removeBreakpoint(element._node, element._type);
217        }
218    },
219
220    _setBreakpoint: function(node, type, enabled)
221    {
222        var breakpointId = this._createBreakpointId(node.id, type);
223        if (breakpointId in this._breakpointElements)
224            return;
225
226        var element = document.createElement("li");
227        element._node = node;
228        element._type = type;
229        element.addEventListener("contextmenu", this._contextMenu.bind(this, node, type), true);
230
231        var checkboxElement = document.createElement("input");
232        checkboxElement.className = "checkbox-elem";
233        checkboxElement.type = "checkbox";
234        checkboxElement.checked = enabled;
235        checkboxElement.addEventListener("click", this._checkboxClicked.bind(this, node, type), false);
236        element._checkboxElement = checkboxElement;
237        element.appendChild(checkboxElement);
238
239        var labelElement = document.createElement("span");
240        element.appendChild(labelElement);
241
242        var linkifiedNode = WebInspector.DOMPresentationUtils.linkifyNodeReference(node);
243        linkifiedNode.classList.add("monospace");
244        labelElement.appendChild(linkifiedNode);
245
246        var description = document.createElement("div");
247        description.className = "source-text";
248        description.textContent = this._breakpointTypeLabels[type];
249        labelElement.appendChild(description);
250
251        var currentElement = this.listElement.firstChild;
252        while (currentElement) {
253            if (currentElement._type && currentElement._type < element._type)
254                break;
255            currentElement = currentElement.nextSibling;
256        }
257        this._addListElement(element, currentElement);
258        this._breakpointElements[breakpointId] = element;
259        if (enabled)
260            DOMDebuggerAgent.setDOMBreakpoint(node.id, type);
261    },
262
263    _removeAllBreakpoints: function()
264    {
265        for (var id in this._breakpointElements) {
266            var element = this._breakpointElements[id];
267            this._removeBreakpoint(element._node, element._type);
268        }
269        this._saveBreakpoints();
270    },
271
272    _removeBreakpoint: function(node, type)
273    {
274        var breakpointId = this._createBreakpointId(node.id, type);
275        var element = this._breakpointElements[breakpointId];
276        if (!element)
277            return;
278
279        this._removeListElement(element);
280        delete this._breakpointElements[breakpointId];
281        if (element._checkboxElement.checked)
282            DOMDebuggerAgent.removeDOMBreakpoint(node.id, type);
283    },
284
285    _contextMenu: function(node, type, event)
286    {
287        var contextMenu = new WebInspector.ContextMenu(event);
288
289        /**
290         * @this {WebInspector.DOMBreakpointsSidebarPane}
291         */
292        function removeBreakpoint()
293        {
294            this._removeBreakpoint(node, type);
295            this._saveBreakpoints();
296        }
297        contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove breakpoint" : "Remove Breakpoint"), removeBreakpoint.bind(this));
298        contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove all DOM breakpoints" : "Remove All DOM Breakpoints"), this._removeAllBreakpoints.bind(this));
299        contextMenu.show();
300    },
301
302    _checkboxClicked: function(node, type, event)
303    {
304        if (event.target.checked)
305            DOMDebuggerAgent.setDOMBreakpoint(node.id, type);
306        else
307            DOMDebuggerAgent.removeDOMBreakpoint(node.id, type);
308        this._saveBreakpoints();
309    },
310
311    highlightBreakpoint: function(auxData)
312    {
313        var breakpointId = this._createBreakpointId(auxData.nodeId, auxData.type);
314        var element = this._breakpointElements[breakpointId];
315        if (!element)
316            return;
317        this.expand();
318        element.classList.add("breakpoint-hit");
319        this._highlightedElement = element;
320    },
321
322    clearBreakpointHighlight: function()
323    {
324        if (this._highlightedElement) {
325            this._highlightedElement.classList.remove("breakpoint-hit");
326            delete this._highlightedElement;
327        }
328    },
329
330    _createBreakpointId: function(nodeId, type)
331    {
332        return nodeId + ":" + type;
333    },
334
335    _saveBreakpoints: function()
336    {
337        var breakpoints = [];
338        var storedBreakpoints = WebInspector.settings.domBreakpoints.get();
339        for (var i = 0; i < storedBreakpoints.length; ++i) {
340            var breakpoint = storedBreakpoints[i];
341            if (breakpoint.url !== this._inspectedURL)
342                breakpoints.push(breakpoint);
343        }
344        for (var id in this._breakpointElements) {
345            var element = this._breakpointElements[id];
346            breakpoints.push({ url: this._inspectedURL, path: element._node.path(), type: element._type, enabled: element._checkboxElement.checked });
347        }
348        WebInspector.settings.domBreakpoints.set(breakpoints);
349    },
350
351    /**
352     * @param {!WebInspector.Target} target
353     */
354    restoreBreakpoints: function(target)
355    {
356        var pathToBreakpoints = {};
357
358        /**
359         * @param {string} path
360         * @param {?DOMAgent.NodeId} nodeId
361         * @this {WebInspector.DOMBreakpointsSidebarPane}
362         */
363        function didPushNodeByPathToFrontend(path, nodeId)
364        {
365            var node = nodeId ? target.domModel.nodeForId(nodeId) : null;
366            if (!node)
367                return;
368
369            var breakpoints = pathToBreakpoints[path];
370            for (var i = 0; i < breakpoints.length; ++i)
371                this._setBreakpoint(node, breakpoints[i].type, breakpoints[i].enabled);
372        }
373
374        var breakpoints = WebInspector.settings.domBreakpoints.get();
375        for (var i = 0; i < breakpoints.length; ++i) {
376            var breakpoint = breakpoints[i];
377            if (breakpoint.url !== this._inspectedURL)
378                continue;
379            var path = breakpoint.path;
380            if (!pathToBreakpoints[path]) {
381                pathToBreakpoints[path] = [];
382                target.domModel.pushNodeByPathToFrontend(path, didPushNodeByPathToFrontend.bind(this, path));
383            }
384            pathToBreakpoints[path].push(breakpoint);
385        }
386    },
387
388    /**
389     * @param {!WebInspector.Panel} panel
390     * @return {!WebInspector.DOMBreakpointsSidebarPane.Proxy}
391     */
392    createProxy: function(panel)
393    {
394        var proxy = new WebInspector.DOMBreakpointsSidebarPane.Proxy(this, panel);
395        if (!this._proxies)
396            this._proxies = [];
397        this._proxies.push(proxy);
398        return proxy;
399    },
400
401    onContentReady: function()
402    {
403        for (var i = 0; i != this._proxies.length; i++)
404            this._proxies[i].onContentReady();
405    },
406
407    __proto__: WebInspector.NativeBreakpointsSidebarPane.prototype
408}
409
410/**
411 * @constructor
412 * @extends {WebInspector.SidebarPane}
413 * @param {!WebInspector.DOMBreakpointsSidebarPane} pane
414 * @param {!WebInspector.Panel} panel
415 */
416WebInspector.DOMBreakpointsSidebarPane.Proxy = function(pane, panel)
417{
418    WebInspector.View._assert(!pane.titleElement.firstChild, "Cannot create proxy for a sidebar pane with a toolbar");
419
420    WebInspector.SidebarPane.call(this, pane.title());
421    this.registerRequiredCSS("breakpointsList.css");
422
423    this._wrappedPane = pane;
424    this._panel = panel;
425
426    this.bodyElement.remove();
427    this.bodyElement = this._wrappedPane.bodyElement;
428}
429
430WebInspector.DOMBreakpointsSidebarPane.Proxy.prototype = {
431    expand: function()
432    {
433        this._wrappedPane.expand();
434    },
435
436    onContentReady: function()
437    {
438        if (this._panel.isShowing())
439            this._reattachBody();
440
441        WebInspector.SidebarPane.prototype.onContentReady.call(this);
442    },
443
444    wasShown: function()
445    {
446        WebInspector.SidebarPane.prototype.wasShown.call(this);
447        this._reattachBody();
448    },
449
450    _reattachBody: function()
451    {
452        if (this.bodyElement.parentNode !== this.element)
453            this.element.appendChild(this.bodyElement);
454    },
455
456    __proto__: WebInspector.SidebarPane.prototype
457}
458
459/**
460 * @type {!WebInspector.DOMBreakpointsSidebarPane}
461 */
462WebInspector.domBreakpointsSidebarPane;
463