Home | History | Annotate | Download | only in front_end
      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 || WebInspector.UIString("No Properties"));
     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);
    113 
    114         this.propertiesForTest = properties;
    115 
    116         if (!this.propertiesTreeOutline.children.length) {
    117             var title = document.createElement("div");
    118             title.className = "info";
    119             title.textContent = this.emptyPlaceholder;
    120             var infoElement = new TreeElement(title, null, false);
    121             this.propertiesTreeOutline.appendChild(infoElement);
    122         }
    123     },
    124 
    125     __proto__: WebInspector.PropertiesSection.prototype
    126 }
    127 
    128 /**
    129  * @param {!WebInspector.RemoteObjectProperty} propertyA
    130  * @param {!WebInspector.RemoteObjectProperty} propertyB
    131  * @return {number}
    132  */
    133 WebInspector.ObjectPropertiesSection.CompareProperties = function(propertyA, propertyB)
    134 {
    135     var a = propertyA.name;
    136     var b = propertyB.name;
    137     if (a === "__proto__")
    138         return 1;
    139     if (b === "__proto__")
    140         return -1;
    141     return String.naturalOrderComparator(a, b);
    142 }
    143 
    144 /**
    145  * @constructor
    146  * @extends {TreeElement}
    147  * @param {!WebInspector.RemoteObjectProperty} property
    148  */
    149 WebInspector.ObjectPropertyTreeElement = function(property)
    150 {
    151     this.property = property;
    152 
    153     // Pass an empty title, the title gets made later in onattach.
    154     TreeElement.call(this, "", null, false);
    155     this.toggleOnClick = true;
    156     this.selectable = false;
    157 }
    158 
    159 WebInspector.ObjectPropertyTreeElement.prototype = {
    160     onpopulate: function()
    161     {
    162         var propertyValue = /** @type {!WebInspector.RemoteObject} */ (this.property.value);
    163         console.assert(propertyValue);
    164         return WebInspector.ObjectPropertyTreeElement.populate(this, propertyValue);
    165     },
    166 
    167     /**
    168      * @override
    169      */
    170     ondblclick: function(event)
    171     {
    172         if (this.property.writable || this.property.setter)
    173             this.startEditing(event);
    174         return false;
    175     },
    176 
    177     /**
    178      * @override
    179      */
    180     onattach: function()
    181     {
    182         this.update();
    183     },
    184 
    185     update: function()
    186     {
    187         this.nameElement = document.createElement("span");
    188         this.nameElement.className = "name";
    189         var name = this.property.name;
    190         if (/^\s|\s$|^$|\n/.test(name))
    191             name = "\"" + name.replace(/\n/g, "\u21B5") + "\"";
    192         this.nameElement.textContent = name;
    193         if (!this.property.enumerable)
    194             this.nameElement.classList.add("dimmed");
    195         if (this.property.isAccessorProperty())
    196             this.nameElement.classList.add("properties-accessor-property-name");
    197 
    198         var separatorElement = document.createElement("span");
    199         separatorElement.className = "separator";
    200         separatorElement.textContent = ": ";
    201 
    202         if (this.property.value) {
    203             this.valueElement = document.createElement("span");
    204             this.valueElement.className = "value";
    205             var description = this.property.value.description;
    206             // Render \n as a nice unicode cr symbol.
    207             if (this.property.wasThrown) {
    208                 this.valueElement.textContent = "[Exception: " + description + "]";
    209             } else if (this.property.value.type === "string" && typeof description === "string") {
    210                 this.valueElement.textContent = "\"" + description.replace(/\n/g, "\u21B5") + "\"";
    211                 this.valueElement._originalTextContent = "\"" + description + "\"";
    212             } else if (this.property.value.type === "function" && typeof description === "string") {
    213                 this.valueElement.textContent = /.*/.exec(description)[0].replace(/ +$/g, "");
    214                 this.valueElement._originalTextContent = description;
    215             } else if (this.property.value.type !== "object" || this.property.value.subtype !== "node") {
    216                 this.valueElement.textContent = description;
    217             }
    218 
    219             if (this.property.wasThrown)
    220                 this.valueElement.classList.add("error");
    221             if (this.property.value.subtype)
    222                 this.valueElement.classList.add("console-formatted-" + this.property.value.subtype);
    223             else if (this.property.value.type)
    224                 this.valueElement.classList.add("console-formatted-" + this.property.value.type);
    225 
    226             this.valueElement.addEventListener("contextmenu", this._contextMenuFired.bind(this, this.property.value), false);
    227             if (this.property.value.type === "object" && this.property.value.subtype === "node" && this.property.value.description) {
    228                 WebInspector.DOMPresentationUtils.createSpansForNodeTitle(this.valueElement, this.property.value.description);
    229                 this.valueElement.addEventListener("mousemove", this._mouseMove.bind(this, this.property.value), false);
    230                 this.valueElement.addEventListener("mouseout", this._mouseOut.bind(this, this.property.value), false);
    231             } else {
    232                 this.valueElement.title = description || "";
    233             }
    234 
    235             this.listItemElement.removeChildren();
    236 
    237             this.hasChildren = this.property.value.hasChildren && !this.property.wasThrown;
    238         } else {
    239             if (this.property.getter) {
    240                 this.valueElement = WebInspector.ObjectPropertyTreeElement.createRemoteObjectAccessorPropertySpan(this.property.parentObject, [this.property.name], this._onInvokeGetterClick.bind(this));
    241             } else {
    242                 this.valueElement = document.createElement("span");
    243                 this.valueElement.className = "console-formatted-undefined";
    244                 this.valueElement.textContent = WebInspector.UIString("<unreadable>");
    245                 this.valueElement.title = WebInspector.UIString("No property getter");
    246             }
    247         }
    248 
    249         this.listItemElement.appendChild(this.nameElement);
    250         this.listItemElement.appendChild(separatorElement);
    251         this.listItemElement.appendChild(this.valueElement);
    252     },
    253 
    254     _contextMenuFired: function(value, event)
    255     {
    256         var contextMenu = new WebInspector.ContextMenu(event);
    257         this.populateContextMenu(contextMenu);
    258         contextMenu.appendApplicableItems(value);
    259         contextMenu.show();
    260     },
    261 
    262     /**
    263      * @param {!WebInspector.ContextMenu} contextMenu
    264      */
    265     populateContextMenu: function(contextMenu)
    266     {
    267     },
    268 
    269     _mouseMove: function(event)
    270     {
    271         this.property.value.highlightAsDOMNode();
    272     },
    273 
    274     _mouseOut: function(event)
    275     {
    276         this.property.value.hideDOMNodeHighlight();
    277     },
    278 
    279     updateSiblings: function()
    280     {
    281         if (this.parent.root)
    282             this.treeOutline.section.update();
    283         else
    284             this.parent.shouldRefreshChildren = true;
    285     },
    286 
    287     renderPromptAsBlock: function()
    288     {
    289         return false;
    290     },
    291 
    292     /**
    293      * @param {!Event=} event
    294      */
    295     elementAndValueToEdit: function(event)
    296     {
    297         return [this.valueElement, (typeof this.valueElement._originalTextContent === "string") ? this.valueElement._originalTextContent : undefined];
    298     },
    299 
    300     startEditing: function(event)
    301     {
    302         var elementAndValueToEdit = this.elementAndValueToEdit(event);
    303         var elementToEdit = elementAndValueToEdit[0];
    304         var valueToEdit = elementAndValueToEdit[1];
    305 
    306         if (WebInspector.isBeingEdited(elementToEdit) || !this.treeOutline.section.editable || this._readOnly)
    307             return;
    308 
    309         // Edit original source.
    310         if (typeof valueToEdit !== "undefined")
    311             elementToEdit.textContent = valueToEdit;
    312 
    313         var context = { expanded: this.expanded, elementToEdit: elementToEdit, previousContent: elementToEdit.textContent };
    314 
    315         // Lie about our children to prevent expanding on double click and to collapse subproperties.
    316         this.hasChildren = false;
    317 
    318         this.listItemElement.classList.add("editing-sub-part");
    319 
    320         this._prompt = new WebInspector.ObjectPropertyPrompt(this.editingCommitted.bind(this, null, elementToEdit.textContent, context.previousContent, context), this.editingCancelled.bind(this, null, context), this.renderPromptAsBlock());
    321 
    322         /**
    323          * @this {WebInspector.ObjectPropertyTreeElement}
    324          */
    325         function blurListener()
    326         {
    327             this.editingCommitted(null, elementToEdit.textContent, context.previousContent, context);
    328         }
    329 
    330         var proxyElement = this._prompt.attachAndStartEditing(elementToEdit, blurListener.bind(this));
    331         window.getSelection().setBaseAndExtent(elementToEdit, 0, elementToEdit, 1);
    332         proxyElement.addEventListener("keydown", this._promptKeyDown.bind(this, context), false);
    333     },
    334 
    335     /**
    336      * @return {boolean}
    337      */
    338     isEditing: function()
    339     {
    340         return !!this._prompt;
    341     },
    342 
    343     editingEnded: function(context)
    344     {
    345         this._prompt.detach();
    346         delete this._prompt;
    347 
    348         this.listItemElement.scrollLeft = 0;
    349         this.listItemElement.classList.remove("editing-sub-part");
    350         if (context.expanded)
    351             this.expand();
    352     },
    353 
    354     editingCancelled: function(element, context)
    355     {
    356         this.editingEnded(context);
    357         this.update();
    358     },
    359 
    360     editingCommitted: function(element, userInput, previousContent, context)
    361     {
    362         if (userInput === previousContent)
    363             return this.editingCancelled(element, context); // nothing changed, so cancel
    364 
    365         this.editingEnded(context);
    366         this.applyExpression(userInput, true);
    367     },
    368 
    369     _promptKeyDown: function(context, event)
    370     {
    371         if (isEnterKey(event)) {
    372             event.consume(true);
    373             return this.editingCommitted(null, context.elementToEdit.textContent, context.previousContent, context);
    374         }
    375         if (event.keyIdentifier === "U+001B") { // Esc
    376             event.consume();
    377             return this.editingCancelled(null, context);
    378         }
    379     },
    380 
    381     applyExpression: function(expression, updateInterface)
    382     {
    383         expression = expression.trim();
    384         var expressionLength = expression.length;
    385 
    386         /**
    387          * @param {?Protocol.Error} error
    388          * @this {WebInspector.ObjectPropertyTreeElement}
    389          */
    390         function callback(error)
    391         {
    392             if (!updateInterface)
    393                 return;
    394 
    395             if (error)
    396                 this.update();
    397 
    398             if (!expressionLength) {
    399                 // The property was deleted, so remove this tree element.
    400                 this.parent.removeChild(this);
    401             } else {
    402                 // Call updateSiblings since their value might be based on the value that just changed.
    403                 this.updateSiblings();
    404             }
    405         };
    406         this.property.parentObject.setPropertyValue(this.property.name, expression.trim(), callback.bind(this));
    407     },
    408 
    409     propertyPath: function()
    410     {
    411         if ("_cachedPropertyPath" in this)
    412             return this._cachedPropertyPath;
    413 
    414         var current = this;
    415         var result;
    416 
    417         do {
    418             if (current.property) {
    419                 if (result)
    420                     result = current.property.name + "." + result;
    421                 else
    422                     result = current.property.name;
    423             }
    424             current = current.parent;
    425         } while (current && !current.root);
    426 
    427         this._cachedPropertyPath = result;
    428         return result;
    429     },
    430 
    431     /**
    432      * @param {?WebInspector.RemoteObject} result
    433      * @param {boolean=} wasThrown
    434      */
    435     _onInvokeGetterClick: function(result, wasThrown)
    436     {
    437         if (!result)
    438             return;
    439         this.property.value = result;
    440         this.property.wasThrown = wasThrown;
    441 
    442         this.update();
    443         this.shouldRefreshChildren = true;
    444     },
    445 
    446     __proto__: TreeElement.prototype
    447 }
    448 
    449 /**
    450  * @param {!TreeElement} treeElement
    451  * @param {!WebInspector.RemoteObject} value
    452  */
    453 WebInspector.ObjectPropertyTreeElement.populate = function(treeElement, value) {
    454     if (treeElement.children.length && !treeElement.shouldRefreshChildren)
    455         return;
    456 
    457     if (value.arrayLength() > WebInspector.ObjectPropertiesSection._arrayLoadThreshold) {
    458         treeElement.removeChildren();
    459         WebInspector.ArrayGroupingTreeElement._populateArray(treeElement, value, 0, value.arrayLength() - 1);
    460         return;
    461     }
    462 
    463     /**
    464      * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
    465      * @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties
    466      */
    467     function callback(properties, internalProperties)
    468     {
    469         treeElement.removeChildren();
    470         if (!properties)
    471             return;
    472         if (!internalProperties)
    473             internalProperties = [];
    474 
    475         WebInspector.ObjectPropertyTreeElement.populateWithProperties(treeElement, properties, internalProperties,
    476             treeElement.treeOutline.section.treeElementConstructor, WebInspector.ObjectPropertiesSection.CompareProperties,
    477             treeElement.treeOutline.section.skipProto, value);
    478     }
    479 
    480     WebInspector.RemoteObject.loadFromObjectPerProto(value, callback);
    481 }
    482 
    483 /**
    484  * @param {!TreeElement|!TreeOutline} treeElement
    485  * @param {!Array.<!WebInspector.RemoteObjectProperty>} properties
    486  * @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties
    487  * @param {function(new:TreeElement, !WebInspector.RemoteObjectProperty)} treeElementConstructor
    488  * @param {function (!WebInspector.RemoteObjectProperty, !WebInspector.RemoteObjectProperty): number} comparator
    489  * @param {boolean} skipProto
    490  * @param {?WebInspector.RemoteObject} value
    491  */
    492 WebInspector.ObjectPropertyTreeElement.populateWithProperties = function(treeElement, properties, internalProperties, treeElementConstructor, comparator, skipProto, value) {
    493     properties.sort(comparator);
    494 
    495     for (var i = 0; i < properties.length; ++i) {
    496         var property = properties[i];
    497         if (skipProto && property.name === "__proto__")
    498             continue;
    499         if (property.isAccessorProperty()) {
    500             if (property.name !== "__proto__" && property.getter) {
    501                 property.parentObject = value;
    502                 treeElement.appendChild(new treeElementConstructor(property));
    503             }
    504             if (property.isOwn) {
    505                 if (property.getter) {
    506                     var getterProperty = new WebInspector.RemoteObjectProperty("get " + property.name, property.getter);
    507                     getterProperty.parentObject = value;
    508                     treeElement.appendChild(new treeElementConstructor(getterProperty));
    509                 }
    510                 if (property.setter) {
    511                     var setterProperty = new WebInspector.RemoteObjectProperty("set " + property.name, property.setter);
    512                     setterProperty.parentObject = value;
    513                     treeElement.appendChild(new treeElementConstructor(setterProperty));
    514                 }
    515             }
    516         } else {
    517             property.parentObject = value;
    518             treeElement.appendChild(new treeElementConstructor(property));
    519         }
    520     }
    521     if (value && value.type === "function") {
    522         // Whether function has TargetFunction internal property.
    523         // This is a simple way to tell that the function is actually a bound function (we are not told).
    524         // Bound function never has inner scope and doesn't need corresponding UI node.
    525         var hasTargetFunction = false;
    526 
    527         if (internalProperties) {
    528             for (var i = 0; i < internalProperties.length; i++) {
    529                 if (internalProperties[i].name == "[[TargetFunction]]") {
    530                     hasTargetFunction = true;
    531                     break;
    532                 }
    533             }
    534         }
    535         if (!hasTargetFunction)
    536             treeElement.appendChild(new WebInspector.FunctionScopeMainTreeElement(value));
    537     }
    538     if (internalProperties) {
    539         for (var i = 0; i < internalProperties.length; i++) {
    540             internalProperties[i].parentObject = value;
    541             treeElement.appendChild(new treeElementConstructor(internalProperties[i]));
    542         }
    543     }
    544 }
    545 
    546 /**
    547  * @param {!WebInspector.RemoteObject} object
    548  * @param {!Array.<string>} propertyPath
    549  * @param {function(?WebInspector.RemoteObject, boolean=)} callback
    550  * @return {!Element}
    551  */
    552 WebInspector.ObjectPropertyTreeElement.createRemoteObjectAccessorPropertySpan = function(object, propertyPath, callback)
    553 {
    554     var rootElement = document.createElement("span");
    555     var element = rootElement.createChild("span", "properties-calculate-value-button");
    556     element.textContent = WebInspector.UIString("(...)");
    557     element.title = WebInspector.UIString("Invoke property getter");
    558     element.addEventListener("click", onInvokeGetterClick, false);
    559 
    560     function onInvokeGetterClick(event)
    561     {
    562         event.consume();
    563         object.getProperty(propertyPath, callback);
    564     }
    565 
    566     return rootElement;
    567 }
    568 
    569 /**
    570  * @constructor
    571  * @extends {TreeElement}
    572  * @param {!WebInspector.RemoteObject} remoteObject
    573  */
    574 WebInspector.FunctionScopeMainTreeElement = function(remoteObject)
    575 {
    576     TreeElement.call(this, "<function scope>", null, false);
    577     this.toggleOnClick = true;
    578     this.selectable = false;
    579     this._remoteObject = remoteObject;
    580     this.hasChildren = true;
    581 }
    582 
    583 WebInspector.FunctionScopeMainTreeElement.prototype = {
    584     onpopulate: function()
    585     {
    586         if (this.children.length && !this.shouldRefreshChildren)
    587             return;
    588 
    589         /**
    590          * @param {?Protocol.Error} error
    591          * @param {!DebuggerAgent.FunctionDetails} response
    592          * @this {WebInspector.FunctionScopeMainTreeElement}
    593          */
    594         function didGetDetails(error, response)
    595         {
    596             if (error) {
    597                 console.error(error);
    598                 return;
    599             }
    600             this.removeChildren();
    601 
    602             var scopeChain = response.scopeChain;
    603             if (!scopeChain)
    604                 return;
    605             for (var i = 0; i < scopeChain.length; ++i) {
    606                 var scope = scopeChain[i];
    607                 var title = null;
    608                 var isTrueObject;
    609 
    610                 switch (scope.type) {
    611                 case DebuggerAgent.ScopeType.Local:
    612                     // Not really expecting this scope type here.
    613                     title = WebInspector.UIString("Local");
    614                     isTrueObject = false;
    615                     break;
    616                 case DebuggerAgent.ScopeType.Closure:
    617                     title = WebInspector.UIString("Closure");
    618                     isTrueObject = false;
    619                     break;
    620                 case DebuggerAgent.ScopeType.Catch:
    621                     title = WebInspector.UIString("Catch");
    622                     isTrueObject = false;
    623                     break;
    624                 case DebuggerAgent.ScopeType.With:
    625                     title = WebInspector.UIString("With Block");
    626                     isTrueObject = true;
    627                     break;
    628                 case DebuggerAgent.ScopeType.Global:
    629                     title = WebInspector.UIString("Global");
    630                     isTrueObject = true;
    631                     break;
    632                 default:
    633                     console.error("Unknown scope type: " + scope.type);
    634                     continue;
    635                 }
    636 
    637                 var scopeRef = isTrueObject ? undefined : new WebInspector.ScopeRef(i, undefined, this._remoteObject.objectId);
    638                 var remoteObject = WebInspector.ScopeRemoteObject.fromPayload(scope.object, scopeRef);
    639                 if (isTrueObject) {
    640                     var property = WebInspector.RemoteObjectProperty.fromScopeValue(title, remoteObject);
    641                     property.parentObject = null;
    642                     this.appendChild(new this.treeOutline.section.treeElementConstructor(property));
    643                 } else {
    644                     var scopeTreeElement = new WebInspector.ScopeTreeElement(title, null, remoteObject);
    645                     this.appendChild(scopeTreeElement);
    646                 }
    647             }
    648 
    649         }
    650         DebuggerAgent.getFunctionDetails(this._remoteObject.objectId, didGetDetails.bind(this));
    651     },
    652 
    653     __proto__: TreeElement.prototype
    654 }
    655 
    656 /**
    657  * @constructor
    658  * @extends {TreeElement}
    659  * @param {!WebInspector.RemoteObject} remoteObject
    660  */
    661 WebInspector.ScopeTreeElement = function(title, subtitle, remoteObject)
    662 {
    663     // TODO: use subtitle parameter.
    664     TreeElement.call(this, title, null, false);
    665     this.toggleOnClick = true;
    666     this.selectable = false;
    667     this._remoteObject = remoteObject;
    668     this.hasChildren = true;
    669 }
    670 
    671 WebInspector.ScopeTreeElement.prototype = {
    672     onpopulate: function()
    673     {
    674         return WebInspector.ObjectPropertyTreeElement.populate(this, this._remoteObject);
    675     },
    676 
    677     __proto__: TreeElement.prototype
    678 }
    679 
    680 /**
    681  * @constructor
    682  * @extends {TreeElement}
    683  * @param {!WebInspector.RemoteObject} object
    684  * @param {number} fromIndex
    685  * @param {number} toIndex
    686  * @param {number} propertyCount
    687  */
    688 WebInspector.ArrayGroupingTreeElement = function(object, fromIndex, toIndex, propertyCount)
    689 {
    690     TreeElement.call(this, String.sprintf("[%d \u2026 %d]", fromIndex, toIndex), undefined, true);
    691     this._fromIndex = fromIndex;
    692     this._toIndex = toIndex;
    693     this._object = object;
    694     this._readOnly = true;
    695     this._propertyCount = propertyCount;
    696     this._populated = false;
    697 }
    698 
    699 WebInspector.ArrayGroupingTreeElement._bucketThreshold = 100;
    700 WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold = 250000;
    701 
    702 /**
    703  * @param {!TreeElement|!TreeOutline} treeElement
    704  * @param {!WebInspector.RemoteObject} object
    705  * @param {number} fromIndex
    706  * @param {number} toIndex
    707  */
    708 WebInspector.ArrayGroupingTreeElement._populateArray = function(treeElement, object, fromIndex, toIndex)
    709 {
    710     WebInspector.ArrayGroupingTreeElement._populateRanges(treeElement, object, fromIndex, toIndex, true);
    711 }
    712 
    713 /**
    714  * @param {!TreeElement|!TreeOutline} treeElement
    715  * @param {!WebInspector.RemoteObject} object
    716  * @param {number} fromIndex
    717  * @param {number} toIndex
    718  * @param {boolean} topLevel
    719  * @this {WebInspector.ArrayGroupingTreeElement}
    720  */
    721 WebInspector.ArrayGroupingTreeElement._populateRanges = function(treeElement, object, fromIndex, toIndex, topLevel)
    722 {
    723     object.callFunctionJSON(packRanges, [{value: fromIndex}, {value: toIndex}, {value: WebInspector.ArrayGroupingTreeElement._bucketThreshold}, {value: WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold}], callback.bind(this));
    724 
    725     /**
    726      * @this {Object}
    727      * @param {number=} fromIndex // must declare optional
    728      * @param {number=} toIndex // must declare optional
    729      * @param {number=} bucketThreshold // must declare optional
    730      * @param {number=} sparseIterationThreshold // must declare optional
    731      */
    732     function packRanges(fromIndex, toIndex, bucketThreshold, sparseIterationThreshold)
    733     {
    734         var ownPropertyNames = null;
    735 
    736         /**
    737          * @this {Object}
    738          */
    739         function doLoop(iterationCallback)
    740         {
    741             if (toIndex - fromIndex < sparseIterationThreshold) {
    742                 for (var i = fromIndex; i <= toIndex; ++i) {
    743                     if (i in this)
    744                         iterationCallback(i);
    745                 }
    746             } else {
    747                 ownPropertyNames = ownPropertyNames || Object.getOwnPropertyNames(this);
    748                 for (var i = 0; i < ownPropertyNames.length; ++i) {
    749                     var name = ownPropertyNames[i];
    750                     var index = name >>> 0;
    751                     if (String(index) === name && fromIndex <= index && index <= toIndex)
    752                         iterationCallback(index);
    753                 }
    754             }
    755         }
    756 
    757         var count = 0;
    758         function countIterationCallback()
    759         {
    760             ++count;
    761         }
    762         doLoop.call(this, countIterationCallback);
    763 
    764         var bucketSize = count;
    765         if (count <= bucketThreshold)
    766             bucketSize = count;
    767         else
    768             bucketSize = Math.pow(bucketThreshold, Math.ceil(Math.log(count) / Math.log(bucketThreshold)) - 1);
    769 
    770         var ranges = [];
    771         count = 0;
    772         var groupStart = -1;
    773         var groupEnd = 0;
    774         function loopIterationCallback(i)
    775         {
    776             if (groupStart === -1)
    777                 groupStart = i;
    778 
    779             groupEnd = i;
    780             if (++count === bucketSize) {
    781                 ranges.push([groupStart, groupEnd, count]);
    782                 count = 0;
    783                 groupStart = -1;
    784             }
    785         }
    786         doLoop.call(this, loopIterationCallback);
    787 
    788         if (count > 0)
    789             ranges.push([groupStart, groupEnd, count]);
    790         return ranges;
    791     }
    792 
    793     function callback(ranges)
    794     {
    795         if (ranges.length == 1)
    796             WebInspector.ArrayGroupingTreeElement._populateAsFragment(treeElement, object, ranges[0][0], ranges[0][1]);
    797         else {
    798             for (var i = 0; i < ranges.length; ++i) {
    799                 var fromIndex = ranges[i][0];
    800                 var toIndex = ranges[i][1];
    801                 var count = ranges[i][2];
    802                 if (fromIndex == toIndex)
    803                     WebInspector.ArrayGroupingTreeElement._populateAsFragment(treeElement, object, fromIndex, toIndex);
    804                 else
    805                     treeElement.appendChild(new WebInspector.ArrayGroupingTreeElement(object, fromIndex, toIndex, count));
    806             }
    807         }
    808         if (topLevel)
    809             WebInspector.ArrayGroupingTreeElement._populateNonIndexProperties(treeElement, object);
    810     }
    811 }
    812 
    813 /**
    814  * @param {!TreeElement|!TreeOutline} treeElement
    815  * @param {!WebInspector.RemoteObject} object
    816  * @param {number} fromIndex
    817  * @param {number} toIndex
    818  * @this {WebInspector.ArrayGroupingTreeElement}
    819  */
    820 WebInspector.ArrayGroupingTreeElement._populateAsFragment = function(treeElement, object, fromIndex, toIndex)
    821 {
    822     object.callFunction(buildArrayFragment, [{value: fromIndex}, {value: toIndex}, {value: WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold}], processArrayFragment.bind(this));
    823 
    824     /**
    825      * @this {Object}
    826      * @param {number=} fromIndex // must declare optional
    827      * @param {number=} toIndex // must declare optional
    828      * @param {number=} sparseIterationThreshold // must declare optional
    829      */
    830     function buildArrayFragment(fromIndex, toIndex, sparseIterationThreshold)
    831     {
    832         var result = Object.create(null);
    833         if (toIndex - fromIndex < sparseIterationThreshold) {
    834             for (var i = fromIndex; i <= toIndex; ++i) {
    835                 if (i in this)
    836                     result[i] = this[i];
    837             }
    838         } else {
    839             var ownPropertyNames = Object.getOwnPropertyNames(this);
    840             for (var i = 0; i < ownPropertyNames.length; ++i) {
    841                 var name = ownPropertyNames[i];
    842                 var index = name >>> 0;
    843                 if (String(index) === name && fromIndex <= index && index <= toIndex)
    844                     result[index] = this[index];
    845             }
    846         }
    847         return result;
    848     }
    849 
    850     /**
    851      * @param {?WebInspector.RemoteObject} arrayFragment
    852      * @param {boolean=} wasThrown
    853      * @this {WebInspector.ArrayGroupingTreeElement}
    854      */
    855     function processArrayFragment(arrayFragment, wasThrown)
    856     {
    857         if (!arrayFragment || wasThrown)
    858             return;
    859         arrayFragment.getAllProperties(false, processProperties.bind(this));
    860     }
    861 
    862     /** @this {WebInspector.ArrayGroupingTreeElement} */
    863     function processProperties(properties, internalProperties)
    864     {
    865         if (!properties)
    866             return;
    867 
    868         properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
    869         for (var i = 0; i < properties.length; ++i) {
    870             properties[i].parentObject = this._object;
    871             var childTreeElement = new treeElement.treeOutline.section.treeElementConstructor(properties[i]);
    872             childTreeElement._readOnly = true;
    873             treeElement.appendChild(childTreeElement);
    874         }
    875     }
    876 }
    877 
    878 /**
    879  * @param {!TreeElement|!TreeOutline} treeElement
    880  * @param {!WebInspector.RemoteObject} object
    881  * @this {WebInspector.ArrayGroupingTreeElement}
    882  */
    883 WebInspector.ArrayGroupingTreeElement._populateNonIndexProperties = function(treeElement, object)
    884 {
    885     object.callFunction(buildObjectFragment, undefined, processObjectFragment.bind(this));
    886 
    887     /** @this {Object} */
    888     function buildObjectFragment()
    889     {
    890         var result = Object.create(this.__proto__);
    891         var names = Object.getOwnPropertyNames(this);
    892         for (var i = 0; i < names.length; ++i) {
    893             var name = names[i];
    894             // Array index check according to the ES5-15.4.
    895             if (String(name >>> 0) === name && name >>> 0 !== 0xffffffff)
    896                 continue;
    897             var descriptor = Object.getOwnPropertyDescriptor(this, name);
    898             if (descriptor)
    899                 Object.defineProperty(result, name, descriptor);
    900         }
    901         return result;
    902     }
    903 
    904     /**
    905      * @param {?WebInspector.RemoteObject} arrayFragment
    906      * @param {boolean=} wasThrown
    907      * @this {WebInspector.ArrayGroupingTreeElement}
    908      */
    909     function processObjectFragment(arrayFragment, wasThrown)
    910     {
    911         if (!arrayFragment || wasThrown)
    912             return;
    913         arrayFragment.getOwnProperties(processProperties.bind(this));
    914     }
    915 
    916     /**
    917      * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
    918      * @param {?Array.<!WebInspector.RemoteObjectProperty>=} internalProperties
    919      * @this {WebInspector.ArrayGroupingTreeElement}
    920      */
    921     function processProperties(properties, internalProperties)
    922     {
    923         if (!properties)
    924             return;
    925         properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
    926         for (var i = 0; i < properties.length; ++i) {
    927             properties[i].parentObject = this._object;
    928             var childTreeElement = new treeElement.treeOutline.section.treeElementConstructor(properties[i]);
    929             childTreeElement._readOnly = true;
    930             treeElement.appendChild(childTreeElement);
    931         }
    932     }
    933 }
    934 
    935 WebInspector.ArrayGroupingTreeElement.prototype = {
    936     onpopulate: function()
    937     {
    938         if (this._populated)
    939             return;
    940 
    941         this._populated = true;
    942 
    943         if (this._propertyCount >= WebInspector.ArrayGroupingTreeElement._bucketThreshold) {
    944             WebInspector.ArrayGroupingTreeElement._populateRanges(this, this._object, this._fromIndex, this._toIndex, false);
    945             return;
    946         }
    947         WebInspector.ArrayGroupingTreeElement._populateAsFragment(this, this._object, this._fromIndex, this._toIndex);
    948     },
    949 
    950     onattach: function()
    951     {
    952         this.listItemElement.classList.add("name");
    953     },
    954 
    955     __proto__: TreeElement.prototype
    956 }
    957 
    958 /**
    959  * @constructor
    960  * @extends {WebInspector.TextPrompt}
    961  * @param {boolean=} renderAsBlock
    962  */
    963 WebInspector.ObjectPropertyPrompt = function(commitHandler, cancelHandler, renderAsBlock)
    964 {
    965     WebInspector.TextPrompt.call(this, WebInspector.runtimeModel.completionsForTextPrompt.bind(WebInspector.runtimeModel));
    966     this.setSuggestBoxEnabled("generic-suggest");
    967     if (renderAsBlock)
    968         this.renderAsBlock();
    969 }
    970 
    971 WebInspector.ObjectPropertyPrompt.prototype = {
    972     __proto__: WebInspector.TextPrompt.prototype
    973 }
    974