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