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