2 * Copyright (C) 2015 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
26 WebInspector.ObjectTreeView = class ObjectTreeView extends WebInspector.Object
28 constructor(object, mode, propertyPath, forceExpanding)
32 console.assert(object instanceof WebInspector.RemoteObject);
33 console.assert(!propertyPath || propertyPath instanceof WebInspector.PropertyPath);
35 this._object = object;
36 this._mode = mode || WebInspector.ObjectTreeView.defaultModeForObject(object);
37 this._propertyPath = propertyPath || new WebInspector.PropertyPath(this._object, "this");
38 this._expanded = false;
39 this._hasLosslessPreview = false;
41 // If ObjectTree is used outside of the console, we do not know when to release
42 // WeakMap entries. Currently collapse would work. For the console, we can just
43 // listen for console clear events. Currently all ObjectTrees are in the console.
44 this._inConsole = true;
46 // Always force expanding for classes.
47 if (this._object.isClass())
48 forceExpanding = true;
50 this._element = document.createElement("div");
51 this._element.className = "object-tree";
53 if (this._object.preview) {
54 this._previewView = new WebInspector.ObjectPreviewView(this._object.preview);
55 this._previewView.element.addEventListener("click", this._handlePreviewOrTitleElementClick.bind(this));
56 this._element.appendChild(this._previewView.element);
58 if (this._previewView.lossless && !this._propertyPath.parent && !forceExpanding) {
59 this._hasLosslessPreview = true;
60 this.element.classList.add("lossless-preview");
63 this._titleElement = document.createElement("span");
64 this._titleElement.className = "title";
65 this._titleElement.appendChild(WebInspector.FormattedValue.createElementForRemoteObject(this._object));
66 this._titleElement.addEventListener("click", this._handlePreviewOrTitleElementClick.bind(this));
67 this._element.appendChild(this._titleElement);
70 this._outlineElement = document.createElement("ol");
71 this._outlineElement.className = "object-tree-outline";
72 this._outline = new WebInspector.TreeOutline(this._outlineElement);
73 this._element.appendChild(this._outlineElement);
75 // FIXME: Support editable ObjectTrees.
80 static defaultModeForObject(object)
82 if (object.subtype === "class")
83 return WebInspector.ObjectTreeView.Mode.ClassAPI;
85 return WebInspector.ObjectTreeView.Mode.Properties;
88 static createEmptyMessageElement(message)
90 var emptyMessageElement = document.createElement("div");
91 emptyMessageElement.className = "empty-message";
92 emptyMessageElement.textContent = message;
93 return emptyMessageElement;
96 static comparePropertyDescriptors(propertyA, propertyB)
98 var a = propertyA.name;
99 var b = propertyB.name;
101 // Put __proto__ at the bottom.
102 if (a === "__proto__")
104 if (b === "__proto__")
107 // Put Internal properties at the top.
108 if (propertyA.isInternalProperty && !propertyB.isInternalProperty)
110 if (propertyB.isInternalProperty && !propertyA.isInternalProperty)
113 // Put Symbol properties at the bottom.
114 if (propertyA.symbol && !propertyB.symbol)
116 if (propertyB.symbol && !propertyA.symbol)
119 // Symbol properties may have the same description string but be different objects.
124 var chunk = /^\d+|^\D+/;
125 var chunka, chunkb, anum, bnum;
131 chunka = a.match(chunk)[0];
132 chunkb = b.match(chunk)[0];
133 anum = !isNaN(chunka);
134 bnum = !isNaN(chunkb);
140 diff = chunka - chunkb;
141 if (diff === 0 && chunka.length !== chunkb.length) {
142 if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case)
143 return chunka.length - chunkb.length;
145 return chunkb.length - chunka.length;
147 } else if (chunka !== chunkb)
148 return (chunka < chunkb) ? -1 : 1;
149 a = a.substring(chunka.length);
150 b = b.substring(chunkb.length);
164 return this._element;
169 return this._outline;
174 return this._expanded;
182 this._expanded = true;
183 this._element.classList.add("expanded");
185 if (this._previewView)
186 this._previewView.showTitle();
188 this._trackWeakEntries();
198 this._expanded = false;
199 this._element.classList.remove("expanded");
201 if (this._previewView)
202 this._previewView.showPreview();
204 this._untrackWeakEntries();
209 this._inConsole = false;
211 this._element.classList.add("properties-only");
214 appendTitleSuffix(suffixElement)
216 if (this._previewView)
217 this._previewView.element.appendChild(suffixElement);
219 this._titleElement.appendChild(suffixElement);
222 appendExtraPropertyDescriptor(propertyDescriptor)
224 if (!this._extraProperties)
225 this._extraProperties = [];
227 this._extraProperties.push(propertyDescriptor);
234 if (this._object.isCollectionType() && this._mode === WebInspector.ObjectTreeView.Mode.Properties)
235 this._object.getCollectionEntries(0, 100, this._updateChildren.bind(this, this._updateEntries));
236 else if (this._object.isClass())
237 this._object.classPrototype.getDisplayablePropertyDescriptors(this._updateChildren.bind(this, this._updateProperties));
239 this._object.getDisplayablePropertyDescriptors(this._updateChildren.bind(this, this._updateProperties));
244 _updateChildren(handler, list)
246 this._outline.removeChildren();
249 var errorMessageElement = WebInspector.ObjectTreeView.createEmptyMessageElement(WebInspector.UIString("Could not fetch properties. Object may no longer exist."));
250 this._outline.appendChild(new WebInspector.TreeElement(errorMessageElement, null, false));
254 handler.call(this, list, this._propertyPath);
257 _updateEntries(entries, propertyPath)
259 for (var entry of entries) {
261 this._outline.appendChild(new WebInspector.ObjectTreeMapKeyTreeElement(entry.key, propertyPath));
262 this._outline.appendChild(new WebInspector.ObjectTreeMapValueTreeElement(entry.value, propertyPath, entry.key));
264 this._outline.appendChild(new WebInspector.ObjectTreeSetIndexTreeElement(entry.value, propertyPath));
267 if (!this._outline.children.length) {
268 var emptyMessageElement = WebInspector.ObjectTreeView.createEmptyMessageElement(WebInspector.UIString("No Entries."));
269 this._outline.appendChild(new WebInspector.TreeElement(emptyMessageElement, null, false));
272 // Show the prototype so users can see the API.
273 this._object.getOwnPropertyDescriptor("__proto__", function(propertyDescriptor) {
274 if (propertyDescriptor)
275 this._outline.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, propertyPath, this._mode));
279 _updateProperties(properties, propertyPath)
281 if (this._extraProperties)
282 properties = properties.concat(this._extraProperties);
284 properties.sort(WebInspector.ObjectTreeView.comparePropertyDescriptors);
286 var isArray = this._object.isArray();
287 var isPropertyMode = this._mode === WebInspector.ObjectTreeView.Mode.Properties;
289 for (var propertyDescriptor of properties) {
290 if (isArray && isPropertyMode) {
291 if (propertyDescriptor.isIndexProperty())
292 this._outline.appendChild(new WebInspector.ObjectTreeArrayIndexTreeElement(propertyDescriptor, propertyPath));
293 else if (propertyDescriptor.name === "__proto__")
294 this._outline.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, propertyPath, this._mode));
296 this._outline.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, propertyPath, this._mode));
299 if (!this._outline.children.length) {
300 var emptyMessageElement = WebInspector.ObjectTreeView.createEmptyMessageElement(WebInspector.UIString("No Properties."));
301 this._outline.appendChild(new WebInspector.TreeElement(emptyMessageElement, null, false));
305 _handlePreviewOrTitleElementClick(event)
307 if (this._hasLosslessPreview)
315 event.stopPropagation();
320 if (this._trackingEntries)
323 if (!this._object.isWeakCollection())
326 this._trackingEntries = true;
328 if (this._inConsole) {
329 WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.Cleared, this._untrackWeakEntries, this);
330 WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.ActiveLogCleared, this._untrackWeakEntries, this);
331 WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.SessionStarted, this._untrackWeakEntries, this);
335 _untrackWeakEntries()
337 if (!this._trackingEntries)
340 if (!this._object.isWeakCollection())
343 this._trackingEntries = false;
345 this._object.releaseWeakCollectionEntries();
347 if (this._inConsole) {
348 WebInspector.logManager.removeEventListener(WebInspector.LogManager.Event.Cleared, this._untrackWeakEntries, this);
349 WebInspector.logManager.removeEventListener(WebInspector.LogManager.Event.ActiveLogCleared, this._untrackWeakEntries, this);
350 WebInspector.logManager.removeEventListener(WebInspector.LogManager.Event.SessionStarted, this._untrackWeakEntries, this);
353 // FIXME: This only tries to release weak entries if this object was a WeakMap.
354 // If there was a WeakMap expanded in a sub-object, we will never release those values.
355 // Should we attempt walking the entire tree and release weak collections?
359 WebInspector.ObjectTreeView.Mode = {
360 Properties: Symbol("object-tree-properties"), // Properties
361 PrototypeAPI: Symbol("object-tree-prototype-api"), // API view on a live object instance, so getters can be invoked.
362 ClassAPI: Symbol("object-tree-class-api"), // API view without an object instance, can not invoke getters.