Web Inspector: register context menu providers for lazily loaded panels.
[WebKit-https.git] / Source / WebCore / inspector / front-end / ObjectPropertiesSection.js
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), true);
57     },
58
59     _contextMenuEventFired: function(event)
60     {
61         var contextMenu = new WebInspector.ContextMenu();
62         contextMenu.appendApplicableItems(this.object);
63         if (!contextMenu.isEmpty())
64             contextMenu.show(event);
65     },
66
67     onpopulate: function()
68     {
69         this.update();
70     },
71
72     update: function()
73     {
74         if (this.object.arrayLength() > WebInspector.ObjectPropertiesSection._arrayLoadThreshold) {
75             this.propertiesTreeOutline.removeChildren();
76             WebInspector.ArrayGroupingTreeElement._populateArray(this.propertiesTreeOutline, this.object, 0, this.object.arrayLength() - 1);
77             return;
78         }
79
80         function callback(properties)
81         {
82             if (!properties)
83                 return;
84             this.updateProperties(properties);
85         }
86
87         if (this.ignoreHasOwnProperty)
88             this.object.getAllProperties(callback.bind(this));
89         else
90             this.object.getOwnProperties(callback.bind(this));
91     },
92
93     updateProperties: function(properties, 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         properties.sort(rootPropertyComparer);
106
107         this.propertiesTreeOutline.removeChildren();
108
109         for (var i = 0; i < properties.length; ++i) {
110             if (this.skipProto && properties[i].name === "__proto__")
111                 continue;
112             properties[i].parentObject = this.object;
113         }
114
115         this.propertiesForTest = properties;
116
117         for (var i = 0; i < properties.length; ++i)
118             this.propertiesTreeOutline.appendChild(new rootTreeElementConstructor(properties[i]));
119
120         if (!this.propertiesTreeOutline.children.length) {
121             var title = document.createElement("div");
122             title.className = "info";
123             title.textContent = this.emptyPlaceholder;
124             var infoElement = new TreeElement(title, null, false);
125             this.propertiesTreeOutline.appendChild(infoElement);
126         }
127     }
128 }
129
130 WebInspector.ObjectPropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;
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
141     // if used elsewhere make sure to
142     //  - convert a and b to strings (not needed here, properties are all strings)
143     //  - check if a == b (not needed here, no two properties can be the same)
144
145     var diff = 0;
146     var chunk = /^\d+|^\D+/;
147     var chunka, chunkb, anum, bnum;
148     while (diff === 0) {
149         if (!a && b)
150             return -1;
151         if (!b && a)
152             return 1;
153         chunka = a.match(chunk)[0];
154         chunkb = b.match(chunk)[0];
155         anum = !isNaN(chunka);
156         bnum = !isNaN(chunkb);
157         if (anum && !bnum)
158             return -1;
159         if (bnum && !anum)
160             return 1;
161         if (anum && bnum) {
162             diff = chunka - chunkb;
163             if (diff === 0 && chunka.length !== chunkb.length) {
164                 if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case)
165                     return chunka.length - chunkb.length;
166                 else
167                     return chunkb.length - chunka.length;
168             }
169         } else if (chunka !== chunkb)
170             return (chunka < chunkb) ? -1 : 1;
171         a = a.substring(chunka.length);
172         b = b.substring(chunkb.length);
173     }
174     return diff;
175 }
176
177 /**
178  * @constructor
179  * @extends {TreeElement}
180  * @param {WebInspector.RemoteObjectProperty} property
181  */
182 WebInspector.ObjectPropertyTreeElement = function(property)
183 {
184     this.property = property;
185
186     // Pass an empty title, the title gets made later in onattach.
187     TreeElement.call(this, "", null, false);
188     this.toggleOnClick = true;
189     this.selectable = false;
190 }
191
192 WebInspector.ObjectPropertyTreeElement.prototype = {
193     onpopulate: function()
194     {
195         return WebInspector.ObjectPropertyTreeElement.populate(this, this.property.value);
196     },
197
198     ondblclick: function(event)
199     {
200         if (this.property.writable)
201             this.startEditing(event);
202     },
203
204     onattach: function()
205     {
206         this.update();
207     },
208
209     update: function()
210     {
211         this.nameElement = document.createElement("span");
212         this.nameElement.className = "name";
213         this.nameElement.textContent = this.property.name;
214         if (!this.property.enumerable)
215             this.nameElement.addStyleClass("dimmed");
216
217         var separatorElement = document.createElement("span");
218         separatorElement.className = "separator";
219         separatorElement.textContent = ": ";
220
221         this.valueElement = document.createElement("span");
222         this.valueElement.className = "value";
223
224         var description = this.property.value.description;
225         // Render \n as a nice unicode cr symbol.
226         if (this.property.wasThrown)
227             this.valueElement.textContent = "[Exception: " + description + "]";
228         else if (this.property.value.type === "string" && typeof description === "string") {
229             this.valueElement.textContent = "\"" + description.replace(/\n/g, "\u21B5") + "\"";
230             this.valueElement._originalTextContent = "\"" + description + "\"";
231         } else if (this.property.value.type === "function" && typeof description === "string") {
232             this.valueElement.textContent = /.*/.exec(description)[0].replace(/ +$/g, "");
233             this.valueElement._originalTextContent = description;
234         } else
235             this.valueElement.textContent = description;
236
237         if (this.property.value.type === "function")
238             this.valueElement.addEventListener("contextmenu", this._functionContextMenuEventFired.bind(this), false);
239
240         if (this.property.wasThrown)
241             this.valueElement.addStyleClass("error");
242         if (this.property.value.subtype)
243             this.valueElement.addStyleClass("console-formatted-" + this.property.value.subtype);
244         else if (this.property.value.type)
245             this.valueElement.addStyleClass("console-formatted-" + this.property.value.type);
246         if (this.property.value.subtype === "node")
247             this.valueElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), false);
248
249         this.valueElement.title = description || "";
250
251         this.listItemElement.removeChildren();
252
253         this.listItemElement.appendChild(this.nameElement);
254         this.listItemElement.appendChild(separatorElement);
255         this.listItemElement.appendChild(this.valueElement);
256         this.hasChildren = this.property.value.hasChildren && !this.property.wasThrown;
257     },
258
259     _contextMenuEventFired: function(event)
260     {
261         function selectNode(nodeId)
262         {
263             if (nodeId)
264                 WebInspector.domAgent.inspectElement(nodeId);
265         }
266
267         function revealElement()
268         {
269             this.property.value.pushNodeToFrontend(selectNode);
270         }
271
272         var contextMenu = new WebInspector.ContextMenu();
273         contextMenu.appendItem(WebInspector.UIString("Reveal in Elements Panel"), revealElement.bind(this));
274         contextMenu.show(event);
275     },
276
277     _functionContextMenuEventFired: function(event)
278     {
279         function didGetDetails(error, response)
280         {
281             if (error) {
282                 console.error(error);
283                 return;
284             }
285             WebInspector.panel("scripts").showFunctionDefinition(response.location);
286         }
287
288         function revealFunction()
289         {
290             DebuggerAgent.getFunctionDetails(this.property.value.objectId, didGetDetails.bind(this));
291         }
292
293         var contextMenu = new WebInspector.ContextMenu();
294         contextMenu.appendItem(WebInspector.UIString("Show function definition"), revealFunction.bind(this));
295         contextMenu.show(event);
296     },
297
298     updateSiblings: function()
299     {
300         if (this.parent.root)
301             this.treeOutline.section.update();
302         else
303             this.parent.shouldRefreshChildren = true;
304     },
305
306     renderPromptAsBlock: function()
307     {
308         return false;
309     },
310
311     /**
312      * @param {Event=} event
313      */
314     elementAndValueToEdit: function(event)
315     {
316         return [this.valueElement, (typeof this.valueElement._originalTextContent === "string") ? this.valueElement._originalTextContent : undefined];
317     },
318
319     startEditing: function(event)
320     {
321         var elementAndValueToEdit = this.elementAndValueToEdit(event);
322         var elementToEdit = elementAndValueToEdit[0];
323         var valueToEdit = elementAndValueToEdit[1];
324
325         if (WebInspector.isBeingEdited(elementToEdit) || !this.treeOutline.section.editable || this._readOnly)
326             return;
327
328         // Edit original source.
329         if (typeof valueToEdit !== "undefined")
330             elementToEdit.textContent = valueToEdit;
331
332         var context = { expanded: this.expanded, elementToEdit: elementToEdit, previousContent: elementToEdit.textContent };
333
334         // Lie about our children to prevent expanding on double click and to collapse subproperties.
335         this.hasChildren = false;
336
337         this.listItemElement.addStyleClass("editing-sub-part");
338
339         this._prompt = new WebInspector.ObjectPropertyPrompt(this.editingCommitted.bind(this, null, elementToEdit.textContent, context.previousContent, context), this.editingCancelled.bind(this, null, context), this.renderPromptAsBlock());
340
341         function blurListener()
342         {
343             this.editingCommitted(null, elementToEdit.textContent, context.previousContent, context);
344         }
345
346         var proxyElement = this._prompt.attachAndStartEditing(elementToEdit, blurListener.bind(this));
347         window.getSelection().setBaseAndExtent(elementToEdit, 0, elementToEdit, 1);
348         proxyElement.addEventListener("keydown", this._promptKeyDown.bind(this, context), false);
349     },
350
351     editingEnded: function(context)
352     {
353         this._prompt.detach();
354         delete this._prompt;
355
356         this.listItemElement.scrollLeft = 0;
357         this.listItemElement.removeStyleClass("editing-sub-part");
358         if (context.expanded)
359             this.expand();
360     },
361
362     editingCancelled: function(element, context)
363     {
364         this.editingEnded(context);
365         this.update();
366     },
367
368     editingCommitted: function(element, userInput, previousContent, context)
369     {
370         if (userInput === previousContent)
371             return this.editingCancelled(element, context); // nothing changed, so cancel
372
373         this.editingEnded(context);
374         this.applyExpression(userInput, true);
375     },
376
377     _promptKeyDown: function(context, event)
378     {
379         if (isEnterKey(event)) {
380             event.consume(true);
381             return this.editingCommitted(null, context.elementToEdit.textContent, context.previousContent, context);
382         }
383         if (event.keyIdentifier === "U+001B") { // Esc
384             event.consume();
385             return this.editingCancelled(null, context);
386         }
387     },
388
389     applyExpression: function(expression, updateInterface)
390     {
391         expression = expression.trim();
392         var expressionLength = expression.length;
393         function callback(error)
394         {
395             if (!updateInterface)
396                 return;
397
398             if (error)
399                 this.update();
400
401             if (!expressionLength) {
402                 // The property was deleted, so remove this tree element.
403                 this.parent.removeChild(this);
404             } else {
405                 // Call updateSiblings since their value might be based on the value that just changed.
406                 this.updateSiblings();
407             }
408         };
409         this.property.parentObject.setPropertyValue(this.property.name, expression.trim(), callback.bind(this));
410     }
411 }
412
413 /**
414  * @param {TreeElement} treeElement
415  * @param {WebInspector.RemoteObject} value
416  */
417 WebInspector.ObjectPropertyTreeElement.populate = function(treeElement, value) {
418     if (treeElement.children.length && !treeElement.shouldRefreshChildren)
419         return;
420
421     if (value.arrayLength() > WebInspector.ObjectPropertiesSection._arrayLoadThreshold) {
422         treeElement.removeChildren();
423         WebInspector.ArrayGroupingTreeElement._populateArray(treeElement, value, 0, value.arrayLength() - 1);
424         return;
425     }
426
427     function callback(properties)
428     {
429         treeElement.removeChildren();
430         if (!properties)
431             return;
432
433         properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
434         for (var i = 0; i < properties.length; ++i) {
435             if (treeElement.treeOutline.section.skipProto && properties[i].name === "__proto__")
436                 continue;
437             properties[i].parentObject = value;
438             treeElement.appendChild(new treeElement.treeOutline.section.treeElementConstructor(properties[i]));
439         }
440         if (value.type === "function")
441             treeElement.appendChild(new WebInspector.FunctionScopeMainTreeElement(value));
442     }
443
444     value.getOwnProperties(callback);
445 }
446
447 WebInspector.ObjectPropertyTreeElement.prototype.__proto__ = TreeElement.prototype;
448
449 /**
450  * @constructor
451  * @extends {TreeElement}
452  * @param {WebInspector.RemoteObject} remoteObject
453  */
454 WebInspector.FunctionScopeMainTreeElement = function(remoteObject)
455 {
456     TreeElement.call(this, "<function scope>", null, false);
457     this.toggleOnClick = true;
458     this.selectable = false;
459     this._remoteObject = remoteObject;
460     this.hasChildren = true;
461 }
462
463 WebInspector.FunctionScopeMainTreeElement.prototype = {
464     onpopulate: function()
465     {
466         if (this.children.length && !this.shouldRefreshChildren)
467             return;
468
469         function didGetDetails(error, response)
470         {
471             if (error) {
472                 console.error(error);
473                 return;
474             }
475             this.removeChildren();
476
477             var scopeChain = response.scopeChain;
478             for (var i = 0; i < scopeChain.length; ++i) {
479                 var scope = scopeChain[i];
480                 var title = null;
481                 var isTrueObject;
482
483                 switch (scope.type) {
484                     case "local":
485                         // Not really expecting this scope type here.
486                         title = WebInspector.UIString("Local");
487                         isTrueObject = false;
488                         break;
489                     case "closure":
490                         title = WebInspector.UIString("Closure");
491                         isTrueObject = false;
492                         break;
493                     case "catch":
494                         title = WebInspector.UIString("Catch");
495                         isTrueObject = false;
496                         break;
497                     case "with":
498                         title = WebInspector.UIString("With Block");
499                         isTrueObject = true;
500                         break;
501                     case "global":
502                         title = WebInspector.UIString("Global");
503                         isTrueObject = true;
504                         break;
505                 }
506
507                 var remoteObject = WebInspector.RemoteObject.fromPayload(scope.object);
508                 if (isTrueObject) {
509                     var property = WebInspector.RemoteObjectProperty.fromScopeValue(title, remoteObject);
510                     property.parentObject = null;
511                     this.appendChild(new this.treeOutline.section.treeElementConstructor(property));
512                 } else {
513                     var scopeTreeElement = new WebInspector.ScopeTreeElement(title, null, remoteObject);
514                     this.appendChild(scopeTreeElement);
515                 }
516             }
517
518         }
519         DebuggerAgent.getFunctionDetails(this._remoteObject.objectId, didGetDetails.bind(this));
520     }
521 };
522
523 WebInspector.FunctionScopeMainTreeElement.prototype.__proto__ = TreeElement.prototype;
524
525 /**
526  * @constructor
527  * @extends {TreeElement}
528  * @param {WebInspector.RemoteObject} remoteObject
529  */
530 WebInspector.ScopeTreeElement = function(title, subtitle, remoteObject)
531 {
532     // TODO: use subtitle parameter.
533     TreeElement.call(this, title, null, false);
534     this.toggleOnClick = true;
535     this.selectable = false;
536     this._remoteObject = remoteObject;
537     this.hasChildren = true;
538 }
539
540 WebInspector.ScopeTreeElement.prototype = {
541     onpopulate: function()
542     {
543         return WebInspector.ObjectPropertyTreeElement.populate(this, this._remoteObject);
544     }
545 };
546
547 WebInspector.ScopeTreeElement.prototype.__proto__ = TreeElement.prototype;
548
549 /**
550  * @constructor
551  * @extends {TreeElement}
552  * @param {WebInspector.RemoteObject} object
553  * @param {number} fromIndex
554  * @param {number} toIndex
555  * @param {number} propertyCount
556  */
557 WebInspector.ArrayGroupingTreeElement = function(object, fromIndex, toIndex, propertyCount)
558 {
559     TreeElement.call(this, String.sprintf("[%d \u2026 %d]", fromIndex, toIndex), undefined, true);
560     this._fromIndex = fromIndex;
561     this._toIndex = toIndex;
562     this._object = object;
563     this._readOnly = true;
564     this._propertyCount = propertyCount;
565     this._populated = false;
566 }
567
568 WebInspector.ArrayGroupingTreeElement._bucketThreshold = 100;
569
570 /**
571  * @param {TreeElement|TreeOutline} treeElement
572  * @param {WebInspector.RemoteObject} object
573  * @param {number} fromIndex
574  * @param {number} toIndex
575  */
576 WebInspector.ArrayGroupingTreeElement._populateArray = function(treeElement, object, fromIndex, toIndex)
577 {
578     WebInspector.ArrayGroupingTreeElement._populateRanges(treeElement, object, fromIndex, toIndex, true);
579 }
580
581 /**
582  * @param {TreeElement|TreeOutline} treeElement
583  * @param {WebInspector.RemoteObject} object
584  * @param {number} fromIndex
585  * @param {number} toIndex
586  * @param {boolean} topLevel
587  */
588 WebInspector.ArrayGroupingTreeElement._populateRanges = function(treeElement, object, fromIndex, toIndex, topLevel)
589 {
590     object.callFunctionJSON(packRanges, [{value: fromIndex}, {value: toIndex}, {value: WebInspector.ArrayGroupingTreeElement._bucketThreshold}], callback.bind(this));
591
592     /**
593      * @this {Object}
594      * @param {number=} fromIndex // must declare optional
595      * @param {number=} toIndex // must declare optional
596      * @param {number=} bucketThreshold // must declare optional
597      */
598     function packRanges(fromIndex, toIndex, bucketThreshold)
599     {
600         var count = 0;
601         for (var i = fromIndex; i <= toIndex; ++i) {
602             var value = this[i];
603             if (typeof value !== "undefined")
604                 ++count;
605         }
606
607         var bucketSize = count;
608         if (count <= bucketThreshold)
609             bucketSize = count;
610         else
611             bucketSize = Math.pow(bucketThreshold, Math.floor(Math.log(count) / Math.log(bucketThreshold)));
612
613         var ranges = [];
614         count = 0;
615         var groupStart = -1;
616         var groupEnd = 0;
617         for (var i = fromIndex; i <= toIndex; ++i) {
618             var value = this[i];
619             if (typeof value === "undefined")
620                 continue;
621
622             if (groupStart === -1)
623                 groupStart = i;
624
625             groupEnd = i;
626             if (++count === bucketSize) {
627                 ranges.push([groupStart, groupEnd, count]);
628                 count = 0;
629                 groupStart = -1;
630             }
631         }
632
633         if (count > 0)
634             ranges.push([groupStart, groupEnd, count]);
635         return ranges;
636     }
637
638     function callback(ranges)
639     {
640         if (ranges.length == 1)
641             WebInspector.ArrayGroupingTreeElement._populateAsFragment(treeElement, object, ranges[0][0], ranges[0][1]);
642         else {
643             for (var i = 0; i < ranges.length; ++i) {
644                 var fromIndex = ranges[i][0];
645                 var toIndex = ranges[i][1];
646                 var count = ranges[i][2];
647                 if (fromIndex == toIndex)
648                     WebInspector.ArrayGroupingTreeElement._populateAsFragment(treeElement, object, fromIndex, toIndex);
649                 else
650                     treeElement.appendChild(new WebInspector.ArrayGroupingTreeElement(object, fromIndex, toIndex, count));
651             }
652         }
653         if (topLevel)
654             WebInspector.ArrayGroupingTreeElement._populateNonIndexProperties(treeElement, object);
655     }
656 }
657
658 /**
659  * @param {TreeElement|TreeOutline} treeElement
660  * @param {WebInspector.RemoteObject} object
661  * @param {number} fromIndex
662  * @param {number} toIndex
663  */
664 WebInspector.ArrayGroupingTreeElement._populateAsFragment = function(treeElement, object, fromIndex, toIndex)
665 {
666     object.callFunction(buildArrayFragment, [{value: fromIndex}, {value: toIndex}], processArrayFragment.bind(this));
667
668     /**
669      * @this {Object}
670      * @param {number=} fromIndex // must declare optional
671      * @param {number=} toIndex // must declare optional
672      */
673     function buildArrayFragment(fromIndex, toIndex)
674     {
675         var result = Object.create(null);
676         for (var i = fromIndex; i <= toIndex; ++i) {
677             var value = this[i];
678             if (typeof value !== "undefined")
679                 result[i] = value;
680         }
681         return result;
682     }
683
684     function processArrayFragment(arrayFragment)
685     {
686         arrayFragment.getAllProperties(processProperties.bind(this));
687     }
688
689     /** @this {WebInspector.ArrayGroupingTreeElement} */
690     function processProperties(properties)
691     {
692         if (!properties)
693             return;
694
695         properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
696         for (var i = 0; i < properties.length; ++i) {
697             properties[i].parentObject = this._object;
698             var childTreeElement = new treeElement.treeOutline.section.treeElementConstructor(properties[i]);
699             childTreeElement._readOnly = true;
700             treeElement.appendChild(childTreeElement);
701         }
702     }
703 }
704
705 /**
706  * @param {TreeElement|TreeOutline} treeElement
707  * @param {WebInspector.RemoteObject} object
708  */
709 WebInspector.ArrayGroupingTreeElement._populateNonIndexProperties = function(treeElement, object)
710 {
711     object.callFunction(buildObjectFragment, undefined, processObjectFragment.bind(this));
712
713     /** @this {Object} */
714     function buildObjectFragment()
715     {
716         var result = Object.create(this.__proto__);
717         var names = Object.getOwnPropertyNames(this);
718         for (var i = 0; i < names.length; ++i) {
719             var name = names[i];
720             if (!isNaN(name))
721                 continue;
722             var descriptor = Object.getOwnPropertyDescriptor(this, name);
723             if (descriptor)
724                 Object.defineProperty(result, name, descriptor);
725         }
726         return result;
727     }
728
729     function processObjectFragment(arrayFragment)
730     {
731         arrayFragment.getOwnProperties(processProperties.bind(this));
732     }
733
734     /** @this {WebInspector.ArrayGroupingTreeElement} */
735     function processProperties(properties)
736     {
737         if (!properties)
738             return;
739
740         properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
741         for (var i = 0; i < properties.length; ++i) {
742             properties[i].parentObject = this._object;
743             var childTreeElement = new treeElement.treeOutline.section.treeElementConstructor(properties[i]);
744             childTreeElement._readOnly = true;
745             treeElement.appendChild(childTreeElement);
746         }
747     }
748 }
749
750 WebInspector.ArrayGroupingTreeElement.prototype = {
751     onpopulate: function()
752     {
753         if (this._populated)
754             return;
755
756         this._populated = true;
757
758         if (this._propertyCount >= WebInspector.ArrayGroupingTreeElement._bucketThreshold) {
759             WebInspector.ArrayGroupingTreeElement._populateRanges(this, this._object, this._fromIndex, this._toIndex, false);
760             return;
761         }
762         WebInspector.ArrayGroupingTreeElement._populateAsFragment(this, this._object, this._fromIndex, this._toIndex);
763     },
764
765     onattach: function()
766     {
767         this.listItemElement.addStyleClass("name");
768     }
769 }
770
771 WebInspector.ArrayGroupingTreeElement.prototype.__proto__ = TreeElement.prototype;
772
773 /**
774  * @constructor
775  * @extends {WebInspector.TextPrompt}
776  * @param {boolean=} renderAsBlock
777  */
778 WebInspector.ObjectPropertyPrompt = function(commitHandler, cancelHandler, renderAsBlock)
779 {
780     const ExpressionStopCharacters = " =:[({;,!+-*/&|^<>."; // Same as in ConsoleView.js + "."
781     WebInspector.TextPrompt.call(this, WebInspector.consoleView.completionsForTextPrompt.bind(WebInspector.consoleView), ExpressionStopCharacters);
782     this.setSuggestBoxEnabled("generic-suggest");
783     if (renderAsBlock)
784         this.renderAsBlock();
785 }
786
787 WebInspector.ObjectPropertyPrompt.prototype.__proto__ = WebInspector.TextPrompt.prototype;