Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / components / ObjectPropertiesSection.js
blob93dbcdd5977eb6c9834fd0b2d8d8ff9d3399e9e6
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  */
27 /**
28  * @constructor
29  * @extends {TreeOutlineInShadow}
30  * @param {!WebInspector.RemoteObject} object
31  * @param {?string|!Element=} title
32  * @param {?string=} emptyPlaceholder
33  * @param {boolean=} ignoreHasOwnProperty
34  * @param {!Array.<!WebInspector.RemoteObjectProperty>=} extraProperties
35  */
36 WebInspector.ObjectPropertiesSection = function(object, title, emptyPlaceholder, ignoreHasOwnProperty, extraProperties)
38     this._object = object;
39     this._editable = true;
40     TreeOutlineInShadow.call(this);
41     this.setFocusable(false);
42     this._objectTreeElement = new WebInspector.ObjectPropertiesSection.RootElement(object, emptyPlaceholder, ignoreHasOwnProperty, extraProperties);
43     this.appendChild(this._objectTreeElement);
44     if (typeof title === "string" || !title)
45         this.element.createChild("span").textContent = title || "";
46     else
47         this.element.appendChild(title);
49     this.element._section = this;
50     this.registerRequiredCSS("components/objectValue.css");
51     this.registerRequiredCSS("components/objectPropertiesSection.css");
52     this.rootElement().childrenListElement.classList.add("source-code", "object-properties-section")
55 /** @const */
56 WebInspector.ObjectPropertiesSection._arrayLoadThreshold = 100;
58 /**
59  * @param {!WebInspector.RemoteObject} object
60  * @param {boolean=} skipProto
61  * @return {!Element}
62  */
63 WebInspector.ObjectPropertiesSection.defaultObjectPresentation = function(object, skipProto)
65     var componentRoot = createElementWithClass("span", "source-code");
66     var shadowRoot = WebInspector.createShadowRootWithCoreStyles(componentRoot);
67     shadowRoot.appendChild(WebInspector.Widget.createStyleElement("components/objectValue.css"));
68     shadowRoot.appendChild(WebInspector.ObjectPropertiesSection.createValueElement(object, false));
69     if (!object.hasChildren)
70         return componentRoot;
72     var objectPropertiesSection = new WebInspector.ObjectPropertiesSection(object, componentRoot);
73     objectPropertiesSection.editable = false;
74     if (skipProto)
75         objectPropertiesSection.skipProto();
77     return objectPropertiesSection.element;
80 WebInspector.ObjectPropertiesSection.prototype = {
81     skipProto: function()
82     {
83         this._skipProto = true;
84     },
86     expand: function()
87     {
88         this._objectTreeElement.expand();
89     },
91     /**
92      * @return {!TreeElement}
93      */
94     objectTreeElement: function()
95     {
96         return this._objectTreeElement;
97     },
99     enableContextMenu: function()
100     {
101         this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), false);
102     },
104     _contextMenuEventFired: function(event)
105     {
106         var contextMenu = new WebInspector.ContextMenu(event);
107         contextMenu.appendApplicableItems(this._object);
108         contextMenu.show();
109     },
111     titleLessMode: function()
112     {
113         this._objectTreeElement.listItemElement.classList.add("hidden");
114         this._objectTreeElement.childrenListElement.classList.add("title-less-mode");
115         this._objectTreeElement.expand();
116     },
118     __proto__: TreeOutlineInShadow.prototype
122  * @param {!WebInspector.RemoteObjectProperty} propertyA
123  * @param {!WebInspector.RemoteObjectProperty} propertyB
124  * @return {number}
125  */
126 WebInspector.ObjectPropertiesSection.CompareProperties = function(propertyA, propertyB)
128     var a = propertyA.name;
129     var b = propertyB.name;
130     if (a === "__proto__")
131         return 1;
132     if (b === "__proto__")
133         return -1;
134     if (propertyA.symbol && !propertyB.symbol)
135         return 1;
136     if (propertyB.symbol && !propertyA.symbol)
137         return -1;
138     return String.naturalOrderComparator(a, b);
142  * @constructor
143  * @extends {TreeElement}
144  * @param {!WebInspector.RemoteObject} object
145  * @param {?string=} emptyPlaceholder
146  * @param {boolean=} ignoreHasOwnProperty
147  * @param {!Array.<!WebInspector.RemoteObjectProperty>=} extraProperties
148  */
149 WebInspector.ObjectPropertiesSection.RootElement = function(object, emptyPlaceholder, ignoreHasOwnProperty, extraProperties)
151     this._object = object;
152     this._extraProperties = extraProperties || [];
153     this._ignoreHasOwnProperty = !!ignoreHasOwnProperty;
154     this._emptyPlaceholder = emptyPlaceholder;
155     var contentElement = createElement("content");
156     TreeElement.call(this, contentElement);
157     this.setExpandable(true);
158     this.selectable = false;
159     this.toggleOnClick = true;
162 WebInspector.ObjectPropertiesSection.RootElement.prototype = {
164     onexpand: function()
165     {
166         if (this.treeOutline)
167             this.treeOutline.element.classList.add("expanded");
168     },
170     oncollapse: function()
171     {
172         if (this.treeOutline)
173             this.treeOutline.element.classList.remove("expanded");
174     },
176     /**
177      * @override
178      * @param {!Event} e
179      * @return {boolean}
180      */
181     ondblclick: function(e)
182     {
183         return true;
184     },
186     onpopulate: function()
187     {
188         WebInspector.ObjectPropertyTreeElement._populate(this, this._object, !!this.treeOutline._skipProto, this._emptyPlaceholder, this._ignoreHasOwnProperty, this._extraProperties);
189     },
191     __proto__: TreeElement.prototype
195  * @constructor
196  * @extends {TreeElement}
197  * @param {!WebInspector.RemoteObjectProperty} property
198  */
199 WebInspector.ObjectPropertyTreeElement = function(property)
201     this.property = property;
203     // Pass an empty title, the title gets made later in onattach.
204     TreeElement.call(this);
205     this.toggleOnClick = true;
206     this.selectable = false;
209 WebInspector.ObjectPropertyTreeElement.prototype = {
210     onpopulate: function()
211     {
212         var propertyValue = /** @type {!WebInspector.RemoteObject} */ (this.property.value);
213         console.assert(propertyValue);
214         var skipProto = this.treeOutline ? this.treeOutline._skipProto : true;
215         WebInspector.ObjectPropertyTreeElement._populate(this, propertyValue, skipProto);
216     },
218     /**
219      * @override
220      * @return {boolean}
221      */
222     ondblclick: function(event)
223     {
224         var editableElement = this.valueElement;
225         if (!this.property.value.customPreview() && (this.property.writable || this.property.setter) && event.target.isSelfOrDescendant(editableElement))
226             this._startEditing();
227         return false;
228     },
230     /**
231      * @override
232      */
233     onattach: function()
234     {
235         this.update();
236         if (this.property.value)
237             this.setExpandable(!this.property.value.customPreview() && this.property.value.hasChildren && !this.property.wasThrown);
238     },
240     update: function()
241     {
242         this.nameElement = WebInspector.ObjectPropertiesSection.createNameElement(this.property.name);
243         if (!this.property.enumerable)
244             this.nameElement.classList.add("object-properties-section-dimmed");
245         if (this.property.isAccessorProperty())
246             this.nameElement.classList.add("properties-accessor-property-name");
247         if (this.property.symbol)
248             this.nameElement.addEventListener("contextmenu", this._contextMenuFired.bind(this, this.property.symbol), false);
250         var separatorElement = createElementWithClass("span", "object-properties-section-separator");
251         separatorElement.textContent = ": ";
253         if (this.property.value) {
254             this.valueElement = WebInspector.ObjectPropertiesSection.createValueElementWithCustomSupport(this.property.value, this.property.wasThrown, this.listItemElement);
255             this.valueElement.addEventListener("contextmenu", this._contextMenuFired.bind(this, this.property.value), false);
256         } else 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 = createElementWithClass("span", "object-value-undefined");
260             this.valueElement.textContent = WebInspector.UIString("<unreadable>");
261             this.valueElement.title = WebInspector.UIString("No property getter");
262         }
264         this.listItemElement.removeChildren();
265         this.listItemElement.appendChildren(this.nameElement, separatorElement, this.valueElement);
266     },
268     _contextMenuFired: function(value, event)
269     {
270         var contextMenu = new WebInspector.ContextMenu(event);
271         contextMenu.appendApplicableItems(value);
272         contextMenu.show();
273     },
275     _startEditing: function()
276     {
277         if (this._prompt || !this.treeOutline._editable || this._readOnly)
278             return;
280         this._editableDiv = this.listItemElement.createChild("span");
282         var text = this.property.value.description;
283         if (this.property.value.type === "string" && typeof text === "string")
284             text = "\"" + text + "\"";
286         this._editableDiv.setTextContentTruncatedIfNeeded(text, WebInspector.UIString("<string is too large to edit>"));
287         var originalContent = this._editableDiv.textContent;
289         this.valueElement.classList.add("hidden");
291         // Lie about our children to prevent expanding on double click and to collapse subproperties.
292         this.setExpandable(false);
293         this.listItemElement.classList.add("editing-sub-part");
295         this._prompt = new WebInspector.ObjectPropertyPrompt();
297         var proxyElement = this._prompt.attachAndStartEditing(this._editableDiv, this._editingCommitted.bind(this, originalContent));
298         this.listItemElement.getComponentSelection().setBaseAndExtent(this._editableDiv, 0, this._editableDiv, 1);
299         proxyElement.addEventListener("keydown", this._promptKeyDown.bind(this, originalContent), false);
300     },
302     _editingEnded: function()
303     {
304        this._prompt.detach();
305        delete this._prompt;
306        this._editableDiv.remove();
307        this.setExpandable(this.property.value.hasChildren && !this.property.wasThrown);
308        this.listItemElement.scrollLeft = 0;
309        this.listItemElement.classList.remove("editing-sub-part");
310     },
312     _editingCancelled: function()
313     {
314        this.valueElement.classList.remove("hidden");
315        this._editingEnded();
316     },
318     /**
319      * @param {string} originalContent
320      */
321     _editingCommitted: function(originalContent)
322     {
323        var userInput = this._prompt.text();
324        if (userInput === originalContent) {
325            this._editingCancelled(); // nothing changed, so cancel
326            return;
327        }
329        this._editingEnded();
330        this._applyExpression(userInput);
331     },
333     /**
334      * @param {string} originalContent
335      * @param {!Event} event
336      */
337     _promptKeyDown: function(originalContent, event)
338     {
339         if (isEnterKey(event)) {
340             event.consume(true);
341             this._editingCommitted(originalContent);
342             return;
343         }
344         if (event.keyIdentifier === "U+001B") { // Esc
345             event.consume();
346             this._editingCancelled();
347             return;
348         }
349     },
351     /**
352      * @param {string} expression
353      */
354     _applyExpression: function(expression)
355     {
356         var property = WebInspector.RemoteObject.toCallArgument(this.property.symbol || this.property.name);
357         expression = expression.trim();
358         if (expression)
359             this.property.parentObject.setPropertyValue(property, expression, callback.bind(this));
360         else
361             this.property.parentObject.deleteProperty(property, callback.bind(this));
363         /**
364          * @param {?Protocol.Error} error
365          * @this {WebInspector.ObjectPropertyTreeElement}
366          */
367         function callback(error)
368         {
369             if (error) {
370                 this.update();
371                 return;
372             }
374             if (!expression) {
375                 // The property was deleted, so remove this tree element.
376                 this.parent.removeChild(this);
377             } else {
378                 // Call updateSiblings since their value might be based on the value that just changed.
379                 var parent = this.parent;
380                 parent.invalidateChildren();
381                 parent.expand();
382             }
383         };
384     },
386     /**
387      * @return {string|undefined}
388      */
389     propertyPath: function()
390     {
391         if (this._cachedPropertyPath)
392             return this._cachedPropertyPath;
394         var current = this;
395         var result;
397         do {
398             if (current.property) {
399                 if (result)
400                     result = current.property.name + "." + result;
401                 else
402                     result = current.property.name;
403             }
404             current = current.parent;
405         } while (current && !current.root);
407         this._cachedPropertyPath = result;
408         return result;
409     },
411     /**
412      * @param {?WebInspector.RemoteObject} result
413      * @param {boolean=} wasThrown
414      */
415     _onInvokeGetterClick: function(result, wasThrown)
416     {
417         if (!result)
418             return;
419         this.property.value = result;
420         this.property.wasThrown = wasThrown;
422         this.update();
423         this.invalidateChildren();
424     },
426     __proto__: TreeElement.prototype
430  * @param {!TreeElement} treeElement
431  * @param {!WebInspector.RemoteObject} value
432  * @param {boolean} skipProto
433  * @param {?string=} emptyPlaceholder
434  * @param {boolean=} flattenProtoChain
435  * @param {!Array.<!WebInspector.RemoteObjectProperty>=} extraProperties
436  */
437 WebInspector.ObjectPropertyTreeElement._populate = function(treeElement, value, skipProto, emptyPlaceholder, flattenProtoChain, extraProperties)
439     if (value.arrayLength() > WebInspector.ObjectPropertiesSection._arrayLoadThreshold) {
440         treeElement.removeChildren();
441         WebInspector.ArrayGroupingTreeElement._populateArray(treeElement, value, 0, value.arrayLength() - 1);
442         return;
443     }
445     /**
446      * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
447      * @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties
448      */
449     function callback(properties, internalProperties)
450     {
451         treeElement.removeChildren();
452         if (!properties)
453             return;
455         extraProperties = extraProperties || [];
456         for (var i = 0; i < extraProperties.length; ++i)
457             properties.push(extraProperties[i]);
459         WebInspector.ObjectPropertyTreeElement.populateWithProperties(treeElement, properties, internalProperties,
460             skipProto, value, emptyPlaceholder);
461     }
463     if (flattenProtoChain)
464         value.getAllProperties(false, callback);
465     else
466         WebInspector.RemoteObject.loadFromObjectPerProto(value, callback);
470  * @param {!TreeElement} treeNode
471  * @param {!Array.<!WebInspector.RemoteObjectProperty>} properties
472  * @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties
473  * @param {boolean} skipProto
474  * @param {?WebInspector.RemoteObject} value
475  * @param {?string=} emptyPlaceholder
476  */
477 WebInspector.ObjectPropertyTreeElement.populateWithProperties = function(treeNode, properties, internalProperties, skipProto, value, emptyPlaceholder) {
478     properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
480     for (var i = 0; i < properties.length; ++i) {
481         var property = properties[i];
482         if (skipProto && property.name === "__proto__")
483             continue;
484         if (property.isAccessorProperty()) {
485             if (property.name !== "__proto__" && property.getter) {
486                 property.parentObject = value;
487                 treeNode.appendChild(new WebInspector.ObjectPropertyTreeElement(property));
488             }
489             if (property.isOwn) {
490                 if (property.getter) {
491                     var getterProperty = new WebInspector.RemoteObjectProperty("get " + property.name, property.getter);
492                     getterProperty.parentObject = value;
493                     treeNode.appendChild(new WebInspector.ObjectPropertyTreeElement(getterProperty));
494                 }
495                 if (property.setter) {
496                     var setterProperty = new WebInspector.RemoteObjectProperty("set " + property.name, property.setter);
497                     setterProperty.parentObject = value;
498                     treeNode.appendChild(new WebInspector.ObjectPropertyTreeElement(setterProperty));
499                 }
500             }
501         } else {
502             property.parentObject = value;
503             treeNode.appendChild(new WebInspector.ObjectPropertyTreeElement(property));
504         }
505     }
506     if (internalProperties) {
507         for (var i = 0; i < internalProperties.length; i++) {
508             internalProperties[i].parentObject = value;
509             treeNode.appendChild(new WebInspector.ObjectPropertyTreeElement(internalProperties[i]));
510         }
511     }
512     if (value && value.type === "function") {
513         // Whether function has TargetFunction internal property.
514         // This is a simple way to tell that the function is actually a bound function (we are not told).
515         // Bound function never has inner scope and doesn't need corresponding UI node.
516         var hasTargetFunction = false;
518         if (internalProperties) {
519             for (var i = 0; i < internalProperties.length; i++) {
520                 if (internalProperties[i].name == "[[TargetFunction]]") {
521                     hasTargetFunction = true;
522                     break;
523                 }
524             }
525         }
526         if (!hasTargetFunction)
527             treeNode.appendChild(new WebInspector.FunctionScopeMainTreeElement(value));
528     }
529     if (value && value.type === "object" && (value.subtype === "map" || value.subtype === "set" || value.subtype === "iterator"))
530         treeNode.appendChild(new WebInspector.CollectionEntriesMainTreeElement(value));
532     WebInspector.ObjectPropertyTreeElement._appendEmptyPlaceholderIfNeeded(treeNode, emptyPlaceholder);
536  * @param {!TreeElement} treeNode
537  * @param {?string=} emptyPlaceholder
538  */
539 WebInspector.ObjectPropertyTreeElement._appendEmptyPlaceholderIfNeeded = function(treeNode, emptyPlaceholder)
541     if (treeNode.childCount())
542         return;
543     var title = createElementWithClass("div", "info");
544     title.textContent = emptyPlaceholder || WebInspector.UIString("No Properties");
545     var infoElement = new TreeElement(title);
546     treeNode.appendChild(infoElement);
550  * @param {?WebInspector.RemoteObject} object
551  * @param {!Array.<string>} propertyPath
552  * @param {function(?WebInspector.RemoteObject, boolean=)} callback
553  * @return {!Element}
554  */
555 WebInspector.ObjectPropertyTreeElement.createRemoteObjectAccessorPropertySpan = function(object, propertyPath, callback)
557     var rootElement = createElement("span");
558     var element = rootElement.createChild("span");
559     element.textContent = WebInspector.UIString("(...)");
560     if (!object)
561         return rootElement;
562     element.classList.add("object-value-calculate-value-button");
563     element.title = WebInspector.UIString("Invoke property getter");
564     element.addEventListener("click", onInvokeGetterClick, false);
566     function onInvokeGetterClick(event)
567     {
568         event.consume();
569         object.getProperty(propertyPath, callback);
570     }
572     return rootElement;
576  * @constructor
577  * @extends {TreeElement}
578  * @param {!WebInspector.RemoteObject} remoteObject
579  */
580 WebInspector.FunctionScopeMainTreeElement = function(remoteObject)
582     TreeElement.call(this, "<function scope>", true);
583     this.toggleOnClick = true;
584     this.selectable = false;
585     this._remoteObject = remoteObject;
588 WebInspector.FunctionScopeMainTreeElement.prototype = {
589     onpopulate: function()
590     {
591         /**
592          * @param {?WebInspector.DebuggerModel.FunctionDetails} response
593          * @this {WebInspector.FunctionScopeMainTreeElement}
594          */
595         function didGetDetails(response)
596         {
597             if (!response)
598                 return;
599             this.removeChildren();
601             var scopeChain = response.scopeChain || [];
602             for (var i = 0; i < scopeChain.length; ++i) {
603                 var scope = scopeChain[i];
604                 var title = null;
605                 var isTrueObject = false;
607                 switch (scope.type) {
608                 case DebuggerAgent.ScopeType.Local:
609                     // Not really expecting this scope type here.
610                     title = WebInspector.UIString("Local");
611                     break;
612                 case DebuggerAgent.ScopeType.Closure:
613                     title = WebInspector.UIString("Closure");
614                     break;
615                 case DebuggerAgent.ScopeType.Catch:
616                     title = WebInspector.UIString("Catch");
617                     break;
618                 case DebuggerAgent.ScopeType.Block:
619                     title = WebInspector.UIString("Block");
620                     break;
621                 case DebuggerAgent.ScopeType.Script:
622                     title = WebInspector.UIString("Script");
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                 }
637                 var runtimeModel = this._remoteObject.target().runtimeModel;
638                 if (isTrueObject) {
639                     var remoteObject = runtimeModel.createRemoteObject(scope.object);
640                     var property = new WebInspector.RemoteObjectProperty(title, remoteObject);
641                     property.writable = false;
642                     property.parentObject = null;
643                     this.appendChild(new WebInspector.ObjectPropertyTreeElement(property));
644                 } else {
645                     var scopeRef = new WebInspector.ScopeRef(i, undefined, this._remoteObject.objectId);
646                     var remoteObject = runtimeModel.createScopeRemoteObject(scope.object, scopeRef);
647                     var scopeTreeElement = new WebInspector.ScopeTreeElement(title, remoteObject);
648                     this.appendChild(scopeTreeElement);
649                 }
650             }
652             WebInspector.ObjectPropertyTreeElement._appendEmptyPlaceholderIfNeeded(this, WebInspector.UIString("No Scopes"));
653         }
655         this._remoteObject.functionDetails(didGetDetails.bind(this));
656     },
658     __proto__: TreeElement.prototype
662  * @constructor
663  * @extends {TreeElement}
664  * @param {!WebInspector.RemoteObject} remoteObject
665  */
666 WebInspector.CollectionEntriesMainTreeElement = function(remoteObject)
668     TreeElement.call(this, "<entries>", true);
669     this.toggleOnClick = true;
670     this.selectable = false;
671     this._remoteObject = remoteObject;
672     this.expand();
675 WebInspector.CollectionEntriesMainTreeElement.prototype = {
676     onpopulate: function()
677     {
678         /**
679          * @param {?Array.<!DebuggerAgent.CollectionEntry>} entries
680          * @this {WebInspector.CollectionEntriesMainTreeElement}
681          */
682         function didGetCollectionEntries(entries)
683         {
684             if (!entries)
685                 return;
686             this.removeChildren();
688             var entriesLocalObject = [];
689             var runtimeModel = this._remoteObject.target().runtimeModel;
690             for (var i = 0; i < entries.length; ++i) {
691                 var entry = entries[i];
692                 if (entry.key) {
693                     entriesLocalObject.push(new WebInspector.MapEntryLocalJSONObject({
694                         key: runtimeModel.createRemoteObject(entry.key),
695                         value: runtimeModel.createRemoteObject(entry.value)
696                     }));
697                 } else {
698                     entriesLocalObject.push(runtimeModel.createRemoteObject(entry.value));
699                 }
700             }
701             WebInspector.ObjectPropertyTreeElement._populate(this, WebInspector.RemoteObject.fromLocalObject(entriesLocalObject), true, WebInspector.UIString("No Entries"));
702             this.title = "<entries>[" + entriesLocalObject.length + "]";
703         }
705         this._remoteObject.collectionEntries(didGetCollectionEntries.bind(this));
706     },
708     __proto__: TreeElement.prototype
712  * @constructor
713  * @extends {TreeElement}
714  * @param {string} title
715  * @param {!WebInspector.RemoteObject} remoteObject
716  */
717 WebInspector.ScopeTreeElement = function(title, remoteObject)
719     TreeElement.call(this, title, true);
720     this.toggleOnClick = true;
721     this.selectable = false;
722     this._remoteObject = remoteObject;
725 WebInspector.ScopeTreeElement.prototype = {
726     onpopulate: function()
727     {
728         WebInspector.ObjectPropertyTreeElement._populate(this, this._remoteObject, false);
729     },
731     __proto__: TreeElement.prototype
735  * @constructor
736  * @extends {TreeElement}
737  * @param {!WebInspector.RemoteObject} object
738  * @param {number} fromIndex
739  * @param {number} toIndex
740  * @param {number} propertyCount
741  */
742 WebInspector.ArrayGroupingTreeElement = function(object, fromIndex, toIndex, propertyCount)
744     TreeElement.call(this, String.sprintf("[%d \u2026 %d]", fromIndex, toIndex), true);
745     this.toggleOnClick = true;
746     this.selectable = false;
747     this._fromIndex = fromIndex;
748     this._toIndex = toIndex;
749     this._object = object;
750     this._readOnly = true;
751     this._propertyCount = propertyCount;
754 WebInspector.ArrayGroupingTreeElement._bucketThreshold = 100;
755 WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold = 250000;
756 WebInspector.ArrayGroupingTreeElement._getOwnPropertyNamesThreshold = 500000;
759  * @param {!TreeElement} treeNode
760  * @param {!WebInspector.RemoteObject} object
761  * @param {number} fromIndex
762  * @param {number} toIndex
763  */
764 WebInspector.ArrayGroupingTreeElement._populateArray = function(treeNode, object, fromIndex, toIndex)
766     WebInspector.ArrayGroupingTreeElement._populateRanges(treeNode, object, fromIndex, toIndex, true);
770  * @param {!TreeElement} treeNode
771  * @param {!WebInspector.RemoteObject} object
772  * @param {number} fromIndex
773  * @param {number} toIndex
774  * @param {boolean} topLevel
775  * @this {WebInspector.ArrayGroupingTreeElement}
776  */
777 WebInspector.ArrayGroupingTreeElement._populateRanges = function(treeNode, object, fromIndex, toIndex, topLevel)
779     object.callFunctionJSON(packRanges, [
780         { value: fromIndex },
781         { value: toIndex },
782         { value: WebInspector.ArrayGroupingTreeElement._bucketThreshold },
783         { value: WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold },
784         { value: WebInspector.ArrayGroupingTreeElement._getOwnPropertyNamesThreshold }
785     ], callback);
787     /**
788      * Note: must declare params as optional.
789      * @param {number=} fromIndex
790      * @param {number=} toIndex
791      * @param {number=} bucketThreshold
792      * @param {number=} sparseIterationThreshold
793      * @param {number=} getOwnPropertyNamesThreshold
794      * @suppressReceiverCheck
795      * @this {Object}
796      */
797     function packRanges(fromIndex, toIndex, bucketThreshold, sparseIterationThreshold, getOwnPropertyNamesThreshold)
798     {
799         var ownPropertyNames = null;
800         var consecutiveRange = (toIndex - fromIndex >= sparseIterationThreshold) && ArrayBuffer.isView(this);
801         var skipGetOwnPropertyNames = consecutiveRange && (toIndex - fromIndex >= getOwnPropertyNamesThreshold);
803         function* arrayIndexes(object)
804         {
805             if (toIndex - fromIndex < sparseIterationThreshold) {
806                 for (var i = fromIndex; i <= toIndex; ++i) {
807                     if (i in object)
808                         yield i;
809                 }
810             } else {
811                 ownPropertyNames = ownPropertyNames || Object.getOwnPropertyNames(object);
812                 for (var i = 0; i < ownPropertyNames.length; ++i) {
813                     var name = ownPropertyNames[i];
814                     var index = name >>> 0;
815                     if (("" + index) === name && fromIndex <= index && index <= toIndex)
816                         yield index;
817                 }
818             }
819         }
821         var count = 0;
822         if (consecutiveRange) {
823             count = toIndex - fromIndex + 1;
824         } else {
825             for (var i of arrayIndexes(this))
826                 ++count;
827         }
829         var bucketSize = count;
830         if (count <= bucketThreshold)
831             bucketSize = count;
832         else
833             bucketSize = Math.pow(bucketThreshold, Math.ceil(Math.log(count) / Math.log(bucketThreshold)) - 1);
835         var ranges = [];
836         if (consecutiveRange) {
837             for (var i = fromIndex; i <= toIndex; i += bucketSize) {
838                 var groupStart = i;
839                 var groupEnd = groupStart + bucketSize - 1;
840                 if (groupEnd > toIndex)
841                     groupEnd = toIndex;
842                 ranges.push([groupStart, groupEnd, groupEnd - groupStart + 1]);
843             }
844         } else {
845             count = 0;
846             var groupStart = -1;
847             var groupEnd = 0;
848             for (var i of arrayIndexes(this)) {
849                 if (groupStart === -1)
850                     groupStart = i;
851                 groupEnd = i;
852                 if (++count === bucketSize) {
853                     ranges.push([groupStart, groupEnd, count]);
854                     count = 0;
855                     groupStart = -1;
856                 }
857             }
858             if (count > 0)
859                 ranges.push([groupStart, groupEnd, count]);
860         }
862         return { ranges: ranges, skipGetOwnPropertyNames: skipGetOwnPropertyNames };
863     }
865     function callback(result)
866     {
867         if (!result)
868             return;
869         var ranges = /** @type {!Array.<!Array.<number>>} */ (result.ranges);
870         if (ranges.length == 1) {
871             WebInspector.ArrayGroupingTreeElement._populateAsFragment(treeNode, object, ranges[0][0], ranges[0][1]);
872         } else {
873             for (var i = 0; i < ranges.length; ++i) {
874                 var fromIndex = ranges[i][0];
875                 var toIndex = ranges[i][1];
876                 var count = ranges[i][2];
877                 if (fromIndex == toIndex)
878                     WebInspector.ArrayGroupingTreeElement._populateAsFragment(treeNode, object, fromIndex, toIndex);
879                 else
880                     treeNode.appendChild(new WebInspector.ArrayGroupingTreeElement(object, fromIndex, toIndex, count));
881             }
882         }
883         if (topLevel)
884             WebInspector.ArrayGroupingTreeElement._populateNonIndexProperties(treeNode, object, result.skipGetOwnPropertyNames);
885     }
889  * @param {!TreeElement} treeNode
890  * @param {!WebInspector.RemoteObject} object
891  * @param {number} fromIndex
892  * @param {number} toIndex
893  * @this {WebInspector.ArrayGroupingTreeElement}
894  */
895 WebInspector.ArrayGroupingTreeElement._populateAsFragment = function(treeNode, object, fromIndex, toIndex)
897     object.callFunction(buildArrayFragment, [{value: fromIndex}, {value: toIndex}, {value: WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold}], processArrayFragment.bind(this));
899     /**
900      * @suppressReceiverCheck
901      * @this {Object}
902      * @param {number=} fromIndex // must declare optional
903      * @param {number=} toIndex // must declare optional
904      * @param {number=} sparseIterationThreshold // must declare optional
905      */
906     function buildArrayFragment(fromIndex, toIndex, sparseIterationThreshold)
907     {
908         var result = Object.create(null);
909         if (toIndex - fromIndex < sparseIterationThreshold) {
910             for (var i = fromIndex; i <= toIndex; ++i) {
911                 if (i in this)
912                     result[i] = this[i];
913             }
914         } else {
915             var ownPropertyNames = Object.getOwnPropertyNames(this);
916             for (var i = 0; i < ownPropertyNames.length; ++i) {
917                 var name = ownPropertyNames[i];
918                 var index = name >>> 0;
919                 if (String(index) === name && fromIndex <= index && index <= toIndex)
920                     result[index] = this[index];
921             }
922         }
923         return result;
924     }
926     /**
927      * @param {?WebInspector.RemoteObject} arrayFragment
928      * @param {boolean=} wasThrown
929      * @this {WebInspector.ArrayGroupingTreeElement}
930      */
931     function processArrayFragment(arrayFragment, wasThrown)
932     {
933         if (!arrayFragment || wasThrown)
934             return;
935         arrayFragment.getAllProperties(false, processProperties.bind(this));
936     }
938     /** @this {WebInspector.ArrayGroupingTreeElement} */
939     function processProperties(properties, internalProperties)
940     {
941         if (!properties)
942             return;
944         properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
945         for (var i = 0; i < properties.length; ++i) {
946             properties[i].parentObject = this._object;
947             var childTreeElement = new WebInspector.ObjectPropertyTreeElement(properties[i]);
948             childTreeElement._readOnly = true;
949             treeNode.appendChild(childTreeElement);
950         }
951     }
955  * @param {!TreeElement} treeNode
956  * @param {!WebInspector.RemoteObject} object
957  * @param {boolean} skipGetOwnPropertyNames
958  * @this {WebInspector.ArrayGroupingTreeElement}
959  */
960 WebInspector.ArrayGroupingTreeElement._populateNonIndexProperties = function(treeNode, object, skipGetOwnPropertyNames)
962     object.callFunction(buildObjectFragment, [{value: skipGetOwnPropertyNames}], processObjectFragment.bind(this));
964     /**
965      * @param {boolean=} skipGetOwnPropertyNames
966      * @suppressReceiverCheck
967      * @this {Object}
968      */
969     function buildObjectFragment(skipGetOwnPropertyNames)
970     {
971         var result = { __proto__: this.__proto__ };
972         if (skipGetOwnPropertyNames)
973             return result;
974         var names = Object.getOwnPropertyNames(this);
975         for (var i = 0; i < names.length; ++i) {
976             var name = names[i];
977             // Array index check according to the ES5-15.4.
978             if (String(name >>> 0) === name && name >>> 0 !== 0xffffffff)
979                 continue;
980             var descriptor = Object.getOwnPropertyDescriptor(this, name);
981             if (descriptor)
982                 Object.defineProperty(result, name, descriptor);
983         }
984         return result;
985     }
987     /**
988      * @param {?WebInspector.RemoteObject} arrayFragment
989      * @param {boolean=} wasThrown
990      * @this {WebInspector.ArrayGroupingTreeElement}
991      */
992     function processObjectFragment(arrayFragment, wasThrown)
993     {
994         if (!arrayFragment || wasThrown)
995             return;
996         arrayFragment.getOwnProperties(processProperties.bind(this));
997     }
999     /**
1000      * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
1001      * @param {?Array.<!WebInspector.RemoteObjectProperty>=} internalProperties
1002      * @this {WebInspector.ArrayGroupingTreeElement}
1003      */
1004     function processProperties(properties, internalProperties)
1005     {
1006         if (!properties)
1007             return;
1008         properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
1009         for (var i = 0; i < properties.length; ++i) {
1010             properties[i].parentObject = this._object;
1011             var childTreeElement = new WebInspector.ObjectPropertyTreeElement(properties[i]);
1012             childTreeElement._readOnly = true;
1013             treeNode.appendChild(childTreeElement);
1014         }
1015     }
1018 WebInspector.ArrayGroupingTreeElement.prototype = {
1019     onpopulate: function()
1020     {
1021         if (this._propertyCount >= WebInspector.ArrayGroupingTreeElement._bucketThreshold) {
1022             WebInspector.ArrayGroupingTreeElement._populateRanges(this, this._object, this._fromIndex, this._toIndex, false);
1023             return;
1024         }
1025         WebInspector.ArrayGroupingTreeElement._populateAsFragment(this, this._object, this._fromIndex, this._toIndex);
1026     },
1028     onattach: function()
1029     {
1030         this.listItemElement.classList.add("object-properties-section-name");
1031     },
1033     __proto__: TreeElement.prototype
1037  * @constructor
1038  * @extends {WebInspector.TextPrompt}
1039  */
1040 WebInspector.ObjectPropertyPrompt = function()
1042     WebInspector.TextPrompt.call(this, WebInspector.ExecutionContextSelector.completionsForTextPromptInCurrentContext);
1043     this.setSuggestBoxEnabled(true);
1046 WebInspector.ObjectPropertyPrompt.prototype = {
1047     __proto__: WebInspector.TextPrompt.prototype
1051  * @param {?string} name
1052  * @return {!Element}
1053  */
1054 WebInspector.ObjectPropertiesSection.createNameElement = function(name)
1056     var nameElement = createElementWithClass("span", "name");
1057     if (/^\s|\s$|^$|\n/.test(name))
1058         nameElement.createTextChildren("\"", name.replace(/\n/g, "\u21B5"), "\"");
1059     else
1060         nameElement.textContent = name;
1061     return nameElement;
1065  * @param {?string=} description
1066  * @return {string} valueText
1067  */
1068 WebInspector.ObjectPropertiesSection.valueTextForFunctionDescription = function(description)
1070     var matches = /function\s([^)]*)/.exec(description);
1071     if (!matches) {
1072         // process shorthand methods
1073         matches = /[^(]*(\([^)]*)/.exec(description);
1074     }
1075     var match = matches ? matches[1] : null;
1076     return match ? match.replace(/\n/g, " ") + ")" : (description || "");
1080  * @param {!WebInspector.RemoteObject} value
1081  * @param {boolean} wasThrown
1082  * @param {!Element=} parentElement
1083  * @return {!Element}
1084  */
1085 WebInspector.ObjectPropertiesSection.createValueElementWithCustomSupport = function(value, wasThrown, parentElement)
1087     if (value.customPreview()) {
1088         var result = (new WebInspector.CustomPreviewComponent(value)).element;
1089         result.classList.add("object-properties-section-custom-section");
1090         return result
1091     }
1092     return WebInspector.ObjectPropertiesSection.createValueElement(value, wasThrown, parentElement);
1096  * @param {!WebInspector.RemoteObject} value
1097  * @param {boolean} wasThrown
1098  * @param {!Element=} parentElement
1099  * @return {!Element}
1100  */
1101 WebInspector.ObjectPropertiesSection.createValueElement = function(value, wasThrown, parentElement)
1103     var valueElement = createElementWithClass("span", "value");
1104     var type = value.type;
1105     var subtype = value.subtype;
1106     var description = value.description;
1107     var prefix;
1108     var valueText;
1109     var suffix;
1110     if (wasThrown) {
1111         prefix = "[Exception: ";
1112         valueText = description;
1113         suffix = "]";
1114     } else if (type === "string" && typeof description === "string") {
1115         // Render \n as a nice unicode cr symbol.
1116         prefix = "\"";
1117         valueText = description.replace(/\n/g, "\u21B5");
1118         suffix = "\"";
1119     } else if (type === "function") {
1120         valueText = WebInspector.ObjectPropertiesSection.valueTextForFunctionDescription(description);
1121     } else if (type !== "object" || subtype !== "node") {
1122         valueText = description;
1123     }
1124     if (type !== "number" || valueText.indexOf("e") === -1) {
1125         valueElement.setTextContentTruncatedIfNeeded(valueText || "");
1126         if (prefix)
1127             valueElement.insertBefore(createTextNode(prefix), valueElement.firstChild);
1128         if (suffix)
1129             valueElement.createTextChild(suffix);
1130     } else {
1131         var numberParts = valueText.split("e");
1132         var mantissa = valueElement.createChild("span", "object-value-scientific-notation-mantissa");
1133         mantissa.textContent = numberParts[0];
1134         var exponent = valueElement.createChild("span", "object-value-scientific-notation-exponent");
1135         exponent.textContent = "e" + numberParts[1];
1136         valueElement.classList.add("object-value-scientific-notation-number");
1137         if (parentElement)  // FIXME: do it in the caller.
1138             parentElement.classList.add("hbox");
1139     }
1141     if (wasThrown)
1142         valueElement.classList.add("error");
1143     if (subtype || type)
1144         valueElement.classList.add("object-value-" + (subtype || type));
1146     if (type === "object" && subtype === "node" && description) {
1147         WebInspector.DOMPresentationUtils.createSpansForNodeTitle(valueElement, description);
1148         valueElement.addEventListener("click", mouseClick, false);
1149         valueElement.addEventListener("mousemove", mouseMove, false);
1150         valueElement.addEventListener("mouseleave", mouseLeave, false);
1151     } else {
1152         valueElement.title = description || "";
1153     }
1155     function mouseMove()
1156     {
1157         WebInspector.DOMModel.highlightObjectAsDOMNode(value);
1158     }
1160     function mouseLeave()
1161     {
1162         WebInspector.DOMModel.hideDOMNodeHighlight();
1163     }
1165     /**
1166      * @param {!Event} event
1167      */
1168     function mouseClick(event)
1169     {
1170         WebInspector.Revealer.reveal(value);
1171         event.consume(true);
1172     }
1174     return valueElement;
1178  * @param {!WebInspector.RemoteObject} func
1179  * @param {!Element} element
1180  * @param {boolean} linkify
1181  * @param {boolean=} includePreview
1182  */
1183 WebInspector.ObjectPropertiesSection.formatObjectAsFunction = function(func, element, linkify, includePreview)
1185     func.functionDetails(didGetDetails);
1187     /**
1188      * @param {?WebInspector.DebuggerModel.FunctionDetails} response
1189      */
1190     function didGetDetails(response)
1191     {
1192         if (!response) {
1193             var valueText = WebInspector.ObjectPropertiesSection.valueTextForFunctionDescription(func.description);
1194             element.createTextChild(valueText);
1195             return;
1196         }
1198         if (linkify && response && response.location) {
1199             var anchor = createElement("span");
1200             element.classList.add("linkified");
1201             element.appendChild(anchor);
1202             element.addEventListener("click", WebInspector.Revealer.reveal.bind(WebInspector.Revealer, response.location, undefined));
1203             element = anchor;
1204         }
1206         var text = func.description.substring(0, 200);
1207         if (includePreview) {
1208             element.textContent = text.replace(/^function /, "") + (func.description.length > 200 ? "\u2026" : "");
1209             return;
1210         }
1212         // Now parse description and get the real params and title.
1213         self.runtime.instancePromise(WebInspector.TokenizerFactory).then(processTokens);
1215         var params = null;
1216         var functionName = response ? response.functionName : "";
1218         /**
1219          * @param {!WebInspector.TokenizerFactory} tokenizerFactory
1220          */
1221         function processTokens(tokenizerFactory)
1222         {
1223             var tokenize = tokenizerFactory.createTokenizer("text/javascript");
1224             tokenize(text, processToken);
1225             element.textContent = (functionName || "anonymous") + "(" + (params || []).join(", ") + ")";
1226         }
1228         var doneProcessing = false;
1230         /**
1231          * @param {string} token
1232          * @param {?string} tokenType
1233          * @param {number} column
1234          * @param {number} newColumn
1235          */
1236         function processToken(token, tokenType, column, newColumn)
1237         {
1238             if (!params && tokenType === "js-variable" && !functionName)
1239                 functionName = token;
1240             doneProcessing = doneProcessing || token === ")";
1241             if (doneProcessing)
1242                 return;
1243             if (token === "(") {
1244                 params = [];
1245                 return;
1246             }
1247             if (params && tokenType === "js-def")
1248                 params.push(token);
1249         }
1250     }