blob: c849af57213c59bd8bf4bbbbb2afc1ff5de1e92d [file] [log] [blame]
/*
* Copyright (C) 2016 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
WI.HeapSnapshotInstanceDataGridNode = class HeapSnapshotInstanceDataGridNode extends WI.DataGridNode
{
constructor(node, tree, edge, base)
{
// Don't treat strings as having child nodes, even if they have a Structure.
let hasChildren = node.hasChildren && node.className !== "string";
super(node, hasChildren);
console.assert(node instanceof WI.HeapSnapshotNodeProxy);
console.assert(!edge || edge instanceof WI.HeapSnapshotEdgeProxy);
console.assert(!base || base instanceof WI.HeapSnapshotInstanceDataGridNode);
this._node = node;
this._tree = tree;
this._edge = edge || null;
this._base = base || null;
// FIXME: Make instance grid nodes copyable.
this.copyable = false;
if (hasChildren)
this.addEventListener("populate", this._populate, this);
}
// Static
static logHeapSnapshotNode(node)
{
node.shortestGCRootPath((gcRootPath) => {
let text = WI.UIString("Heap Snapshot Object (%s)").format("@" + node.id);
let addSpecialUserLogClass = !gcRootPath.length;
if (gcRootPath.length) {
gcRootPath = gcRootPath.slice().reverse();
let windowIndex = gcRootPath.findIndex((x) => {
return x instanceof WI.HeapSnapshotNodeProxy && x.className === "Window";
});
let heapSnapshotRootPath = WI.HeapSnapshotRootPath.emptyPath();
for (let i = windowIndex === -1 ? 0 : windowIndex; i < gcRootPath.length; ++i) {
let component = gcRootPath[i];
if (component instanceof WI.HeapSnapshotNodeProxy) {
if (component.className === "Window")
heapSnapshotRootPath = heapSnapshotRootPath.appendGlobalScopeName(component, "window");
} else if (component instanceof WI.HeapSnapshotEdgeProxy)
heapSnapshotRootPath = heapSnapshotRootPath.appendEdge(component);
}
if (heapSnapshotRootPath.isFullPathImpossible())
addSpecialUserLogClass = true;
else
text = heapSnapshotRootPath.fullPath;
}
const shouldRevealConsole = true;
if (node.className === "string") {
WI.heapManager.getPreview(node, function(error, string, functionDetails, objectPreviewPayload) {
let remoteObject = error ? WI.RemoteObject.fromPrimitiveValue(undefined) : WI.RemoteObject.fromPrimitiveValue(string);
WI.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, addSpecialUserLogClass, shouldRevealConsole);
});
} else {
WI.heapManager.getRemoteObject(node, WI.RuntimeManager.ConsoleObjectGroup, function(error, remoteObjectPayload) {
let remoteObject = error ? WI.RemoteObject.fromPrimitiveValue(undefined) : WI.RemoteObject.fromPayload(remoteObjectPayload, WI.assumingMainTarget());
WI.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, addSpecialUserLogClass, shouldRevealConsole);
});
}
});
}
// Protected
get data() { return this._node; }
get node() { return this._node; }
get propertyName()
{
if (!this._edge)
return "";
if (!this._propertyName)
this._propertyName = WI.HeapSnapshotRootPath.pathComponentForIndividualEdge(this._edge);
return this._propertyName;
}
createCells()
{
super.createCells();
this.element.classList.add("instance");
}
createCellContent(columnIdentifier)
{
if (columnIdentifier === "retainedSize") {
let subRetainedSize = false;
if (this._base && !this._tree.alwaysShowRetainedSize) {
if (this._isDominatedByNonBaseParent())
subRetainedSize = true;
else if (!this._isDominatedByBase())
return emDash;
}
let size = this._node.retainedSize;
let fragment = document.createDocumentFragment();
let sizeElement = fragment.appendChild(document.createElement("span"));
sizeElement.classList.add("size");
sizeElement.textContent = Number.bytesToString(size);
let fraction = size / this._tree._heapSnapshot.totalSize;
let percentElement = fragment.appendChild(document.createElement("span"));
percentElement.classList.add("percentage");
percentElement.textContent = Number.percentageString(fraction);
if (subRetainedSize) {
sizeElement.classList.add("sub-retained");
percentElement.classList.add("sub-retained");
}
return fragment;
}
if (columnIdentifier === "size")
return Number.bytesToString(this._node.size);
if (columnIdentifier === "className") {
let {className, id, internal, isObjectType} = this._node;
let containerElement = document.createElement("span");
containerElement.addEventListener("contextmenu", this._contextMenuHandler.bind(this));
let iconElement = containerElement.appendChild(document.createElement("img"));
iconElement.classList.add("icon", WI.HeapSnapshotClusterContentView.iconStyleClassNameForClassName(className, internal, isObjectType));
if (this._edge) {
let nameElement = containerElement.appendChild(document.createElement("span"));
let propertyName = this.propertyName;
if (propertyName)
nameElement.textContent = propertyName + ": " + this._node.className + " ";
else
nameElement.textContent = this._node.className + " ";
}
let idElement = containerElement.appendChild(document.createElement("span"));
idElement.classList.add("object-id");
idElement.textContent = "@" + id;
idElement.addEventListener("click", WI.HeapSnapshotInstanceDataGridNode.logHeapSnapshotNode.bind(null, this._node));
idElement.addEventListener("mouseover", this._mouseoverHandler.bind(this));
let spacerElement = containerElement.appendChild(document.createElement("span"));
spacerElement.textContent = " ";
if (className === "Window" && this._node.dominatorNodeIdentifier === 0) {
containerElement.append("Window ");
this._populateWindowPreview(containerElement);
} else
this._populatePreview(containerElement);
return containerElement;
}
return super.createCellContent(columnIdentifier);
}
sort()
{
let children = this.children;
children.sort(this._tree._sortComparator);
for (let i = 0; i < children.length; ++i) {
children[i]._recalculateSiblings(i);
children[i].sort();
}
}
// Private
_isDominatedByBase()
{
return this._node.dominatorNodeIdentifier === this._base.node.id;
}
_isDominatedByNonBaseParent()
{
for (let p = this.parent; p; p = p.parent) {
if (p === this._base)
return false;
if (this._node.dominatorNodeIdentifier === p.node.id)
return true;
}
return false;
}
_populate()
{
this.removeEventListener("populate", this._populate, this);
function propertyName(edge) {
return edge ? WI.HeapSnapshotRootPath.pathComponentForIndividualEdge(edge) : "";
}
this._node.retainedNodes((instances, edges) => {
// Reference edge from instance so we can get it after sorting.
for (let i = 0; i < instances.length; ++i)
instances[i].__edge = edges[i];
instances.sort((a, b) => {
let fakeDataGridNodeA = {data: a, propertyName: propertyName(a.__edge)};
let fakeDataGridNodeB = {data: b, propertyName: propertyName(b.__edge)};
return this._tree._sortComparator(fakeDataGridNodeA, fakeDataGridNodeB);
});
// FIXME: This should gracefully handle a node that references many objects.
for (let instance of instances) {
if (instance.__edge && instance.__edge.isPrivateSymbol())
continue;
this.appendChild(new WI.HeapSnapshotInstanceDataGridNode(instance, this._tree, instance.__edge, this._base || this));
}
});
}
_contextMenuHandler(event)
{
let contextMenu = WI.ContextMenu.createFromEvent(event);
contextMenu.appendSeparator();
contextMenu.appendItem(WI.UIString("Log Value"), WI.HeapSnapshotInstanceDataGridNode.logHeapSnapshotNode.bind(null, this._node));
}
_populateError(containerElement)
{
if (this._node.internal)
return;
let previewErrorElement = containerElement.appendChild(document.createElement("span"));
previewErrorElement.classList.add("preview-error");
previewErrorElement.textContent = WI.UIString("No preview available");
}
_populateWindowPreview(containerElement)
{
const objectGroup = undefined;
WI.heapManager.getRemoteObject(this._node, objectGroup, (error, remoteObjectPayload) => {
if (error) {
this._populateError(containerElement);
return;
}
function inspectedPage_window_getLocationHref() {
return this.location.href;
}
let remoteObject = WI.RemoteObject.fromPayload(remoteObjectPayload, WI.assumingMainTarget());
remoteObject.callFunctionJSON(inspectedPage_window_getLocationHref, undefined, (href) => {
remoteObject.release();
if (!href)
this._populateError(containerElement);
else {
let primitiveRemoteObject = WI.RemoteObject.fromPrimitiveValue(href);
containerElement.appendChild(WI.FormattedValue.createElementForRemoteObject(primitiveRemoteObject));
}
});
});
}
_populatePreview(containerElement)
{
WI.heapManager.getPreview(this._node, (error, string, functionDetails, objectPreviewPayload) => {
if (error) {
this._populateError(containerElement);
return;
}
if (string) {
if (this._node.className === "BigInt") {
let bigIntRemoteObject = WI.RemoteObject.createBigIntFromDescriptionString(string + "n");
containerElement.appendChild(WI.FormattedValue.createElementForRemoteObject(bigIntRemoteObject));
} else {
let primitiveRemoteObject = WI.RemoteObject.fromPrimitiveValue(string);
containerElement.appendChild(WI.FormattedValue.createElementForRemoteObject(primitiveRemoteObject));
}
return;
}
if (functionDetails) {
let {location, name, displayName} = functionDetails;
let functionNameElement = containerElement.appendChild(document.createElement("span"));
functionNameElement.classList.add("function-name");
functionNameElement.textContent = name || displayName || WI.UIString("(anonymous function)");
let sourceCode = WI.debuggerManager.scriptForIdentifier(location.scriptId, WI.assumingMainTarget());
if (sourceCode) {
let locationElement = containerElement.appendChild(document.createElement("span"));
locationElement.classList.add("location");
let sourceCodeLocation = sourceCode.createSourceCodeLocation(location.lineNumber, location.columnNumber);
sourceCodeLocation.populateLiveDisplayLocationString(locationElement, "textContent", WI.SourceCodeLocation.ColumnStyle.Hidden, WI.SourceCodeLocation.NameStyle.Short);
const options = {
dontFloat: true,
useGoToArrowButton: true,
ignoreNetworkTab: true,
ignoreSearchTab: true,
};
let goToArrowButtonLink = WI.createSourceCodeLocationLink(sourceCodeLocation, options);
containerElement.appendChild(goToArrowButtonLink);
}
return;
}
if (objectPreviewPayload) {
let objectPreview = WI.ObjectPreview.fromPayload(objectPreviewPayload);
let previewElement = WI.FormattedValue.createObjectPreviewOrFormattedValueForObjectPreview(objectPreview);
containerElement.appendChild(previewElement);
return;
}
});
}
_mouseoverHandler(event)
{
let targetFrame = WI.Rect.rectFromClientRect(event.target.getBoundingClientRect());
if (!targetFrame.size.width && !targetFrame.size.height)
return;
if (this._tree.popoverGridNode === this._node)
return;
this._tree.popoverGridNode = this._node;
this._tree.popoverTargetElement = event.target;
let popoverContentElement = document.createElement("div");
popoverContentElement.classList.add("heap-snapshot", "heap-snapshot-instance-popover-content");
function appendTitle(node) {
let idElement = document.createElement("span");
idElement.classList.add("object-id");
idElement.textContent = "@" + node.id;
idElement.addEventListener("click", WI.HeapSnapshotInstanceDataGridNode.logHeapSnapshotNode.bind(null, node));
let title = popoverContentElement.appendChild(document.createElement("div"));
title.classList.add("title");
let localizedString = WI.UIString("Shortest property path to %s").format("@@@");
let [before, after] = localizedString.split(/\s*@@@\s*/);
title.append(before + " ", idElement, " " + after);
}
function appendPath(path) {
let tableContainer = popoverContentElement.appendChild(document.createElement("div"));
tableContainer.classList.add("table-container");
let tableElement = tableContainer.appendChild(document.createElement("table"));
path = path.slice().reverse();
let windowIndex = path.findIndex((x) => {
return x instanceof WI.HeapSnapshotNodeProxy && x.className === "Window";
});
let edge = null;
for (let i = windowIndex === -1 ? 0 : windowIndex; i < path.length; ++i) {
let component = path[i];
if (component instanceof WI.HeapSnapshotEdgeProxy) {
edge = component;
continue;
}
appendPathRow(tableElement, edge, component);
edge = null;
}
}
function appendPathRow(tableElement, edge, node) {
let tableRow = tableElement.appendChild(document.createElement("tr"));
// Edge name.
let pathDataElement = tableRow.appendChild(document.createElement("td"));
pathDataElement.classList.add("edge-name");
if (node.className === "Window")
pathDataElement.textContent = "window";
else if (edge) {
let edgeString = stringifyEdge(edge);
pathDataElement.textContent = typeof edgeString === "string" ? edgeString : emDash;
} else
pathDataElement.textContent = emDash;
if (pathDataElement.textContent.length > 10)
pathDataElement.title = pathDataElement.textContent;
// Object.
let objectDataElement = tableRow.appendChild(document.createElement("td"));
objectDataElement.classList.add("object-data");
let containerElement = objectDataElement.appendChild(document.createElement("div"));
containerElement.classList.add("node");
let iconElement = containerElement.appendChild(document.createElement("img"));
iconElement.classList.add("icon", WI.HeapSnapshotClusterContentView.iconStyleClassNameForClassName(node.className, node.internal, node.isObjectType));
let classNameElement = containerElement.appendChild(document.createElement("span"));
classNameElement.textContent = sanitizeClassName(node.className) + " ";
let idElement = containerElement.appendChild(document.createElement("span"));
idElement.classList.add("object-id");
idElement.textContent = "@" + node.id;
idElement.addEventListener("click", WI.HeapSnapshotInstanceDataGridNode.logHeapSnapshotNode.bind(null, node));
// Extra.
if (node.className === "Function") {
let goToArrowPlaceHolderElement = containerElement.appendChild(document.createElement("span"));
goToArrowPlaceHolderElement.style.display = "inline-block";
goToArrowPlaceHolderElement.style.width = "10px";
WI.heapManager.getPreview(node, function(error, string, functionDetails, objectPreviewPayload) {
if (functionDetails) {
let location = functionDetails.location;
let sourceCode = WI.debuggerManager.scriptForIdentifier(location.scriptId, WI.assumingMainTarget());
if (sourceCode) {
let sourceCodeLocation = sourceCode.createSourceCodeLocation(location.lineNumber, location.columnNumber);
const options = {
dontFloat: true,
useGoToArrowButton: true,
ignoreNetworkTab: true,
ignoreSearchTab: true,
};
let goToArrowButtonLink = WI.createSourceCodeLocationLink(sourceCodeLocation, options);
containerElement.replaceChild(goToArrowButtonLink, goToArrowPlaceHolderElement);
}
}
});
}
}
function sanitizeClassName(className) {
if (className.endsWith("LexicalEnvironment"))
return WI.UIString("Scope");
return className;
}
function stringifyEdge(edge) {
switch (edge.type) {
case WI.HeapSnapshotEdgeProxy.EdgeType.Property:
case WI.HeapSnapshotEdgeProxy.EdgeType.Variable:
if (/^(?![0-9])\w+$/.test(edge.data))
return edge.data;
return "[" + doubleQuotedString(edge.data) + "]";
case WI.HeapSnapshotEdgeProxy.EdgeType.Index:
return "[" + edge.data + "]";
case WI.HeapSnapshotEdgeProxy.EdgeType.Internal:
default:
return null;
}
}
this._node.shortestGCRootPath((path) => {
if (!this._tree.visible)
return;
if (path.length) {
appendTitle(this._node);
appendPath(path);
} else if (this._node.gcRoot) {
let textElement = popoverContentElement.appendChild(document.createElement("div"));
textElement.textContent = WI.UIString("This object is a root");
} else {
let emptyElement = popoverContentElement.appendChild(document.createElement("div"));
emptyElement.textContent = WI.UIString("This object is referenced by internal objects");
}
this._tree.popover.presentNewContentWithFrame(popoverContentElement, targetFrame.pad(2), [WI.RectEdge.MAX_Y, WI.RectEdge.MIN_Y, WI.RectEdge.MAX_X]);
});
}
};