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