Web Inspector: Heap: logging an object from a snapshot shouldn't use the "special...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / HeapSnapshotInstanceDataGridNode.js
1 /*
2 * Copyright (C) 2016 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 WI.HeapSnapshotInstanceDataGridNode = class HeapSnapshotInstanceDataGridNode extends WI.DataGridNode
27 {
28     constructor(node, tree, edge, base)
29     {
30         // Don't treat strings as having child nodes, even if they have a Structure.
31         let hasChildren = node.hasChildren && node.className !== "string";
32
33         super(node, hasChildren);
34
35         console.assert(node instanceof WI.HeapSnapshotNodeProxy);
36         console.assert(!edge || edge instanceof WI.HeapSnapshotEdgeProxy);
37         console.assert(!base || base instanceof WI.HeapSnapshotInstanceDataGridNode);
38
39         this._node = node;
40         this._tree = tree;
41         this._edge = edge || null;
42         this._base = base || null;
43
44         // FIXME: Make instance grid nodes copyable.
45         this.copyable = false;
46
47         if (hasChildren)
48             this.addEventListener("populate", this._populate, this);
49     }
50
51     // Static
52
53     static logHeapSnapshotNode(node)
54     {
55         node.shortestGCRootPath((gcRootPath) => {
56             let text = WI.UIString("Heap Snapshot Object (%s)").format("@" + node.id);
57             let addSpecialUserLogClass = !gcRootPath.length;
58
59             if (gcRootPath.length) {
60                 gcRootPath = gcRootPath.slice().reverse();
61                 let windowIndex = gcRootPath.findIndex((x) => {
62                     return x instanceof WI.HeapSnapshotNodeProxy && x.className === "Window";
63                 });
64
65                 let heapSnapshotRootPath = WI.HeapSnapshotRootPath.emptyPath();
66                 for (let i = windowIndex === -1 ? 0 : windowIndex; i < gcRootPath.length; ++i) {
67                     let component = gcRootPath[i];
68                     if (component instanceof WI.HeapSnapshotNodeProxy) {
69                         if (component.className === "Window")
70                             heapSnapshotRootPath = heapSnapshotRootPath.appendGlobalScopeName(component, "window");
71                     } else if (component instanceof WI.HeapSnapshotEdgeProxy)
72                         heapSnapshotRootPath = heapSnapshotRootPath.appendEdge(component);
73                 }
74
75                 if (heapSnapshotRootPath.isFullPathImpossible())
76                     addSpecialUserLogClass = true;
77                 else
78                     text = heapSnapshotRootPath.fullPath;
79             }
80
81             const shouldRevealConsole = true;
82
83             if (node.className === "string") {
84                 HeapAgent.getPreview(node.id, function(error, string, functionDetails, objectPreviewPayload) {
85                     let remoteObject = error ? WI.RemoteObject.fromPrimitiveValue(undefined) : WI.RemoteObject.fromPrimitiveValue(string);
86                     WI.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, addSpecialUserLogClass, shouldRevealConsole);
87                 });
88             } else {
89                 HeapAgent.getRemoteObject(node.id, WI.RuntimeManager.ConsoleObjectGroup, function(error, remoteObjectPayload) {
90                     let remoteObject = error ? WI.RemoteObject.fromPrimitiveValue(undefined) : WI.RemoteObject.fromPayload(remoteObjectPayload, WI.assumingMainTarget());
91                     WI.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, addSpecialUserLogClass, shouldRevealConsole);
92                 });
93             }
94         });
95     }
96
97     // Protected
98
99     get data() { return this._node; }
100     get node() { return this._node; }
101
102     get propertyName()
103     {
104         if (!this._edge)
105             return "";
106
107         if (!this._propertyName)
108             this._propertyName = WI.HeapSnapshotRootPath.pathComponentForIndividualEdge(this._edge);
109         return this._propertyName;
110     }
111
112     createCells()
113     {
114         super.createCells();
115
116         this.element.classList.add("instance");
117     }
118
119     createCellContent(columnIdentifier)
120     {
121         if (columnIdentifier === "retainedSize") {
122             let subRetainedSize = false;
123             if (this._base && !this._tree.alwaysShowRetainedSize) {
124                 if (this._isDominatedByNonBaseParent())
125                     subRetainedSize = true;
126                 else if (!this._isDominatedByBase())
127                     return emDash;
128             }
129
130             let size = this._node.retainedSize;
131             let fragment = document.createDocumentFragment();
132             let sizeElement = fragment.appendChild(document.createElement("span"));
133             sizeElement.classList.add("size");
134             sizeElement.textContent = Number.bytesToString(size);
135             let fraction = size / this._tree._heapSnapshot.totalSize;
136             let percentElement = fragment.appendChild(document.createElement("span"));
137             percentElement.classList.add("percentage");
138             percentElement.textContent = Number.percentageString(fraction);
139
140             if (subRetainedSize) {
141                 sizeElement.classList.add("sub-retained");
142                 percentElement.classList.add("sub-retained");
143             }
144
145             return fragment;
146         }
147
148         if (columnIdentifier === "size")
149             return Number.bytesToString(this._node.size);
150
151         if (columnIdentifier === "className") {
152             let {className, id, internal, isObjectType} = this._node;
153             let containerElement = document.createElement("span");
154             containerElement.addEventListener("contextmenu", this._contextMenuHandler.bind(this));
155
156             let iconElement = containerElement.appendChild(document.createElement("img"));
157             iconElement.classList.add("icon", WI.HeapSnapshotClusterContentView.iconStyleClassNameForClassName(className, internal, isObjectType));
158
159             if (this._edge) {
160                 let nameElement = containerElement.appendChild(document.createElement("span"));
161                 let propertyName = this.propertyName;
162                 if (propertyName)
163                     nameElement.textContent = propertyName + ": " + this._node.className + " ";
164                 else
165                     nameElement.textContent = this._node.className + " ";
166             }
167
168             let idElement = containerElement.appendChild(document.createElement("span"));
169             idElement.classList.add("object-id");
170             idElement.textContent = "@" + id;
171             idElement.addEventListener("click", WI.HeapSnapshotInstanceDataGridNode.logHeapSnapshotNode.bind(null, this._node));
172             idElement.addEventListener("mouseover", this._mouseoverHandler.bind(this));
173
174             let spacerElement = containerElement.appendChild(document.createElement("span"));
175             spacerElement.textContent = " ";
176
177             if (className === "Window" && this._node.dominatorNodeIdentifier === 0) {
178                 containerElement.append("Window ");
179                 this._populateWindowPreview(containerElement);
180             } else
181                 this._populatePreview(containerElement);
182
183             return containerElement;
184         }
185
186         return super.createCellContent(columnIdentifier);
187     }
188
189     sort()
190     {
191         let children = this.children;
192         children.sort(this._tree._sortComparator);
193
194         for (let i = 0; i < children.length; ++i) {
195             children[i]._recalculateSiblings(i);
196             children[i].sort();
197         }
198     }
199
200     // Private
201
202     _isDominatedByBase()
203     {
204         return this._node.dominatorNodeIdentifier === this._base.node.id;
205     }
206
207     _isDominatedByNonBaseParent()
208     {
209         for (let p = this.parent; p; p = p.parent) {
210             if (p === this._base)
211                 return false;
212             if (this._node.dominatorNodeIdentifier === p.node.id)
213                 return true;
214         }
215
216         return false;
217     }
218
219     _populate()
220     {
221         this.removeEventListener("populate", this._populate, this);
222
223         function propertyName(edge) {
224             return edge ? WI.HeapSnapshotRootPath.pathComponentForIndividualEdge(edge) : "";
225         }
226
227         this._node.retainedNodes((instances, edges) => {
228             // Reference edge from instance so we can get it after sorting.
229             for (let i = 0; i < instances.length; ++i)
230                 instances[i].__edge = edges[i];
231
232             instances.sort((a, b) => {
233                 let fakeDataGridNodeA = {data: a, propertyName: propertyName(a.__edge)};
234                 let fakeDataGridNodeB = {data: b, propertyName: propertyName(b.__edge)};
235                 return this._tree._sortComparator(fakeDataGridNodeA, fakeDataGridNodeB);
236             });
237
238             // FIXME: This should gracefully handle a node that references many objects.
239
240             for (let instance of instances) {
241                 if (instance.__edge && instance.__edge.isPrivateSymbol())
242                     continue;
243
244                 this.appendChild(new WI.HeapSnapshotInstanceDataGridNode(instance, this._tree, instance.__edge, this._base || this));
245             }
246         });
247     }
248
249     _contextMenuHandler(event)
250     {
251         let contextMenu = WI.ContextMenu.createFromEvent(event);
252         contextMenu.appendSeparator();
253         contextMenu.appendItem(WI.UIString("Log Value"), WI.HeapSnapshotInstanceDataGridNode.logHeapSnapshotNode.bind(null, this._node));
254     }
255
256     _populateError(containerElement)
257     {
258         if (this._node.internal)
259             return;
260
261         let previewErrorElement = containerElement.appendChild(document.createElement("span"));
262         previewErrorElement.classList.add("preview-error");
263         previewErrorElement.textContent = WI.UIString("No preview available");
264     }
265
266     _populateWindowPreview(containerElement)
267     {
268         HeapAgent.getRemoteObject(this._node.id, (error, remoteObjectPayload) => {
269             if (error) {
270                 this._populateError(containerElement);
271                 return;
272             }
273
274             function inspectedPage_window_getLocationHref() {
275                 return this.location.href;
276             }
277
278             let remoteObject = WI.RemoteObject.fromPayload(remoteObjectPayload, WI.assumingMainTarget());
279             remoteObject.callFunctionJSON(inspectedPage_window_getLocationHref, undefined, (href) => {
280                 remoteObject.release();
281
282                 if (!href)
283                     this._populateError(containerElement);
284                 else {
285                     let primitiveRemoteObject = WI.RemoteObject.fromPrimitiveValue(href);
286                     containerElement.appendChild(WI.FormattedValue.createElementForRemoteObject(primitiveRemoteObject));
287                 }
288             });
289         });
290     }
291
292     _populatePreview(containerElement)
293     {
294         HeapAgent.getPreview(this._node.id, (error, string, functionDetails, objectPreviewPayload) => {
295             if (error) {
296                 this._populateError(containerElement);
297                 return;
298             }
299
300             if (string) {
301                 let primitiveRemoteObject = WI.RemoteObject.fromPrimitiveValue(string);
302                 containerElement.appendChild(WI.FormattedValue.createElementForRemoteObject(primitiveRemoteObject));
303                 return;
304             }
305
306             if (functionDetails) {
307                 let {location, name, displayName} = functionDetails;
308                 let functionNameElement = containerElement.appendChild(document.createElement("span"));
309                 functionNameElement.classList.add("function-name");
310                 functionNameElement.textContent = name || displayName || WI.UIString("(anonymous function)");
311                 let sourceCode = WI.debuggerManager.scriptForIdentifier(location.scriptId, WI.assumingMainTarget());
312                 if (sourceCode) {
313                     let locationElement = containerElement.appendChild(document.createElement("span"));
314                     locationElement.classList.add("location");
315                     let sourceCodeLocation = sourceCode.createSourceCodeLocation(location.lineNumber, location.columnNumber);
316                     sourceCodeLocation.populateLiveDisplayLocationString(locationElement, "textContent", WI.SourceCodeLocation.ColumnStyle.Hidden, WI.SourceCodeLocation.NameStyle.Short);
317
318                     const options = {
319                         dontFloat: true,
320                         useGoToArrowButton: true,
321                         ignoreNetworkTab: true,
322                         ignoreSearchTab: true,
323                     };
324                     let goToArrowButtonLink = WI.createSourceCodeLocationLink(sourceCodeLocation, options);
325                     containerElement.appendChild(goToArrowButtonLink);
326                 }
327                 return;
328             }
329
330             if (objectPreviewPayload) {
331                 let objectPreview = WI.ObjectPreview.fromPayload(objectPreviewPayload);
332                 let previewElement = WI.FormattedValue.createObjectPreviewOrFormattedValueForObjectPreview(objectPreview);
333                 containerElement.appendChild(previewElement);
334                 return;
335             }
336         });
337     }
338
339     _mouseoverHandler(event)
340     {
341         let targetFrame = WI.Rect.rectFromClientRect(event.target.getBoundingClientRect());
342         if (!targetFrame.size.width && !targetFrame.size.height)
343             return;
344
345         if (this._tree.popoverGridNode === this._node)
346             return;
347
348         this._tree.popoverGridNode = this._node;
349         this._tree.popoverTargetElement = event.target;
350
351         let popoverContentElement = document.createElement("div");
352         popoverContentElement.classList.add("heap-snapshot", "heap-snapshot-instance-popover-content");
353
354         function appendTitle(node) {
355             let idElement = document.createElement("span");
356             idElement.classList.add("object-id");
357             idElement.textContent = "@" + node.id;
358             idElement.addEventListener("click", WI.HeapSnapshotInstanceDataGridNode.logHeapSnapshotNode.bind(null, node));
359
360             let title = popoverContentElement.appendChild(document.createElement("div"));
361             title.classList.add("title");
362             let localizedString = WI.UIString("Shortest property path to %s").format("@@@");
363             let [before, after] = localizedString.split(/\s*@@@\s*/);
364             title.append(before + " ", idElement, " " + after);
365         }
366
367         function appendPath(path) {
368             let tableContainer = popoverContentElement.appendChild(document.createElement("div"));
369             tableContainer.classList.add("table-container");
370
371             let tableElement = tableContainer.appendChild(document.createElement("table"));
372
373             path = path.slice().reverse();
374             let windowIndex = path.findIndex((x) => {
375                 return x instanceof WI.HeapSnapshotNodeProxy && x.className === "Window";
376             });
377
378             let edge = null;
379             for (let i = windowIndex === -1 ? 0 : windowIndex; i < path.length; ++i) {
380                 let component = path[i];
381                 if (component instanceof WI.HeapSnapshotEdgeProxy) {
382                     edge = component;
383                     continue;
384                 }
385                 appendPathRow(tableElement, edge, component);
386                 edge = null;
387             }
388         }
389
390         function appendPathRow(tableElement, edge, node) {
391             let tableRow = tableElement.appendChild(document.createElement("tr"));
392
393             // Edge name.
394             let pathDataElement = tableRow.appendChild(document.createElement("td"));
395             pathDataElement.classList.add("edge-name");
396
397             if (node.className === "Window")
398                 pathDataElement.textContent = "window";
399             else if (edge) {
400                 let edgeString = stringifyEdge(edge);
401                 pathDataElement.textContent = typeof edgeString === "string" ? edgeString : emDash;
402             } else
403                 pathDataElement.textContent = emDash;
404
405             if (pathDataElement.textContent.length > 10)
406                 pathDataElement.title = pathDataElement.textContent;
407
408             // Object.
409             let objectDataElement = tableRow.appendChild(document.createElement("td"));
410             objectDataElement.classList.add("object-data");
411
412             let containerElement = objectDataElement.appendChild(document.createElement("div"));
413             containerElement.classList.add("node");
414
415             let iconElement = containerElement.appendChild(document.createElement("img"));
416             iconElement.classList.add("icon", WI.HeapSnapshotClusterContentView.iconStyleClassNameForClassName(node.className, node.internal, node.isObjectType));
417
418             let classNameElement = containerElement.appendChild(document.createElement("span"));
419             classNameElement.textContent = sanitizeClassName(node.className) + " ";
420
421             let idElement = containerElement.appendChild(document.createElement("span"));
422             idElement.classList.add("object-id");
423             idElement.textContent = "@" + node.id;
424             idElement.addEventListener("click", WI.HeapSnapshotInstanceDataGridNode.logHeapSnapshotNode.bind(null, node));
425
426             // Extra.
427             if (node.className === "Function") {
428                 let goToArrowPlaceHolderElement = containerElement.appendChild(document.createElement("span"));
429                 goToArrowPlaceHolderElement.style.display = "inline-block";
430                 goToArrowPlaceHolderElement.style.width = "10px";
431                 HeapAgent.getPreview(node.id, function(error, string, functionDetails, objectPreviewPayload) {
432                     if (functionDetails) {
433                         let location = functionDetails.location;
434                         let sourceCode = WI.debuggerManager.scriptForIdentifier(location.scriptId, WI.assumingMainTarget());
435                         if (sourceCode) {
436                             let sourceCodeLocation = sourceCode.createSourceCodeLocation(location.lineNumber, location.columnNumber);
437
438                             const options = {
439                                 dontFloat: true,
440                                 useGoToArrowButton: true,
441                                 ignoreNetworkTab: true,
442                                 ignoreSearchTab: true,
443                             };
444                             let goToArrowButtonLink = WI.createSourceCodeLocationLink(sourceCodeLocation, options);
445                             containerElement.replaceChild(goToArrowButtonLink, goToArrowPlaceHolderElement);
446                         }
447                     }
448                 });
449             }
450         }
451
452         function sanitizeClassName(className) {
453             if (className.endsWith("LexicalEnvironment"))
454                 return WI.UIString("Scope");
455             return className;
456         }
457
458         function stringifyEdge(edge) {
459             switch (edge.type) {
460             case WI.HeapSnapshotEdgeProxy.EdgeType.Property:
461             case WI.HeapSnapshotEdgeProxy.EdgeType.Variable:
462                 if (/^(?![0-9])\w+$/.test(edge.data))
463                     return edge.data;
464                 return "[" + doubleQuotedString(edge.data) + "]";
465             case WI.HeapSnapshotEdgeProxy.EdgeType.Index:
466                 return "[" + edge.data + "]";
467             case WI.HeapSnapshotEdgeProxy.EdgeType.Internal:
468             default:
469                 return null;
470             }
471         }
472
473         this._node.shortestGCRootPath((path) => {
474             if (!this._tree.visible)
475                 return;
476
477             if (path.length) {
478                 appendTitle(this._node);
479                 appendPath(path);
480             } else if (this._node.gcRoot) {
481                 let textElement = popoverContentElement.appendChild(document.createElement("div"));
482                 textElement.textContent = WI.UIString("This object is a root");
483             } else {
484                 let emptyElement = popoverContentElement.appendChild(document.createElement("div"));
485                 emptyElement.textContent = WI.UIString("This object is referenced by internal objects");
486             }
487
488             this._tree.popover.presentNewContentWithFrame(popoverContentElement, targetFrame.pad(2), [WI.RectEdge.MAX_Y, WI.RectEdge.MIN_Y, WI.RectEdge.MAX_X]);
489         });
490     }
491 };