Home | History | Annotate | Download | only in sources
      1 /*
      2  * Copyright (C) IBM Corp. 2009  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 IBM Corp. 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.SidebarPane}
     34  */
     35 WebInspector.WatchExpressionsSidebarPane = function()
     36 {
     37     WebInspector.SidebarPane.call(this, WebInspector.UIString("Watch Expressions"));
     38 
     39     this.section = new WebInspector.WatchExpressionsSection();
     40     this.bodyElement.appendChild(this.section.element);
     41 
     42     var refreshButton = document.createElement("button");
     43     refreshButton.className = "pane-title-button refresh";
     44     refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false);
     45     refreshButton.title = WebInspector.UIString("Refresh");
     46     this.titleElement.appendChild(refreshButton);
     47 
     48     var addButton = document.createElement("button");
     49     addButton.className = "pane-title-button add";
     50     addButton.addEventListener("click", this._addButtonClicked.bind(this), false);
     51     this.titleElement.appendChild(addButton);
     52     addButton.title = WebInspector.UIString("Add watch expression");
     53 
     54     this._requiresUpdate = true;
     55     WebInspector.context.addFlavorChangeListener(WebInspector.ExecutionContext ,this.refreshExpressions, this);
     56 }
     57 
     58 WebInspector.WatchExpressionsSidebarPane.prototype = {
     59     wasShown: function()
     60     {
     61         this._refreshExpressionsIfNeeded();
     62     },
     63 
     64     refreshExpressions: function()
     65     {
     66         this._requiresUpdate = true;
     67         this._refreshExpressionsIfNeeded();
     68     },
     69 
     70     addExpression: function(expression)
     71     {
     72         this.section.addExpression(expression);
     73         this.expand();
     74     },
     75 
     76     _refreshExpressionsIfNeeded: function()
     77     {
     78         if (this._requiresUpdate && this.isShowing()) {
     79             this.section.update();
     80             delete this._requiresUpdate;
     81         } else
     82             this._requiresUpdate = true;
     83     },
     84 
     85     _addButtonClicked: function(event)
     86     {
     87         event.consume();
     88         this.expand();
     89         this.section.addNewExpressionAndEdit();
     90     },
     91 
     92     _refreshButtonClicked: function(event)
     93     {
     94         event.consume();
     95         this.refreshExpressions();
     96     },
     97 
     98     __proto__: WebInspector.SidebarPane.prototype
     99 }
    100 
    101 /**
    102  * @constructor
    103  * @extends {WebInspector.PropertiesSection}
    104  */
    105 WebInspector.WatchExpressionsSection = function()
    106 {
    107     this._watchObjectGroupId = "watch-group";
    108 
    109     WebInspector.PropertiesSection.call(this, "");
    110     this.treeElementConstructor = WebInspector.WatchedPropertyTreeElement;
    111     this.skipProto = false;
    112     this._expandedExpressions = {};
    113     this._expandedProperties = {};
    114 
    115     this.emptyElement = document.createElement("div");
    116     this.emptyElement.className = "info";
    117     this.emptyElement.textContent = WebInspector.UIString("No Watch Expressions");
    118 
    119     this.watchExpressions = WebInspector.settings.watchExpressions.get();
    120 
    121     this.headerElement.className = "hidden";
    122     this.editable = true;
    123     this.expanded = true;
    124     this.propertiesElement.classList.add("watch-expressions");
    125 
    126     this.element.addEventListener("mousemove", this._mouseMove.bind(this), true);
    127     this.element.addEventListener("mouseout", this._mouseOut.bind(this), true);
    128     this.element.addEventListener("dblclick", this._sectionDoubleClick.bind(this), false);
    129     this.emptyElement.addEventListener("contextmenu", this._emptyElementContextMenu.bind(this), false);
    130 }
    131 
    132 WebInspector.WatchExpressionsSection.NewWatchExpression = "\xA0";
    133 
    134 WebInspector.WatchExpressionsSection.prototype = {
    135     /**
    136      * @param {!Event=} e
    137      */
    138     update: function(e)
    139     {
    140         if (e)
    141             e.consume();
    142 
    143         /***
    144          * @param {string} expression
    145          * @param {number} watchIndex
    146          * @param {?WebInspector.RemoteObject} result
    147          * @param {boolean} wasThrown
    148          * @this {WebInspector.WatchExpressionsSection}
    149          */
    150         function appendResult(expression, watchIndex, result, wasThrown)
    151         {
    152             if (!result)
    153                 return;
    154 
    155             var property = new WebInspector.RemoteObjectProperty(expression, result);
    156             property.watchIndex = watchIndex;
    157             property.wasThrown = wasThrown;
    158 
    159             // To clarify what's going on here:
    160             // In the outer function, we calculate the number of properties
    161             // that we're going to be updating, and set that in the
    162             // propertyCount variable.
    163             // In this function, we test to see when we are processing the
    164             // last property, and then call the superclass's updateProperties()
    165             // method to get all the properties refreshed at once.
    166             properties.push(property);
    167 
    168             if (properties.length == propertyCount) {
    169                 this.updateProperties(properties);
    170 
    171                 // check to see if we just added a new watch expression,
    172                 // which will always be the last property
    173                 if (this._newExpressionAdded) {
    174                     delete this._newExpressionAdded;
    175 
    176                     var treeElement = this.findAddedTreeElement();
    177                     if (treeElement)
    178                         treeElement.startEditing();
    179                 }
    180 
    181                 // Force displaying delete button for hovered element.
    182                 if (this._lastMouseMovePageY)
    183                     this._updateHoveredElement(this._lastMouseMovePageY);
    184             }
    185         }
    186 
    187         // TODO: pass exact injected script id.
    188         WebInspector.targetManager.targets().forEach(function(target) {target.runtimeAgent().releaseObjectGroup(this._watchObjectGroupId)}, this);
    189         var properties = [];
    190 
    191         // Count the properties, so we known when to call this.updateProperties()
    192         // in appendResult()
    193         var propertyCount = 0;
    194         for (var i = 0; i < this.watchExpressions.length; ++i) {
    195             if (!this.watchExpressions[i])
    196                 continue;
    197             ++propertyCount;
    198         }
    199 
    200         // Now process all the expressions, since we have the actual count,
    201         // which is checked in the appendResult inner function.
    202         var currentExecutionContext = WebInspector.context.flavor(WebInspector.ExecutionContext);
    203         if (currentExecutionContext) {
    204             for (var i = 0; i < this.watchExpressions.length; ++i) {
    205                 var expression = this.watchExpressions[i];
    206                 if (!expression)
    207                     continue;
    208 
    209                 currentExecutionContext.evaluate(expression, this._watchObjectGroupId, false, true, false, false, appendResult.bind(this, expression, i));
    210             }
    211         }
    212 
    213         if (!propertyCount) {
    214             if (!this.emptyElement.parentNode)
    215                 this.element.appendChild(this.emptyElement);
    216         } else {
    217             if (this.emptyElement.parentNode)
    218                 this.element.removeChild(this.emptyElement);
    219         }
    220 
    221         // note this is setting the expansion of the tree, not the section;
    222         // with no expressions, and expanded tree, we get some extra vertical
    223         // white space
    224         this.expanded = (propertyCount != 0);
    225     },
    226 
    227 
    228     /**
    229      * @param {!Array.<!WebInspector.RemoteObjectProperty>} properties
    230      */
    231     updateProperties: function(properties)
    232     {
    233         this.propertiesTreeOutline.removeChildren();
    234         WebInspector.ObjectPropertyTreeElement.populateWithProperties(this.propertiesTreeOutline, properties, [],
    235             WebInspector.WatchExpressionTreeElement, WebInspector.WatchExpressionsSection.CompareProperties, false, null);
    236 
    237         this.propertiesForTest = properties;
    238     },
    239 
    240     addExpression: function(expression)
    241     {
    242         this.watchExpressions.push(expression);
    243         this.saveExpressions();
    244         this.update();
    245     },
    246 
    247     addNewExpressionAndEdit: function()
    248     {
    249         this._newExpressionAdded = true;
    250         this.watchExpressions.push(WebInspector.WatchExpressionsSection.NewWatchExpression);
    251         this.update();
    252     },
    253 
    254     _sectionDoubleClick: function(event)
    255     {
    256         if (event.target !== this.element && event.target !== this.propertiesElement && event.target !== this.emptyElement)
    257             return;
    258         event.consume();
    259         this.addNewExpressionAndEdit();
    260     },
    261 
    262     updateExpression: function(element, value)
    263     {
    264         if (value === null) {
    265             var index = element.property.watchIndex;
    266             this.watchExpressions.splice(index, 1);
    267         }
    268         else
    269             this.watchExpressions[element.property.watchIndex] = value;
    270         this.saveExpressions();
    271         this.update();
    272     },
    273 
    274     _deleteAllExpressions: function()
    275     {
    276         this.watchExpressions = [];
    277         this.saveExpressions();
    278         this.update();
    279     },
    280 
    281     /**
    282      * @return {?TreeElement}
    283      */
    284     findAddedTreeElement: function()
    285     {
    286         var children = this.propertiesTreeOutline.children;
    287         for (var i = 0; i < children.length; ++i) {
    288             if (children[i].property.name === WebInspector.WatchExpressionsSection.NewWatchExpression)
    289                 return children[i];
    290         }
    291         return null;
    292     },
    293 
    294     /**
    295      * @return {number}
    296      */
    297     saveExpressions: function()
    298     {
    299         var toSave = [];
    300         for (var i = 0; i < this.watchExpressions.length; i++)
    301             if (this.watchExpressions[i])
    302                 toSave.push(this.watchExpressions[i]);
    303 
    304         WebInspector.settings.watchExpressions.set(toSave);
    305         return toSave.length;
    306     },
    307 
    308     _mouseMove: function(e)
    309     {
    310         if (this.propertiesElement.firstChild)
    311             this._updateHoveredElement(e.pageY);
    312     },
    313 
    314     _mouseOut: function()
    315     {
    316         if (this._hoveredElement) {
    317             this._hoveredElement.classList.remove("hovered");
    318             delete this._hoveredElement;
    319         }
    320         delete this._lastMouseMovePageY;
    321     },
    322 
    323     _updateHoveredElement: function(pageY)
    324     {
    325         var candidateElement = this.propertiesElement.firstChild;
    326         while (true) {
    327             var next = candidateElement.nextSibling;
    328             while (next && !next.clientHeight)
    329                 next = next.nextSibling;
    330             if (!next || next.totalOffsetTop() > pageY)
    331                 break;
    332             candidateElement = next;
    333         }
    334 
    335         if (this._hoveredElement !== candidateElement) {
    336             if (this._hoveredElement)
    337                 this._hoveredElement.classList.remove("hovered");
    338             if (candidateElement)
    339                 candidateElement.classList.add("hovered");
    340             this._hoveredElement = candidateElement;
    341         }
    342 
    343         this._lastMouseMovePageY = pageY;
    344     },
    345 
    346     _emptyElementContextMenu: function(event)
    347     {
    348         var contextMenu = new WebInspector.ContextMenu(event);
    349         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add watch expression" : "Add Watch Expression"), this.addNewExpressionAndEdit.bind(this));
    350         contextMenu.show();
    351     },
    352 
    353     __proto__: WebInspector.PropertiesSection.prototype
    354 }
    355 
    356 /**
    357  * @param {!WebInspector.RemoteObjectProperty} propertyA
    358  * @param {!WebInspector.RemoteObjectProperty} propertyB
    359  * @return {number}
    360  */
    361 WebInspector.WatchExpressionsSection.CompareProperties = function(propertyA, propertyB)
    362 {
    363     if (propertyA.watchIndex == propertyB.watchIndex)
    364         return 0;
    365     else if (propertyA.watchIndex < propertyB.watchIndex)
    366         return -1;
    367     else
    368         return 1;
    369 }
    370 
    371 /**
    372  * @constructor
    373  * @extends {WebInspector.ObjectPropertyTreeElement}
    374  * @param {!WebInspector.RemoteObjectProperty} property
    375  */
    376 WebInspector.WatchExpressionTreeElement = function(property)
    377 {
    378     WebInspector.ObjectPropertyTreeElement.call(this, property);
    379 }
    380 
    381 WebInspector.WatchExpressionTreeElement.prototype = {
    382     onexpand: function()
    383     {
    384         WebInspector.ObjectPropertyTreeElement.prototype.onexpand.call(this);
    385         this.treeOutline.section._expandedExpressions[this._expression()] = true;
    386     },
    387 
    388     oncollapse: function()
    389     {
    390         WebInspector.ObjectPropertyTreeElement.prototype.oncollapse.call(this);
    391         delete this.treeOutline.section._expandedExpressions[this._expression()];
    392     },
    393 
    394     onattach: function()
    395     {
    396         WebInspector.ObjectPropertyTreeElement.prototype.onattach.call(this);
    397         if (this.treeOutline.section._expandedExpressions[this._expression()])
    398             this.expanded = true;
    399     },
    400 
    401     _expression: function()
    402     {
    403         return this.property.name;
    404     },
    405 
    406     update: function()
    407     {
    408         WebInspector.ObjectPropertyTreeElement.prototype.update.call(this);
    409 
    410         if (this.property.wasThrown) {
    411             this.valueElement.textContent = WebInspector.UIString("<not available>");
    412             this.listItemElement.classList.add("dimmed");
    413         } else
    414             this.listItemElement.classList.remove("dimmed");
    415 
    416         var deleteButton = document.createElement("input");
    417         deleteButton.type = "button";
    418         deleteButton.title = WebInspector.UIString("Delete watch expression.");
    419         deleteButton.classList.add("enabled-button");
    420         deleteButton.classList.add("delete-button");
    421         deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false);
    422         this.listItemElement.addEventListener("contextmenu", this._contextMenu.bind(this), false);
    423         this.listItemElement.insertBefore(deleteButton, this.listItemElement.firstChild);
    424     },
    425 
    426     /**
    427      * @param {!WebInspector.ContextMenu} contextMenu
    428      * @override
    429      */
    430     populateContextMenu: function(contextMenu)
    431     {
    432         if (!this.isEditing()) {
    433             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add watch expression" : "Add Watch Expression"), this.treeOutline.section.addNewExpressionAndEdit.bind(this.treeOutline.section));
    434             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete watch expression" : "Delete Watch Expression"), this._deleteButtonClicked.bind(this));
    435         }
    436         if (this.treeOutline.section.watchExpressions.length > 1)
    437             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete all watch expressions" : "Delete All Watch Expressions"), this._deleteAllButtonClicked.bind(this));
    438     },
    439 
    440     _contextMenu: function(event)
    441     {
    442         var contextMenu = new WebInspector.ContextMenu(event);
    443         this.populateContextMenu(contextMenu);
    444         contextMenu.show();
    445     },
    446 
    447     _deleteAllButtonClicked: function()
    448     {
    449         this.treeOutline.section._deleteAllExpressions();
    450     },
    451 
    452     _deleteButtonClicked: function()
    453     {
    454         this.treeOutline.section.updateExpression(this, null);
    455     },
    456 
    457     /**
    458      * @return {boolean}
    459      */
    460     renderPromptAsBlock: function()
    461     {
    462         return true;
    463     },
    464 
    465     /**
    466      * @override
    467      * @return {{element: !Element, value: (string|undefined)}}
    468      */
    469     elementAndValueToEdit: function()
    470     {
    471         return { element: this.nameElement, value: this.property.name.trim() };
    472     },
    473 
    474     /**
    475      * @override
    476      */
    477     editingCancelled: function(element, context)
    478     {
    479         if (!context.elementToEdit.textContent)
    480             this.treeOutline.section.updateExpression(this, null);
    481 
    482         WebInspector.ObjectPropertyTreeElement.prototype.editingCancelled.call(this, element, context);
    483     },
    484 
    485     /**
    486      * @override
    487      * @param {string} expression
    488      */
    489     applyExpression: function(expression)
    490     {
    491         expression = expression.trim();
    492         this.property.name = expression || null;
    493         this.treeOutline.section.updateExpression(this, expression);
    494     },
    495 
    496     __proto__: WebInspector.ObjectPropertyTreeElement.prototype
    497 }
    498 
    499 
    500 /**
    501  * @constructor
    502  * @extends {WebInspector.ObjectPropertyTreeElement}
    503  * @param {!WebInspector.RemoteObjectProperty} property
    504  */
    505 WebInspector.WatchedPropertyTreeElement = function(property)
    506 {
    507     WebInspector.ObjectPropertyTreeElement.call(this, property);
    508 }
    509 
    510 WebInspector.WatchedPropertyTreeElement.prototype = {
    511     onattach: function()
    512     {
    513         WebInspector.ObjectPropertyTreeElement.prototype.onattach.call(this);
    514         if (this.hasChildren && this.propertyPath() in this.treeOutline.section._expandedProperties)
    515             this.expand();
    516     },
    517 
    518     onexpand: function()
    519     {
    520         WebInspector.ObjectPropertyTreeElement.prototype.onexpand.call(this);
    521         this.treeOutline.section._expandedProperties[this.propertyPath()] = true;
    522     },
    523 
    524     oncollapse: function()
    525     {
    526         WebInspector.ObjectPropertyTreeElement.prototype.oncollapse.call(this);
    527         delete this.treeOutline.section._expandedProperties[this.propertyPath()];
    528     },
    529 
    530     __proto__: WebInspector.ObjectPropertyTreeElement.prototype
    531 }
    532