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.ObjectPropertiesSection}
    104  */
    105 WebInspector.WatchExpressionsSection = function()
    106 {
    107     this._watchObjectGroupId = "watch-group";
    108 
    109     WebInspector.ObjectPropertiesSection.call(this, WebInspector.runtimeModel.createRemoteObjectFromPrimitiveValue(""));
    110 
    111     this.treeElementConstructor = WebInspector.WatchedPropertyTreeElement;
    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, [], WebInspector.WatchExpressionTreeElement, WebInspector.WatchExpressionsSection.CompareProperties);
    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     addExpression: function(expression)
    228     {
    229         this.watchExpressions.push(expression);
    230         this.saveExpressions();
    231         this.update();
    232     },
    233 
    234     addNewExpressionAndEdit: function()
    235     {
    236         this._newExpressionAdded = true;
    237         this.watchExpressions.push(WebInspector.WatchExpressionsSection.NewWatchExpression);
    238         this.update();
    239     },
    240 
    241     _sectionDoubleClick: function(event)
    242     {
    243         if (event.target !== this.element && event.target !== this.propertiesElement && event.target !== this.emptyElement)
    244             return;
    245         event.consume();
    246         this.addNewExpressionAndEdit();
    247     },
    248 
    249     updateExpression: function(element, value)
    250     {
    251         if (value === null) {
    252             var index = element.property.watchIndex;
    253             this.watchExpressions.splice(index, 1);
    254         }
    255         else
    256             this.watchExpressions[element.property.watchIndex] = value;
    257         this.saveExpressions();
    258         this.update();
    259     },
    260 
    261     _deleteAllExpressions: function()
    262     {
    263         this.watchExpressions = [];
    264         this.saveExpressions();
    265         this.update();
    266     },
    267 
    268     /**
    269      * @return {?TreeElement}
    270      */
    271     findAddedTreeElement: function()
    272     {
    273         var children = this.propertiesTreeOutline.children;
    274         for (var i = 0; i < children.length; ++i) {
    275             if (children[i].property.name === WebInspector.WatchExpressionsSection.NewWatchExpression)
    276                 return children[i];
    277         }
    278         return null;
    279     },
    280 
    281     /**
    282      * @return {number}
    283      */
    284     saveExpressions: function()
    285     {
    286         var toSave = [];
    287         for (var i = 0; i < this.watchExpressions.length; i++)
    288             if (this.watchExpressions[i])
    289                 toSave.push(this.watchExpressions[i]);
    290 
    291         WebInspector.settings.watchExpressions.set(toSave);
    292         return toSave.length;
    293     },
    294 
    295     _mouseMove: function(e)
    296     {
    297         if (this.propertiesElement.firstChild)
    298             this._updateHoveredElement(e.pageY);
    299     },
    300 
    301     _mouseOut: function()
    302     {
    303         if (this._hoveredElement) {
    304             this._hoveredElement.classList.remove("hovered");
    305             delete this._hoveredElement;
    306         }
    307         delete this._lastMouseMovePageY;
    308     },
    309 
    310     _updateHoveredElement: function(pageY)
    311     {
    312         var candidateElement = this.propertiesElement.firstChild;
    313         while (true) {
    314             var next = candidateElement.nextSibling;
    315             while (next && !next.clientHeight)
    316                 next = next.nextSibling;
    317             if (!next || next.totalOffsetTop() > pageY)
    318                 break;
    319             candidateElement = next;
    320         }
    321 
    322         if (this._hoveredElement !== candidateElement) {
    323             if (this._hoveredElement)
    324                 this._hoveredElement.classList.remove("hovered");
    325             if (candidateElement)
    326                 candidateElement.classList.add("hovered");
    327             this._hoveredElement = candidateElement;
    328         }
    329 
    330         this._lastMouseMovePageY = pageY;
    331     },
    332 
    333     _emptyElementContextMenu: function(event)
    334     {
    335         var contextMenu = new WebInspector.ContextMenu(event);
    336         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add watch expression" : "Add Watch Expression"), this.addNewExpressionAndEdit.bind(this));
    337         contextMenu.show();
    338     },
    339 
    340     __proto__: WebInspector.ObjectPropertiesSection.prototype
    341 }
    342 
    343 /**
    344  * @param {!WebInspector.RemoteObjectProperty} propertyA
    345  * @param {!WebInspector.RemoteObjectProperty} propertyB
    346  * @return {number}
    347  */
    348 WebInspector.WatchExpressionsSection.CompareProperties = function(propertyA, propertyB)
    349 {
    350     if (propertyA.watchIndex == propertyB.watchIndex)
    351         return 0;
    352     else if (propertyA.watchIndex < propertyB.watchIndex)
    353         return -1;
    354     else
    355         return 1;
    356 }
    357 
    358 /**
    359  * @constructor
    360  * @extends {WebInspector.ObjectPropertyTreeElement}
    361  * @param {!WebInspector.RemoteObjectProperty} property
    362  */
    363 WebInspector.WatchExpressionTreeElement = function(property)
    364 {
    365     WebInspector.ObjectPropertyTreeElement.call(this, property);
    366 }
    367 
    368 WebInspector.WatchExpressionTreeElement.prototype = {
    369     onexpand: function()
    370     {
    371         WebInspector.ObjectPropertyTreeElement.prototype.onexpand.call(this);
    372         this.treeOutline.section._expandedExpressions[this._expression()] = true;
    373     },
    374 
    375     oncollapse: function()
    376     {
    377         WebInspector.ObjectPropertyTreeElement.prototype.oncollapse.call(this);
    378         delete this.treeOutline.section._expandedExpressions[this._expression()];
    379     },
    380 
    381     onattach: function()
    382     {
    383         WebInspector.ObjectPropertyTreeElement.prototype.onattach.call(this);
    384         if (this.treeOutline.section._expandedExpressions[this._expression()])
    385             this.expanded = true;
    386     },
    387 
    388     _expression: function()
    389     {
    390         return this.property.name;
    391     },
    392 
    393     update: function()
    394     {
    395         WebInspector.ObjectPropertyTreeElement.prototype.update.call(this);
    396 
    397         if (this.property.wasThrown) {
    398             this.valueElement.textContent = WebInspector.UIString("<not available>");
    399             this.listItemElement.classList.add("dimmed");
    400         } else
    401             this.listItemElement.classList.remove("dimmed");
    402 
    403         var deleteButton = document.createElement("input");
    404         deleteButton.type = "button";
    405         deleteButton.title = WebInspector.UIString("Delete watch expression.");
    406         deleteButton.classList.add("enabled-button");
    407         deleteButton.classList.add("delete-button");
    408         deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false);
    409         this.listItemElement.addEventListener("contextmenu", this._contextMenu.bind(this), false);
    410         this.listItemElement.insertBefore(deleteButton, this.listItemElement.firstChild);
    411     },
    412 
    413     /**
    414      * @param {!WebInspector.ContextMenu} contextMenu
    415      * @override
    416      */
    417     populateContextMenu: function(contextMenu)
    418     {
    419         if (!this.isEditing()) {
    420             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add watch expression" : "Add Watch Expression"), this.treeOutline.section.addNewExpressionAndEdit.bind(this.treeOutline.section));
    421             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete watch expression" : "Delete Watch Expression"), this._deleteButtonClicked.bind(this));
    422         }
    423         if (this.treeOutline.section.watchExpressions.length > 1)
    424             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete all watch expressions" : "Delete All Watch Expressions"), this._deleteAllButtonClicked.bind(this));
    425     },
    426 
    427     _contextMenu: function(event)
    428     {
    429         var contextMenu = new WebInspector.ContextMenu(event);
    430         this.populateContextMenu(contextMenu);
    431         contextMenu.show();
    432     },
    433 
    434     _deleteAllButtonClicked: function()
    435     {
    436         this.treeOutline.section._deleteAllExpressions();
    437     },
    438 
    439     _deleteButtonClicked: function()
    440     {
    441         this.treeOutline.section.updateExpression(this, null);
    442     },
    443 
    444     /**
    445      * @return {boolean}
    446      */
    447     renderPromptAsBlock: function()
    448     {
    449         return true;
    450     },
    451 
    452     /**
    453      * @override
    454      * @return {{element: !Element, value: (string|undefined)}}
    455      */
    456     elementAndValueToEdit: function()
    457     {
    458         return { element: this.nameElement, value: this.property.name.trim() };
    459     },
    460 
    461     /**
    462      * @override
    463      */
    464     editingCancelled: function(element, context)
    465     {
    466         if (!context.elementToEdit.textContent)
    467             this.treeOutline.section.updateExpression(this, null);
    468 
    469         WebInspector.ObjectPropertyTreeElement.prototype.editingCancelled.call(this, element, context);
    470     },
    471 
    472     /**
    473      * @override
    474      * @param {string} expression
    475      */
    476     applyExpression: function(expression)
    477     {
    478         expression = expression.trim();
    479         this.property.name = expression || null;
    480         this.treeOutline.section.updateExpression(this, expression);
    481     },
    482 
    483     __proto__: WebInspector.ObjectPropertyTreeElement.prototype
    484 }
    485 
    486 
    487 /**
    488  * @constructor
    489  * @extends {WebInspector.ObjectPropertyTreeElement}
    490  * @param {!WebInspector.RemoteObjectProperty} property
    491  */
    492 WebInspector.WatchedPropertyTreeElement = function(property)
    493 {
    494     WebInspector.ObjectPropertyTreeElement.call(this, property);
    495 }
    496 
    497 WebInspector.WatchedPropertyTreeElement.prototype = {
    498     onattach: function()
    499     {
    500         WebInspector.ObjectPropertyTreeElement.prototype.onattach.call(this);
    501         if (this.hasChildren && this.propertyPath() in this.treeOutline.section._expandedProperties)
    502             this.expand();
    503     },
    504 
    505     onexpand: function()
    506     {
    507         WebInspector.ObjectPropertyTreeElement.prototype.onexpand.call(this);
    508         this.treeOutline.section._expandedProperties[this.propertyPath()] = true;
    509     },
    510 
    511     oncollapse: function()
    512     {
    513         WebInspector.ObjectPropertyTreeElement.prototype.oncollapse.call(this);
    514         delete this.treeOutline.section._expandedProperties[this.propertyPath()];
    515     },
    516 
    517     __proto__: WebInspector.ObjectPropertyTreeElement.prototype
    518 }
    519