Web Inspector: Save Console Evaluations into Command Line variables $1-$99 ($n)
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / ObjectTreeView.js
1 /*
2  * Copyright (C) 2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
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.
24  */
25
26 WebInspector.ObjectTreeView = function(object, mode, propertyPath, forceExpanding)
27 {
28     WebInspector.Object.call(this);
29
30     console.assert(object instanceof WebInspector.RemoteObject);
31     console.assert(!propertyPath || propertyPath instanceof WebInspector.PropertyPath);
32
33     this._object = object;
34     this._mode = mode || WebInspector.ObjectTreeView.Mode.Properties;
35     this._propertyPath = propertyPath || new WebInspector.PropertyPath(this._object, "this");
36     this._expanded = false;
37     this._hasLosslessPreview = false;
38
39     // If ObjectTree is used outside of the console, we do not know when to release
40     // WeakMap entries. Currently collapse would work. For the console, we can just
41     // listen for console clear events. Currently all ObjectTrees are in the console.
42     this._inConsole = true;
43
44     this._element = document.createElement("div");
45     this._element.className = "object-tree";
46
47     if (this._object.preview) {
48         this._previewView = new WebInspector.ObjectPreviewView(this._object.preview);
49         this._previewView.element.addEventListener("click", this._handlePreviewOrTitleElementClick.bind(this));
50         this._element.appendChild(this._previewView.element);
51
52         if (this._previewView.lossless && !forceExpanding) {
53             this._hasLosslessPreview = true;
54             this.element.classList.add("lossless-preview");
55         }
56     } else {
57         this._titleElement = document.createElement("span");
58         this._titleElement.className = "title";
59         this._titleElement.appendChild(WebInspector.FormattedValue.createElementForRemoteObject(this._object));
60         this._titleElement.addEventListener("click", this._handlePreviewOrTitleElementClick.bind(this));
61         this._element.appendChild(this._titleElement);
62     }
63
64     this._outlineElement = document.createElement("ol");
65     this._outlineElement.className = "object-tree-outline";
66     this._outline = new TreeOutline(this._outlineElement);
67     this._element.appendChild(this._outlineElement);
68
69     // FIXME: Support editable ObjectTrees.
70 };
71
72 WebInspector.ObjectTreeView.emptyMessageElement = function(message)
73 {
74     var emptyMessageElement = document.createElement("div");
75     emptyMessageElement.className = "empty-message";
76     emptyMessageElement.textContent = message;
77     return emptyMessageElement;
78 };
79
80 WebInspector.ObjectTreeView.Mode = {
81     Properties: Symbol("object-tree-properties"),
82     API: Symbol("object-tree-api"),
83 };
84
85 WebInspector.ObjectTreeView.ComparePropertyDescriptors = function(propertyA, propertyB)
86 {
87     var a = propertyA.name;
88     var b = propertyB.name;
89
90     // Put __proto__ at the bottom.
91     if (a === "__proto__")
92         return 1;
93     if (b === "__proto__")
94         return -1;
95
96     // Put internal properties at the top.
97     if (a.isInternalProperty && !b.isInternalProperty)
98         return -1;
99     if (b.isInternalProperty && !a.isInternalProperty)
100         return 1;
101
102     // if used elsewhere make sure to
103     //  - convert a and b to strings (not needed here, properties are all strings)
104     //  - check if a == b (not needed here, no two properties can be the same)
105
106     var diff = 0;
107     var chunk = /^\d+|^\D+/;
108     var chunka, chunkb, anum, bnum;
109     while (diff === 0) {
110         if (!a && b)
111             return -1;
112         if (!b && a)
113             return 1;
114         chunka = a.match(chunk)[0];
115         chunkb = b.match(chunk)[0];
116         anum = !isNaN(chunka);
117         bnum = !isNaN(chunkb);
118         if (anum && !bnum)
119             return -1;
120         if (bnum && !anum)
121             return 1;
122         if (anum && bnum) {
123             diff = chunka - chunkb;
124             if (diff === 0 && chunka.length !== chunkb.length) {
125                 if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case)
126                     return chunka.length - chunkb.length;
127                 else
128                     return chunkb.length - chunka.length;
129             }
130         } else if (chunka !== chunkb)
131             return (chunka < chunkb) ? -1 : 1;
132         a = a.substring(chunka.length);
133         b = b.substring(chunkb.length);
134     }
135     return diff;
136 };
137
138 WebInspector.ObjectTreeView.prototype = {
139     constructor: WebInspector.ObjectTreeView,
140     __proto__: WebInspector.Object.prototype,
141
142     // Public
143
144     get object()
145     {
146         return this._object;
147     },
148
149     get element()
150     {
151         return this._element;
152     },
153
154     get expanded()
155     {
156         return this._expanded;
157     },
158
159     expand: function()
160     {
161         if (this._expanded)
162             return;
163
164         this._expanded = true;
165         this._element.classList.add("expanded");
166
167         if (this._previewView)
168             this._previewView.showTitle();
169
170         this._trackWeakEntries();
171
172         this.update();
173     },
174
175     collapse: function()
176     {
177         if (!this._expanded)
178             return;
179
180         this._expanded = false;
181         this._element.classList.remove("expanded");
182
183         if (this._previewView)
184             this._previewView.showPreview();
185
186         this._untrackWeakEntries();
187     },
188
189     appendTitleSuffix: function(suffixElement)
190     {
191         if (this._previewView)
192             this._previewView.element.appendChild(suffixElement);
193         else
194             this._titleElement.appendChild(suffixElement);
195     },
196
197     // Protected
198
199     update: function()
200     {
201         if (this._object.isCollectionType() && this._mode === WebInspector.ObjectTreeView.Mode.Properties)
202             this._object.getCollectionEntries(0, 100, this._updateChildren.bind(this, this._updateEntries));
203         else
204             this._object.getDisplayablePropertyDescriptors(this._updateChildren.bind(this, this._updateProperties));
205     },
206
207     // Private
208
209     _updateChildren: function(handler, list)
210     {
211         this._outline.removeChildren();
212
213         if (!list) {
214             var errorMessageElement = WebInspector.ObjectTreeView.emptyMessageElement(WebInspector.UIString("Could not fetch properties. Object may no longer exist."));
215             this._outline.appendChild(new TreeElement(errorMessageElement, null, false));
216             return;
217         }
218
219         handler.call(this, list, this._propertyPath);
220     },
221
222     _updateEntries: function(entries, propertyPath)
223     {
224         for (var entry of entries) {
225             if (entry.key) {
226                 this._outline.appendChild(new WebInspector.ObjectTreeMapKeyTreeElement(entry.key, propertyPath));
227                 this._outline.appendChild(new WebInspector.ObjectTreeMapValueTreeElement(entry.value, propertyPath, entry.key));
228             } else
229                 this._outline.appendChild(new WebInspector.ObjectTreeSetIndexTreeElement(entry.value, propertyPath));
230         }
231
232         if (!this._outline.children.length) {
233             var emptyMessageElement = WebInspector.ObjectTreeView.emptyMessageElement(WebInspector.UIString("No Entries."));
234             this._outline.appendChild(new TreeElement(emptyMessageElement, null, false));
235         }
236
237         // Show the prototype so users can see the API.
238         this._object.getOwnPropertyDescriptor("__proto__", function(propertyDescriptor) {
239             if (propertyDescriptor)
240                 this._outline.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, propertyPath, this._mode));
241         }.bind(this));
242     },
243
244     _updateProperties: function(properties, propertyPath)
245     {
246         properties.sort(WebInspector.ObjectTreeView.ComparePropertyDescriptors);
247
248         var isArray = this._object.isArray();
249         var isPropertyMode = this._mode === WebInspector.ObjectTreeView.Mode.Properties;
250
251         for (var propertyDescriptor of properties) {
252             if (isArray && isPropertyMode) {
253                 if (propertyDescriptor.isIndexProperty())
254                     this._outline.appendChild(new WebInspector.ObjectTreeArrayIndexTreeElement(propertyDescriptor, propertyPath));
255                 else if (propertyDescriptor.name === "__proto__")
256                     this._outline.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, propertyPath, this._mode));
257             } else
258                 this._outline.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, propertyPath, this._mode));
259         }
260
261         if (!this._outline.children.length) {
262             var emptyMessageElement = WebInspector.ObjectTreeView.emptyMessageElement(WebInspector.UIString("No Properties."));
263             this._outline.appendChild(new TreeElement(emptyMessageElement, null, false));
264         }
265     },
266
267     _handlePreviewOrTitleElementClick: function(event)
268     {
269         if (this._hasLosslessPreview)
270             return;
271
272         if (!this._expanded)
273             this.expand();
274         else
275             this.collapse();
276
277         event.stopPropagation();
278     },
279
280     _trackWeakEntries: function()
281     {
282         if (this._trackingEntries)
283             return;
284
285         if (!this._object.isWeakCollection())
286             return;
287
288         this._trackingEntries = true;
289
290         if (this._inConsole) {
291             WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.Cleared, this._untrackWeakEntries, this);
292             WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.ActiveLogCleared, this._untrackWeakEntries, this);
293             WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.SessionStarted, this._untrackWeakEntries, this);
294         }
295     },
296
297     _untrackWeakEntries: function()
298     {
299         if (!this._trackingEntries)
300             return;
301
302         if (!this._object.isWeakCollection())
303             return;
304
305         this._trackingEntries = false;
306
307         this._object.releaseWeakCollectionEntries();
308
309         if (this._inConsole) {
310             WebInspector.logManager.removeEventListener(WebInspector.LogManager.Event.Cleared, this._untrackWeakEntries, this);
311             WebInspector.logManager.removeEventListener(WebInspector.LogManager.Event.ActiveLogCleared, this._untrackWeakEntries, this);
312             WebInspector.logManager.removeEventListener(WebInspector.LogManager.Event.SessionStarted, this._untrackWeakEntries, this);
313         }
314
315         // FIXME: This only tries to release weak entries if this object was a WeakMap.
316         // If there was a WeakMap expanded in a sub-object, we will never release those values.
317         // Should we attempt walking the entire tree and release weak collections?
318     },
319 };