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