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 */
38WebInspector.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
51WebInspector.ObjectPropertiesSection._arrayLoadThreshold = 100;
52
53WebInspector.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 */
125WebInspector.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 */
145WebInspector.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
155WebInspector.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 */
482WebInspector.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 */
519WebInspector.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 */
581WebInspector.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 */
597WebInspector.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 */
622WebInspector.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
631WebInspector.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 */
709WebInspector.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
718WebInspector.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 */
763WebInspector.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
772WebInspector.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 */
789WebInspector.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
802WebInspector.ArrayGroupingTreeElement._bucketThreshold = 100;
803WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold = 250000;
804
805/**
806 * @param {!TreeElement|!TreeOutline} treeElement
807 * @param {!WebInspector.RemoteObject} object
808 * @param {number} fromIndex
809 * @param {number} toIndex
810 */
811WebInspector.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 */
824WebInspector.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 */
924WebInspector.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 */
988WebInspector.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
1043WebInspector.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 */
1071WebInspector.ObjectPropertyPrompt = function(renderAsBlock)
1072{
1073    WebInspector.TextPrompt.call(this, WebInspector.ExecutionContextSelector.completionsForTextPromptInCurrentContext);
1074    this.setSuggestBoxEnabled(true);
1075    if (renderAsBlock)
1076        this.renderAsBlock();
1077}
1078
1079WebInspector.ObjectPropertyPrompt.prototype = {
1080    __proto__: WebInspector.TextPrompt.prototype
1081}
1082