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  * @implements {WebInspector.TargetManager.Observer}
     35  */
     36 WebInspector.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 
     60 WebInspector.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  */
    416 WebInspector.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 
    430 WebInspector.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  */
    462 WebInspector.domBreakpointsSidebarPane;
    463