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