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