| /* |
| * 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 non-internal classes. |
| for (let [className, {size, retainedSize, count, internalCount, deadCount}] of this.heapSnapshot.categories) { |
| if (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; |
| |
| this.appendChild(new WI.HeapSnapshotClassDataGridNode({className, size, retainedSize, 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(); |
| }); |
| } |
| }; |