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