Home | History | Annotate | Download | only in components
      1 /*
      2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
      3  * Copyright (C) 2009 Joseph Pecoraro
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  * 1. Redistributions of source code must retain the above copyright
      9  *    notice, this list of conditions and the following disclaimer.
     10  * 2. Redistributions in binary form must reproduce the above copyright
     11  *    notice, this list of conditions and the following disclaimer in the
     12  *    documentation and/or other materials provided with the distribution.
     13  *
     14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
     15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
     18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     25  */
     26 
     27 /**
     28  * @constructor
     29  * @extends {WebInspector.PropertiesSection}
     30  * @param {!WebInspector.RemoteObject} object
     31  * @param {?string|!Element=} title
     32  * @param {string=} subtitle
     33  * @param {?string=} emptyPlaceholder
     34  * @param {boolean=} ignoreHasOwnProperty
     35  * @param {!Array.<!WebInspector.RemoteObjectProperty>=} extraProperties
     36  * @param {function(new:TreeElement, !WebInspector.RemoteObjectProperty)=} treeElementConstructor
     37  */
     38 WebInspector.ObjectPropertiesSection = function(object, title, subtitle, emptyPlaceholder, ignoreHasOwnProperty, extraProperties, treeElementConstructor)
     39 {
     40     this._emptyPlaceholder = emptyPlaceholder;
     41     this.object = object;
     42     this.ignoreHasOwnProperty = ignoreHasOwnProperty;
     43     this.extraProperties = extraProperties;
     44     this.treeElementConstructor = treeElementConstructor || WebInspector.ObjectPropertyTreeElement;
     45     this.editable = true;
     46     this.skipProto = false;
     47 
     48     WebInspector.PropertiesSection.call(this, title || "", subtitle);
     49 }
     50 
     51 WebInspector.ObjectPropertiesSection._arrayLoadThreshold = 100;
     52 
     53 WebInspector.ObjectPropertiesSection.prototype = {
     54     enableContextMenu: function()
     55     {
     56         this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), false);
     57     },
     58 
     59     _contextMenuEventFired: function(event)
     60     {
     61         var contextMenu = new WebInspector.ContextMenu(event);
     62         contextMenu.appendApplicableItems(this.object);
     63         contextMenu.show();
     64     },
     65 
     66     onpopulate: function()
     67     {
     68         this.update();
     69     },
     70 
     71     update: function()
     72     {
     73         if (this.object.arrayLength() > WebInspector.ObjectPropertiesSection._arrayLoadThreshold) {
     74             this.propertiesTreeOutline.removeChildren();
     75             WebInspector.ArrayGroupingTreeElement._populateArray(this.propertiesTreeOutline, this.object, 0, this.object.arrayLength() - 1);
     76             return;
     77         }
     78 
     79         /**
     80          * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
     81          * @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties
     82          * @this {WebInspector.ObjectPropertiesSection}
     83          */
     84         function callback(properties, internalProperties)
     85         {
     86             if (!properties)
     87                 return;
     88             this.updateProperties(properties, internalProperties);
     89         }
     90 
     91         WebInspector.RemoteObject.loadFromObject(this.object, !!this.ignoreHasOwnProperty, callback.bind(this));
     92     },
     93 
     94     updateProperties: function(properties, internalProperties, rootTreeElementConstructor, rootPropertyComparer)
     95     {
     96         if (!rootTreeElementConstructor)
     97             rootTreeElementConstructor = this.treeElementConstructor;
     98 
     99         if (!rootPropertyComparer)
    100             rootPropertyComparer = WebInspector.ObjectPropertiesSection.CompareProperties;
    101 
    102         if (this.extraProperties) {
    103             for (var i = 0; i < this.extraProperties.length; ++i)
    104                 properties.push(this.extraProperties[i]);
    105         }
    106 
    107         this.propertiesTreeOutline.removeChildren();
    108 
    109         WebInspector.ObjectPropertyTreeElement.populateWithProperties(this.propertiesTreeOutline,
    110             properties, internalProperties,
    111             rootTreeElementConstructor, rootPropertyComparer,
    112             this.skipProto, this.object, this._emptyPlaceholder);
    113 
    114         this.propertiesForTest = properties;
    115     },
    116 
    117     __proto__: WebInspector.PropertiesSection.prototype
    118 }
    119 
    120 /**
    121  * @param {!WebInspector.RemoteObjectProperty} propertyA
    122  * @param {!WebInspector.RemoteObjectProperty} propertyB
    123  * @return {number}
    124  */
    125 WebInspector.ObjectPropertiesSection.CompareProperties = function(propertyA, propertyB)
    126 {
    127     var a = propertyA.name;
    128     var b = propertyB.name;
    129     if (a === "__proto__")
    130         return 1;
    131     if (b === "__proto__")
    132         return -1;
    133     if (propertyA.symbol && !propertyB.symbol)
    134         return 1;
    135     if (propertyB.symbol && !propertyA.symbol)
    136         return -1;
    137     return String.naturalOrderComparator(a, b);
    138 }
    139 
    140 /**
    141  * @constructor
    142  * @extends {TreeElement}
    143  * @param {!WebInspector.RemoteObjectProperty} property
    144  */
    145 WebInspector.ObjectPropertyTreeElement = function(property)
    146 {
    147     this.property = property;
    148 
    149     // Pass an empty title, the title gets made later in onattach.
    150     TreeElement.call(this, "", null, false);
    151     this.toggleOnClick = true;
    152     this.selectable = false;
    153 }
    154 
    155 WebInspector.ObjectPropertyTreeElement.prototype = {
    156     onpopulate: function()
    157     {
    158         var propertyValue = /** @type {!WebInspector.RemoteObject} */ (this.property.value);
    159         console.assert(propertyValue);
    160         WebInspector.ObjectPropertyTreeElement.populate(this, propertyValue);
    161     },
    162 
    163     /**
    164      * @override
    165      * @return {boolean}
    166      */
    167     ondblclick: function(event)
    168     {
    169         if (this.property.writable || this.property.setter)
    170             this.startEditing(event);
    171         return false;
    172     },
    173 
    174     /**
    175      * @override
    176      */
    177     onattach: function()
    178     {
    179         this.update();
    180     },
    181 
    182     update: function()
    183     {
    184         this.nameElement = document.createElementWithClass("span", "name");
    185         var name = this.property.name;
    186         if (/^\s|\s$|^$|\n/.test(name))
    187             this.nameElement.createTextChildren("\"", name.replace(/\n/g, "\u21B5"), "\"");
    188         else
    189             this.nameElement.textContent = name;
    190         if (!this.property.enumerable)
    191             this.nameElement.classList.add("dimmed");
    192         if (this.property.isAccessorProperty())
    193             this.nameElement.classList.add("properties-accessor-property-name");
    194         if (this.property.symbol)
    195             this.nameElement.addEventListener("contextmenu", this._contextMenuFired.bind(this, this.property.symbol), false);
    196 
    197         var separatorElement = document.createElementWithClass("span", "separator");
    198         separatorElement.textContent = ": ";
    199 
    200         if (this.property.value) {
    201             this.valueElement = document.createElementWithClass("span", "value");
    202             var type = this.property.value.type;
    203             var subtype = this.property.value.subtype;
    204             var description = this.property.value.description;
    205             var prefix;
    206             var valueText;
    207             var suffix;
    208             if (this.property.wasThrown) {
    209                 prefix = "[Exception: ";
    210                 valueText = description;
    211                 suffix = "]";
    212             } else if (type === "string" && typeof description === "string") {
    213                 // Render \n as a nice unicode cr symbol.
    214                 prefix = "\"";
    215                 valueText = description.replace(/\n/g, "\u21B5");
    216                 suffix = "\"";
    217                 this.valueElement._originalTextContent = "\"" + description + "\"";
    218             } else if (type === "function" && typeof description === "string") {
    219                 // Render function description until the first \n.
    220                 valueText = /.*/.exec(description)[0].replace(/\s+$/g, "");
    221                 this.valueElement._originalTextContent = description;
    222             } else if (type !== "object" || subtype !== "node") {
    223                 valueText = description;
    224             }
    225             this.valueElement.setTextContentTruncatedIfNeeded(valueText || "");
    226             if (prefix)
    227                 this.valueElement.insertBefore(document.createTextNode(prefix), this.valueElement.firstChild);
    228             if (suffix)
    229                 this.valueElement.createTextChild(suffix);
    230 
    231             if (this.property.wasThrown)
    232                 this.valueElement.classList.add("error");
    233             if (subtype || type)
    234                 this.valueElement.classList.add("console-formatted-" + (subtype || type));
    235 
    236             this.valueElement.addEventListener("contextmenu", this._contextMenuFired.bind(this, this.property.value), false);
    237             if (type === "object" && subtype === "node" && description) {
    238                 WebInspector.DOMPresentationUtils.createSpansForNodeTitle(this.valueElement, description);
    239                 this.valueElement.addEventListener("mousemove", this._mouseMove.bind(this, this.property.value), false);
    240                 this.valueElement.addEventListener("mouseout", this._mouseOut.bind(this, this.property.value), false);
    241             } else {
    242                 this.valueElement.title = description || "";
    243             }
    244 
    245             this.listItemElement.removeChildren();
    246 
    247             this.hasChildren = this.property.value.hasChildren && !this.property.wasThrown;
    248         } else {
    249             if (this.property.getter) {
    250                 this.valueElement = WebInspector.ObjectPropertyTreeElement.createRemoteObjectAccessorPropertySpan(this.property.parentObject, [this.property.name], this._onInvokeGetterClick.bind(this));
    251             } else {
    252                 this.valueElement = document.createElementWithClass("span", "console-formatted-undefined");
    253                 this.valueElement.textContent = WebInspector.UIString("<unreadable>");
    254                 this.valueElement.title = WebInspector.UIString("No property getter");
    255             }
    256         }
    257 
    258         this.listItemElement.appendChildren(this.nameElement, separatorElement, this.valueElement);
    259     },
    260 
    261     _contextMenuFired: function(value, event)
    262     {
    263         var contextMenu = new WebInspector.ContextMenu(event);
    264         this.populateContextMenu(contextMenu);
    265         contextMenu.appendApplicableItems(value);
    266         contextMenu.show();
    267     },
    268 
    269     /**
    270      * @param {!WebInspector.ContextMenu} contextMenu
    271      */
    272     populateContextMenu: function(contextMenu)
    273     {
    274     },
    275 
    276     _mouseMove: function(event)
    277     {
    278         this.property.value.highlightAsDOMNode();
    279     },
    280 
    281     _mouseOut: function(event)
    282     {
    283         this.property.value.hideDOMNodeHighlight();
    284     },
    285 
    286     updateSiblings: function()
    287     {
    288         if (this.parent.root)
    289             this.treeOutline.section.update();
    290         else
    291             this.parent.shouldRefreshChildren = true;
    292     },
    293 
    294     /**
    295      * @return {boolean}
    296      */
    297     renderPromptAsBlock: function()
    298     {
    299         return false;
    300     },
    301 
    302     /**
    303      * @return {{element: !Element, value: (string|undefined)}}
    304      */
    305     elementAndValueToEdit: function()
    306     {
    307         return {
    308             element: this.valueElement,
    309             value: (typeof this.valueElement._originalTextContent === "string") ? this.valueElement._originalTextContent : undefined
    310         };
    311     },
    312 
    313     /**
    314      * @param {!Event=} event
    315      */
    316     startEditing: function(event)
    317     {
    318         var elementAndValueToEdit = this.elementAndValueToEdit();
    319         var elementToEdit = elementAndValueToEdit.element;
    320         var valueToEdit = elementAndValueToEdit.value;
    321 
    322         if (WebInspector.isBeingEdited(elementToEdit) || !this.treeOutline.section.editable || this._readOnly)
    323             return;
    324 
    325         // Edit original source.
    326         if (typeof valueToEdit !== "undefined")
    327             elementToEdit.setTextContentTruncatedIfNeeded(valueToEdit, WebInspector.UIString("<string is too large to edit>"));
    328 
    329         var context = { expanded: this.expanded, elementToEdit: elementToEdit, previousContent: elementToEdit.textContent };
    330 
    331         // Lie about our children to prevent expanding on double click and to collapse subproperties.
    332         this.hasChildren = false;
    333 
    334         this.listItemElement.classList.add("editing-sub-part");
    335 
    336         this._prompt = new WebInspector.ObjectPropertyPrompt(this.renderPromptAsBlock());
    337 
    338         /**
    339          * @this {WebInspector.ObjectPropertyTreeElement}
    340          */
    341         function blurListener()
    342         {
    343             this.editingCommitted(null, elementToEdit.textContent, context.previousContent, context);
    344         }
    345 
    346         var proxyElement = this._prompt.attachAndStartEditing(elementToEdit, blurListener.bind(this));
    347         window.getSelection().setBaseAndExtent(elementToEdit, 0, elementToEdit, 1);
    348         proxyElement.addEventListener("keydown", this._promptKeyDown.bind(this, context), false);
    349     },
    350 
    351     /**
    352      * @return {boolean}
    353      */
    354     isEditing: function()
    355     {
    356         return !!this._prompt;
    357     },
    358 
    359     editingEnded: function(context)
    360     {
    361         this._prompt.detach();
    362         delete this._prompt;
    363 
    364         this.listItemElement.scrollLeft = 0;
    365         this.listItemElement.classList.remove("editing-sub-part");
    366         if (context.expanded)
    367             this.expand();
    368     },
    369 
    370     editingCancelled: function(element, context)
    371     {
    372         this.editingEnded(context);
    373         this.update();
    374     },
    375 
    376     editingCommitted: function(element, userInput, previousContent, context)
    377     {
    378         if (userInput === previousContent) {
    379             this.editingCancelled(element, context); // nothing changed, so cancel
    380             return;
    381         }
    382 
    383         this.editingEnded(context);
    384         this.applyExpression(userInput);
    385     },
    386 
    387     _promptKeyDown: function(context, event)
    388     {
    389         if (isEnterKey(event)) {
    390             event.consume(true);
    391             this.editingCommitted(null, context.elementToEdit.textContent, context.previousContent, context);
    392             return;
    393         }
    394         if (event.keyIdentifier === "U+001B") { // Esc
    395             event.consume();
    396             this.editingCancelled(null, context);
    397             return;
    398         }
    399     },
    400 
    401     /**
    402      * @param {string} expression
    403      */
    404     applyExpression: function(expression)
    405     {
    406         var property = WebInspector.RemoteObject.toCallArgument(this.property.symbol || this.property.name);
    407         expression = expression.trim();
    408         if (expression)
    409             this.property.parentObject.setPropertyValue(property, expression, callback.bind(this));
    410         else
    411             this.property.parentObject.deleteProperty(property, callback.bind(this));
    412 
    413         /**
    414          * @param {?Protocol.Error} error
    415          * @this {WebInspector.ObjectPropertyTreeElement}
    416          */
    417         function callback(error)
    418         {
    419             if (error) {
    420                 this.update();
    421                 return;
    422             }
    423 
    424             if (!expression) {
    425                 // The property was deleted, so remove this tree element.
    426                 this.parent.removeChild(this);
    427             } else {
    428                 // Call updateSiblings since their value might be based on the value that just changed.
    429                 this.updateSiblings();
    430             }
    431         };
    432     },
    433 
    434     /**
    435      * @return {string|undefined}
    436      */
    437     propertyPath: function()
    438     {
    439         if ("_cachedPropertyPath" in this)
    440             return this._cachedPropertyPath;
    441 
    442         var current = this;
    443         var result;
    444 
    445         do {
    446             if (current.property) {
    447                 if (result)
    448                     result = current.property.name + "." + result;
    449                 else
    450                     result = current.property.name;
    451             }
    452             current = current.parent;
    453         } while (current && !current.root);
    454 
    455         this._cachedPropertyPath = result;
    456         return result;
    457     },
    458 
    459     /**
    460      * @param {?WebInspector.RemoteObject} result
    461      * @param {boolean=} wasThrown
    462      */
    463     _onInvokeGetterClick: function(result, wasThrown)
    464     {
    465         if (!result)
    466             return;
    467         this.property.value = result;
    468         this.property.wasThrown = wasThrown;
    469 
    470         this.update();
    471         this.shouldRefreshChildren = true;
    472     },
    473 
    474     __proto__: TreeElement.prototype
    475 }
    476 
    477 /**
    478  * @param {!TreeElement} treeElement
    479  * @param {!WebInspector.RemoteObject} value
    480  * @param {string=} emptyPlaceholder
    481  */
    482 WebInspector.ObjectPropertyTreeElement.populate = function(treeElement, value, emptyPlaceholder) {
    483     if (treeElement.children.length && !treeElement.shouldRefreshChildren)
    484         return;
    485 
    486     if (value.arrayLength() > WebInspector.ObjectPropertiesSection._arrayLoadThreshold) {
    487         treeElement.removeChildren();
    488         WebInspector.ArrayGroupingTreeElement._populateArray(treeElement, value, 0, value.arrayLength() - 1);
    489         return;
    490     }
    491 
    492     /**
    493      * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
    494      * @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties
    495      */
    496     function callback(properties, internalProperties)
    497     {
    498         treeElement.removeChildren();
    499         if (!properties)
    500             return;
    501         WebInspector.ObjectPropertyTreeElement.populateWithProperties(treeElement, properties, internalProperties,
    502             treeElement.treeOutline.section.treeElementConstructor, WebInspector.ObjectPropertiesSection.CompareProperties,
    503             treeElement.treeOutline.section.skipProto, value, emptyPlaceholder);
    504     }
    505 
    506     WebInspector.RemoteObject.loadFromObjectPerProto(value, callback);
    507 }
    508 
    509 /**
    510  * @param {!TreeElement|!TreeOutline} treeElement
    511  * @param {!Array.<!WebInspector.RemoteObjectProperty>} properties
    512  * @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties
    513  * @param {function(new:TreeElement, !WebInspector.RemoteObjectProperty)} treeElementConstructor
    514  * @param {function (!WebInspector.RemoteObjectProperty, !WebInspector.RemoteObjectProperty): number} comparator
    515  * @param {boolean} skipProto
    516  * @param {?WebInspector.RemoteObject} value
    517  * @param {?string=} emptyPlaceholder
    518  */
    519 WebInspector.ObjectPropertyTreeElement.populateWithProperties = function(treeElement, properties, internalProperties, treeElementConstructor, comparator, skipProto, value, emptyPlaceholder) {
    520     properties.sort(comparator);
    521 
    522     for (var i = 0; i < properties.length; ++i) {
    523         var property = properties[i];
    524         if (skipProto && property.name === "__proto__")
    525             continue;
    526         if (property.isAccessorProperty()) {
    527             if (property.name !== "__proto__" && property.getter) {
    528                 property.parentObject = value;
    529                 treeElement.appendChild(new treeElementConstructor(property));
    530             }
    531             if (property.isOwn) {
    532                 if (property.getter) {
    533                     var getterProperty = new WebInspector.RemoteObjectProperty("get " + property.name, property.getter);
    534                     getterProperty.parentObject = value;
    535                     treeElement.appendChild(new treeElementConstructor(getterProperty));
    536                 }
    537                 if (property.setter) {
    538                     var setterProperty = new WebInspector.RemoteObjectProperty("set " + property.name, property.setter);
    539                     setterProperty.parentObject = value;
    540                     treeElement.appendChild(new treeElementConstructor(setterProperty));
    541                 }
    542             }
    543         } else {
    544             property.parentObject = value;
    545             treeElement.appendChild(new treeElementConstructor(property));
    546         }
    547     }
    548     if (value && value.type === "function") {
    549         // Whether function has TargetFunction internal property.
    550         // This is a simple way to tell that the function is actually a bound function (we are not told).
    551         // Bound function never has inner scope and doesn't need corresponding UI node.
    552         var hasTargetFunction = false;
    553 
    554         if (internalProperties) {
    555             for (var i = 0; i < internalProperties.length; i++) {
    556                 if (internalProperties[i].name == "[[TargetFunction]]") {
    557                     hasTargetFunction = true;
    558                     break;
    559                 }
    560             }
    561         }
    562         if (!hasTargetFunction)
    563             treeElement.appendChild(new WebInspector.FunctionScopeMainTreeElement(value));
    564     }
    565     if (value && value.type === "object" && (value.subtype === "map" || value.subtype === "set"))
    566         treeElement.appendChild(new WebInspector.CollectionEntriesMainTreeElement(value));
    567     if (internalProperties) {
    568         for (var i = 0; i < internalProperties.length; i++) {
    569             internalProperties[i].parentObject = value;
    570             treeElement.appendChild(new treeElementConstructor(internalProperties[i]));
    571         }
    572     }
    573 
    574     WebInspector.ObjectPropertyTreeElement._appendEmptyPlaceholderIfNeeded(treeElement, emptyPlaceholder);
    575 }
    576 
    577 /**
    578  * @param {!TreeElement|!TreeOutline} treeElement
    579  * @param {?string=} emptyPlaceholder
    580  */
    581 WebInspector.ObjectPropertyTreeElement._appendEmptyPlaceholderIfNeeded = function(treeElement, emptyPlaceholder)
    582 {
    583     if (treeElement.children.length)
    584         return;
    585     var title = document.createElementWithClass("div", "info");
    586     title.textContent = emptyPlaceholder || WebInspector.UIString("No Properties");
    587     var infoElement = new TreeElement(title, null, false);
    588     treeElement.appendChild(infoElement);
    589 }
    590 
    591 /**
    592  * @param {?WebInspector.RemoteObject} object
    593  * @param {!Array.<string>} propertyPath
    594  * @param {function(?WebInspector.RemoteObject, boolean=)} callback
    595  * @return {!Element}
    596  */
    597 WebInspector.ObjectPropertyTreeElement.createRemoteObjectAccessorPropertySpan = function(object, propertyPath, callback)
    598 {
    599     var rootElement = document.createElement("span");
    600     var element = rootElement.createChild("span");
    601     element.textContent = WebInspector.UIString("(...)");
    602     if (!object)
    603         return rootElement;
    604     element.classList.add("properties-calculate-value-button");
    605     element.title = WebInspector.UIString("Invoke property getter");
    606     element.addEventListener("click", onInvokeGetterClick, false);
    607 
    608     function onInvokeGetterClick(event)
    609     {
    610         event.consume();
    611         object.getProperty(propertyPath, callback);
    612     }
    613 
    614     return rootElement;
    615 }
    616 
    617 /**
    618  * @constructor
    619  * @extends {TreeElement}
    620  * @param {!WebInspector.RemoteObject} remoteObject
    621  */
    622 WebInspector.FunctionScopeMainTreeElement = function(remoteObject)
    623 {
    624     TreeElement.call(this, "<function scope>", null, false);
    625     this.toggleOnClick = true;
    626     this.selectable = false;
    627     this._remoteObject = remoteObject;
    628     this.hasChildren = true;
    629 }
    630 
    631 WebInspector.FunctionScopeMainTreeElement.prototype = {
    632     onpopulate: function()
    633     {
    634         if (this.children.length && !this.shouldRefreshChildren)
    635             return;
    636 
    637         /**
    638          * @param {?WebInspector.DebuggerModel.FunctionDetails} response
    639          * @this {WebInspector.FunctionScopeMainTreeElement}
    640          */
    641         function didGetDetails(response)
    642         {
    643             if (!response)
    644                 return;
    645             this.removeChildren();
    646 
    647             var scopeChain = response.scopeChain || [];
    648             for (var i = 0; i < scopeChain.length; ++i) {
    649                 var scope = scopeChain[i];
    650                 var title = null;
    651                 var isTrueObject;
    652 
    653                 switch (scope.type) {
    654                 case DebuggerAgent.ScopeType.Local:
    655                     // Not really expecting this scope type here.
    656                     title = WebInspector.UIString("Local");
    657                     isTrueObject = false;
    658                     break;
    659                 case DebuggerAgent.ScopeType.Closure:
    660                     title = WebInspector.UIString("Closure");
    661                     isTrueObject = false;
    662                     break;
    663                 case DebuggerAgent.ScopeType.Catch:
    664                     title = WebInspector.UIString("Catch");
    665                     isTrueObject = false;
    666                     break;
    667                 case DebuggerAgent.ScopeType.With:
    668                     title = WebInspector.UIString("With Block");
    669                     isTrueObject = true;
    670                     break;
    671                 case DebuggerAgent.ScopeType.Global:
    672                     title = WebInspector.UIString("Global");
    673                     isTrueObject = true;
    674                     break;
    675                 default:
    676                     console.error("Unknown scope type: " + scope.type);
    677                     continue;
    678                 }
    679 
    680                 var runtimeModel = this._remoteObject.target().runtimeModel;
    681                 if (isTrueObject) {
    682                     var remoteObject = runtimeModel.createRemoteObject(scope.object);
    683                     var property = new WebInspector.RemoteObjectProperty(title, remoteObject);
    684                     property.writable = false;
    685                     property.parentObject = null;
    686                     this.appendChild(new this.treeOutline.section.treeElementConstructor(property));
    687                 } else {
    688                     var scopeRef = new WebInspector.ScopeRef(i, undefined, this._remoteObject.objectId);
    689                     var remoteObject = runtimeModel.createScopeRemoteObject(scope.object, scopeRef);
    690                     var scopeTreeElement = new WebInspector.ScopeTreeElement(title, remoteObject);
    691                     this.appendChild(scopeTreeElement);
    692                 }
    693             }
    694 
    695             WebInspector.ObjectPropertyTreeElement._appendEmptyPlaceholderIfNeeded(this, WebInspector.UIString("No Scopes"));
    696         }
    697 
    698         this._remoteObject.functionDetails(didGetDetails.bind(this));
    699     },
    700 
    701     __proto__: TreeElement.prototype
    702 }
    703 
    704 /**
    705  * @constructor
    706  * @extends {TreeElement}
    707  * @param {!WebInspector.RemoteObject} remoteObject
    708  */
    709 WebInspector.CollectionEntriesMainTreeElement = function(remoteObject)
    710 {
    711     TreeElement.call(this, "<entries>", null, false);
    712     this.toggleOnClick = true;
    713     this.selectable = false;
    714     this._remoteObject = remoteObject;
    715     this.hasChildren = true;
    716 }
    717 
    718 WebInspector.CollectionEntriesMainTreeElement.prototype = {
    719     onpopulate: function()
    720     {
    721         if (this.children.length && !this.shouldRefreshChildren)
    722             return;
    723 
    724         /**
    725          * @param {?Array.<!DebuggerAgent.CollectionEntry>} entries
    726          * @this {WebInspector.CollectionEntriesMainTreeElement}
    727          */
    728         function didGetCollectionEntries(entries)
    729         {
    730             if (!entries)
    731                 return;
    732             this.removeChildren();
    733 
    734             var entriesLocalObject = [];
    735             var runtimeModel = this._remoteObject.target().runtimeModel;
    736             for (var i = 0; i < entries.length; ++i) {
    737                 var entry = entries[i];
    738                 if (entry.key) {
    739                     entriesLocalObject.push(new WebInspector.MapEntryLocalJSONObject({
    740                         key: runtimeModel.createRemoteObject(entry.key),
    741                         value: runtimeModel.createRemoteObject(entry.value)
    742                     }));
    743                 } else {
    744                     entriesLocalObject.push(runtimeModel.createRemoteObject(entry.value));
    745                 }
    746             }
    747             WebInspector.ObjectPropertyTreeElement.populate(this, WebInspector.RemoteObject.fromLocalObject(entriesLocalObject), WebInspector.UIString("No Entries"));
    748             this.title = "<entries>[" + entriesLocalObject.length + "]";
    749         }
    750 
    751         this._remoteObject.collectionEntries(didGetCollectionEntries.bind(this));
    752     },
    753 
    754     __proto__: TreeElement.prototype
    755 }
    756 
    757 /**
    758  * @constructor
    759  * @extends {TreeElement}
    760  * @param {string} title
    761  * @param {!WebInspector.RemoteObject} remoteObject
    762  */
    763 WebInspector.ScopeTreeElement = function(title, remoteObject)
    764 {
    765     TreeElement.call(this, title, null, false);
    766     this.toggleOnClick = true;
    767     this.selectable = false;
    768     this._remoteObject = remoteObject;
    769     this.hasChildren = true;
    770 }
    771 
    772 WebInspector.ScopeTreeElement.prototype = {
    773     onpopulate: function()
    774     {
    775         WebInspector.ObjectPropertyTreeElement.populate(this, this._remoteObject);
    776     },
    777 
    778     __proto__: TreeElement.prototype
    779 }
    780 
    781 /**
    782  * @constructor
    783  * @extends {TreeElement}
    784  * @param {!WebInspector.RemoteObject} object
    785  * @param {number} fromIndex
    786  * @param {number} toIndex
    787  * @param {number} propertyCount
    788  */
    789 WebInspector.ArrayGroupingTreeElement = function(object, fromIndex, toIndex, propertyCount)
    790 {
    791     TreeElement.call(this, String.sprintf("[%d \u2026 %d]", fromIndex, toIndex), undefined, true);
    792     this.toggleOnClick = true;
    793     this.selectable = false;
    794     this._fromIndex = fromIndex;
    795     this._toIndex = toIndex;
    796     this._object = object;
    797     this._readOnly = true;
    798     this._propertyCount = propertyCount;
    799     this._populated = false;
    800 }
    801 
    802 WebInspector.ArrayGroupingTreeElement._bucketThreshold = 100;
    803 WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold = 250000;
    804 
    805 /**
    806  * @param {!TreeElement|!TreeOutline} treeElement
    807  * @param {!WebInspector.RemoteObject} object
    808  * @param {number} fromIndex
    809  * @param {number} toIndex
    810  */
    811 WebInspector.ArrayGroupingTreeElement._populateArray = function(treeElement, object, fromIndex, toIndex)
    812 {
    813     WebInspector.ArrayGroupingTreeElement._populateRanges(treeElement, object, fromIndex, toIndex, true);
    814 }
    815 
    816 /**
    817  * @param {!TreeElement|!TreeOutline} treeElement
    818  * @param {!WebInspector.RemoteObject} object
    819  * @param {number} fromIndex
    820  * @param {number} toIndex
    821  * @param {boolean} topLevel
    822  * @this {WebInspector.ArrayGroupingTreeElement}
    823  */
    824 WebInspector.ArrayGroupingTreeElement._populateRanges = function(treeElement, object, fromIndex, toIndex, topLevel)
    825 {
    826     object.callFunctionJSON(packRanges, [{value: fromIndex}, {value: toIndex}, {value: WebInspector.ArrayGroupingTreeElement._bucketThreshold}, {value: WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold}], callback);
    827 
    828     /**
    829      * @suppressReceiverCheck
    830      * @this {Object}
    831      * @param {number=} fromIndex // must declare optional
    832      * @param {number=} toIndex // must declare optional
    833      * @param {number=} bucketThreshold // must declare optional
    834      * @param {number=} sparseIterationThreshold // must declare optional
    835      */
    836     function packRanges(fromIndex, toIndex, bucketThreshold, sparseIterationThreshold)
    837     {
    838         var ownPropertyNames = null;
    839 
    840         /**
    841          * @this {Object}
    842          */
    843         function doLoop(iterationCallback)
    844         {
    845             if (toIndex - fromIndex < sparseIterationThreshold) {
    846                 for (var i = fromIndex; i <= toIndex; ++i) {
    847                     if (i in this)
    848                         iterationCallback(i);
    849                 }
    850             } else {
    851                 ownPropertyNames = ownPropertyNames || Object.getOwnPropertyNames(this);
    852                 for (var i = 0; i < ownPropertyNames.length; ++i) {
    853                     var name = ownPropertyNames[i];
    854                     var index = name >>> 0;
    855                     if (String(index) === name && fromIndex <= index && index <= toIndex)
    856                         iterationCallback(index);
    857                 }
    858             }
    859         }
    860 
    861         var count = 0;
    862         function countIterationCallback()
    863         {
    864             ++count;
    865         }
    866         doLoop.call(this, countIterationCallback);
    867 
    868         var bucketSize = count;
    869         if (count <= bucketThreshold)
    870             bucketSize = count;
    871         else
    872             bucketSize = Math.pow(bucketThreshold, Math.ceil(Math.log(count) / Math.log(bucketThreshold)) - 1);
    873 
    874         var ranges = [];
    875         count = 0;
    876         var groupStart = -1;
    877         var groupEnd = 0;
    878         function loopIterationCallback(i)
    879         {
    880             if (groupStart === -1)
    881                 groupStart = i;
    882 
    883             groupEnd = i;
    884             if (++count === bucketSize) {
    885                 ranges.push([groupStart, groupEnd, count]);
    886                 count = 0;
    887                 groupStart = -1;
    888             }
    889         }
    890         doLoop.call(this, loopIterationCallback);
    891 
    892         if (count > 0)
    893             ranges.push([groupStart, groupEnd, count]);
    894         return ranges;
    895     }
    896 
    897     function callback(ranges)
    898     {
    899         if (ranges.length == 1)
    900             WebInspector.ArrayGroupingTreeElement._populateAsFragment(treeElement, object, ranges[0][0], ranges[0][1]);
    901         else {
    902             for (var i = 0; i < ranges.length; ++i) {
    903                 var fromIndex = ranges[i][0];
    904                 var toIndex = ranges[i][1];
    905                 var count = ranges[i][2];
    906                 if (fromIndex == toIndex)
    907                     WebInspector.ArrayGroupingTreeElement._populateAsFragment(treeElement, object, fromIndex, toIndex);
    908                 else
    909                     treeElement.appendChild(new WebInspector.ArrayGroupingTreeElement(object, fromIndex, toIndex, count));
    910             }
    911         }
    912         if (topLevel)
    913             WebInspector.ArrayGroupingTreeElement._populateNonIndexProperties(treeElement, object);
    914     }
    915 }
    916 
    917 /**
    918  * @param {!TreeElement|!TreeOutline} treeElement
    919  * @param {!WebInspector.RemoteObject} object
    920  * @param {number} fromIndex
    921  * @param {number} toIndex
    922  * @this {WebInspector.ArrayGroupingTreeElement}
    923  */
    924 WebInspector.ArrayGroupingTreeElement._populateAsFragment = function(treeElement, object, fromIndex, toIndex)
    925 {
    926     object.callFunction(buildArrayFragment, [{value: fromIndex}, {value: toIndex}, {value: WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold}], processArrayFragment.bind(this));
    927 
    928     /**
    929      * @suppressReceiverCheck
    930      * @this {Object}
    931      * @param {number=} fromIndex // must declare optional
    932      * @param {number=} toIndex // must declare optional
    933      * @param {number=} sparseIterationThreshold // must declare optional
    934      */
    935     function buildArrayFragment(fromIndex, toIndex, sparseIterationThreshold)
    936     {
    937         var result = Object.create(null);
    938         if (toIndex - fromIndex < sparseIterationThreshold) {
    939             for (var i = fromIndex; i <= toIndex; ++i) {
    940                 if (i in this)
    941                     result[i] = this[i];
    942             }
    943         } else {
    944             var ownPropertyNames = Object.getOwnPropertyNames(this);
    945             for (var i = 0; i < ownPropertyNames.length; ++i) {
    946                 var name = ownPropertyNames[i];
    947                 var index = name >>> 0;
    948                 if (String(index) === name && fromIndex <= index && index <= toIndex)
    949                     result[index] = this[index];
    950             }
    951         }
    952         return result;
    953     }
    954 
    955     /**
    956      * @param {?WebInspector.RemoteObject} arrayFragment
    957      * @param {boolean=} wasThrown
    958      * @this {WebInspector.ArrayGroupingTreeElement}
    959      */
    960     function processArrayFragment(arrayFragment, wasThrown)
    961     {
    962         if (!arrayFragment || wasThrown)
    963             return;
    964         arrayFragment.getAllProperties(false, processProperties.bind(this));
    965     }
    966 
    967     /** @this {WebInspector.ArrayGroupingTreeElement} */
    968     function processProperties(properties, internalProperties)
    969     {
    970         if (!properties)
    971             return;
    972 
    973         properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
    974         for (var i = 0; i < properties.length; ++i) {
    975             properties[i].parentObject = this._object;
    976             var childTreeElement = new treeElement.treeOutline.section.treeElementConstructor(properties[i]);
    977             childTreeElement._readOnly = true;
    978             treeElement.appendChild(childTreeElement);
    979         }
    980     }
    981 }
    982 
    983 /**
    984  * @param {!TreeElement|!TreeOutline} treeElement
    985  * @param {!WebInspector.RemoteObject} object
    986  * @this {WebInspector.ArrayGroupingTreeElement}
    987  */
    988 WebInspector.ArrayGroupingTreeElement._populateNonIndexProperties = function(treeElement, object)
    989 {
    990     object.callFunction(buildObjectFragment, undefined, processObjectFragment.bind(this));
    991 
    992     /**
    993      * @suppressReceiverCheck
    994      * @this {Object}
    995      */
    996     function buildObjectFragment()
    997     {
    998         var result = Object.create(this.__proto__);
    999         var names = Object.getOwnPropertyNames(this);
   1000         for (var i = 0; i < names.length; ++i) {
   1001             var name = names[i];
   1002             // Array index check according to the ES5-15.4.
   1003             if (String(name >>> 0) === name && name >>> 0 !== 0xffffffff)
   1004                 continue;
   1005             var descriptor = Object.getOwnPropertyDescriptor(this, name);
   1006             if (descriptor)
   1007                 Object.defineProperty(result, name, descriptor);
   1008         }
   1009         return result;
   1010     }
   1011 
   1012     /**
   1013      * @param {?WebInspector.RemoteObject} arrayFragment
   1014      * @param {boolean=} wasThrown
   1015      * @this {WebInspector.ArrayGroupingTreeElement}
   1016      */
   1017     function processObjectFragment(arrayFragment, wasThrown)
   1018     {
   1019         if (!arrayFragment || wasThrown)
   1020             return;
   1021         arrayFragment.getOwnProperties(processProperties.bind(this));
   1022     }
   1023 
   1024     /**
   1025      * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
   1026      * @param {?Array.<!WebInspector.RemoteObjectProperty>=} internalProperties
   1027      * @this {WebInspector.ArrayGroupingTreeElement}
   1028      */
   1029     function processProperties(properties, internalProperties)
   1030     {
   1031         if (!properties)
   1032             return;
   1033         properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
   1034         for (var i = 0; i < properties.length; ++i) {
   1035             properties[i].parentObject = this._object;
   1036             var childTreeElement = new treeElement.treeOutline.section.treeElementConstructor(properties[i]);
   1037             childTreeElement._readOnly = true;
   1038             treeElement.appendChild(childTreeElement);
   1039         }
   1040     }
   1041 }
   1042 
   1043 WebInspector.ArrayGroupingTreeElement.prototype = {
   1044     onpopulate: function()
   1045     {
   1046         if (this._populated)
   1047             return;
   1048 
   1049         this._populated = true;
   1050 
   1051         if (this._propertyCount >= WebInspector.ArrayGroupingTreeElement._bucketThreshold) {
   1052             WebInspector.ArrayGroupingTreeElement._populateRanges(this, this._object, this._fromIndex, this._toIndex, false);
   1053             return;
   1054         }
   1055         WebInspector.ArrayGroupingTreeElement._populateAsFragment(this, this._object, this._fromIndex, this._toIndex);
   1056     },
   1057 
   1058     onattach: function()
   1059     {
   1060         this.listItemElement.classList.add("name");
   1061     },
   1062 
   1063     __proto__: TreeElement.prototype
   1064 }
   1065 
   1066 /**
   1067  * @constructor
   1068  * @extends {WebInspector.TextPrompt}
   1069  * @param {boolean=} renderAsBlock
   1070  */
   1071 WebInspector.ObjectPropertyPrompt = function(renderAsBlock)
   1072 {
   1073     WebInspector.TextPrompt.call(this, WebInspector.ExecutionContextSelector.completionsForTextPromptInCurrentContext);
   1074     this.setSuggestBoxEnabled(true);
   1075     if (renderAsBlock)
   1076         this.renderAsBlock();
   1077 }
   1078 
   1079 WebInspector.ObjectPropertyPrompt.prototype = {
   1080     __proto__: WebInspector.TextPrompt.prototype
   1081 }
   1082