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