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