blob: d28f06ae1201644239ed5aa1eed52d262a569920 [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.HeapSnapshotDataGridTree = class HeapSnapshotDataGridTree extends WI.Object
{
constructor(heapSnapshot, sortComparator)
{
super();
console.assert(heapSnapshot instanceof WI.HeapSnapshotProxy || heapSnapshot instanceof WI.HeapSnapshotDiffProxy);
this._heapSnapshot = heapSnapshot;
this._heapSnapshot.addEventListener(WI.HeapSnapshotProxy.Event.CollectedNodes, this._heapSnapshotCollectedNodes, this);
this._children = [];
this._sortComparator = sortComparator;
this._visible = false;
this._popover = null;
this._popoverGridNode = null;
this._popoverTargetElement = null;
this.populateTopLevel();
}
// Static
static buildSortComparator(columnIdentifier, sortOrder)
{
let multiplier = sortOrder === WI.DataGrid.SortOrder.Ascending ? 1 : -1;
let numberCompare = (columnIdentifier, a, b) => multiplier * (a.data[columnIdentifier] - b.data[columnIdentifier]);
let nameCompare = (a, b) => {
// Sort by property name if available. Property names before no property name.
if (a.propertyName || b.propertyName) {
if (a.propertyName && !b.propertyName)
return multiplier * -1;
if (!a.propertyName && b.propertyName)
return multiplier * 1;
let propertyNameCompare = a.propertyName.extendedLocaleCompare(b.propertyName);
console.assert(propertyNameCompare !== 0, "Property names should be unique, we shouldn't have equal property names.");
return multiplier * propertyNameCompare;
}
// Sort by class name and object id if no property name.
let classNameCompare = a.data.className.extendedLocaleCompare(b.data.className);
if (classNameCompare)
return multiplier * classNameCompare;
if (a.data.id || b.data.id)
return multiplier * (a.data.id - b.data.id);
return 0;
};
switch (columnIdentifier) {
case "retainedSize":
return numberCompare.bind(this, "retainedSize");
case "size":
return numberCompare.bind(this, "size");
case "count":
return numberCompare.bind(this, "count");
case "className":
return nameCompare;
}
}
// Public
get heapSnapshot() { return this._heapSnapshot; }
get visible() { return this._visible; }
get popoverGridNode() { return this._popoverGridNode; }
set popoverGridNode(x) { this._popoverGridNode = x; }
get popoverTargetElement() { return this._popoverTargetElement; }
set popoverTargetElement(x) { this._popoverTargetElement = x; }
get popover()
{
if (!this._popover) {
this._popover = new WI.Popover(this);
this._popover.windowResizeHandler = () => {
let bounds = WI.Rect.rectFromClientRect(this._popoverTargetElement.getBoundingClientRect());
this._popover.present(bounds.pad(2), [WI.RectEdge.MAX_Y, WI.RectEdge.MIN_Y, WI.RectEdge.MAX_X]);
};
}
return this._popover;
}
get children()
{
return this._children;
}
appendChild(node)
{
this._children.push(node);
}
insertChild(node, index)
{
this._children.splice(index, 0, node);
}
removeChild(node)
{
this._children.remove(node, true);
}
removeChildren()
{
this._children = [];
}
set sortComparator(comparator)
{
this._sortComparator = comparator;
this.sort();
}
sort()
{
let children = this._children;
children.sort(this._sortComparator);
for (let i = 0; i < children.length; ++i) {
children[i]._recalculateSiblings(i);
children[i].sort();
}
}
shown()
{
this._visible = true;
}
hidden()
{
this._visible = false;
if (this._popover && this._popover.visible)
this._popover.dismiss();
}
// Popover delegate
willDismissPopover(popover)
{
this._popoverGridNode = null;
this._popoverTargetElement = null;
}
// Protected
get alwaysShowRetainedSize()
{
return false;
}
populateTopLevel()
{
// Implemented by subclasses.
}
removeCollectedNodes(collectedNodes)
{
// Implemented by subclasses.
}
didPopulate()
{
this.sort();
this.dispatchEventToListeners(WI.HeapSnapshotDataGridTree.Event.DidPopulate);
}
// Private
_heapSnapshotCollectedNodes(event)
{
this.removeCollectedNodes(event.data.collectedNodes);
}
};
WI.HeapSnapshotDataGridTree.Event = {
DidPopulate: "heap-snapshot-data-grid-tree-did-populate",
};
WI.HeapSnapshotInstancesDataGridTree = class HeapSnapshotInstancesDataGridTree extends WI.HeapSnapshotDataGridTree
{
get alwaysShowRetainedSize()
{
return false;
}
populateTopLevel()
{
// Populate the first level with the different classes.
let skipInternalOnlyObjects = !WI.settings.engineeringShowInternalObjectsInHeapSnapshot.value;
for (let [className, {size, retainedSize, count, internalCount, deadCount, objectCount}] of this.heapSnapshot.categories) {
console.assert(count > 0);
// Possibly skip internal only classes.
if (skipInternalOnlyObjects && count === internalCount)
continue;
// FIXME: <https://webkit.org/b/157905> Web Inspector: Provide a way to toggle between showing only live objects and live+dead objects
let liveCount = count - deadCount;
if (!liveCount)
continue;
// If over half of the objects with this class name are Object sub-types, treat this as an Object category.
// This can happen if the page has a JavaScript Class with the same name as a native class.
let isObjectSubcategory = (objectCount / count) > 0.5;
this.appendChild(new WI.HeapSnapshotClassDataGridNode({className, size, retainedSize, isObjectSubcategory, count: liveCount}, this));
}
this.didPopulate();
}
removeCollectedNodes(collectedNodes)
{
for (let classDataGridNode of this.children) {
let {count, deadCount} = this.heapSnapshot.categories.get(classDataGridNode.data.className);
let liveCount = count - deadCount;
classDataGridNode.updateCount(liveCount);
if (liveCount)
classDataGridNode.removeCollectedNodes(collectedNodes);
}
this.didPopulate();
}
};
WI.HeapSnapshotObjectGraphDataGridTree = class HeapSnapshotInstancesDataGridTree extends WI.HeapSnapshotDataGridTree
{
get alwaysShowRetainedSize()
{
return true;
}
populateTopLevel()
{
this.heapSnapshot.instancesWithClassName("GlobalObject", (instances) => {
for (let instance of instances)
this.appendChild(new WI.HeapSnapshotInstanceDataGridNode(instance, this));
});
this.heapSnapshot.instancesWithClassName("Window", (instances) => {
for (let instance of instances) {
// FIXME: Why is the window.Window Function classified as a Window?
// In any case, ignore objects not dominated by the root, as they
// are probably not what we want.
if (instance.dominatorNodeIdentifier === 0)
this.appendChild(new WI.HeapSnapshotInstanceDataGridNode(instance, this));
}
this.didPopulate();
});
}
};