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