// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Heap profiler panel implementation.
 */

WebInspector.HeapProfilerPanel = function() {
    WebInspector.Panel.call(this);

    this.element.addStyleClass("heap-profiler");

    this.sidebarElement = document.createElement("div");
    this.sidebarElement.id = "heap-snapshot-sidebar";
    this.sidebarElement.className = "sidebar";
    this.element.appendChild(this.sidebarElement);

    this.sidebarResizeElement = document.createElement("div");
    this.sidebarResizeElement.className = "sidebar-resizer-vertical";
    this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false);
    this.element.appendChild(this.sidebarResizeElement);

    this.sidebarTreeElement = document.createElement("ol");
    this.sidebarTreeElement.className = "sidebar-tree";
    this.sidebarElement.appendChild(this.sidebarTreeElement);

    this.sidebarTree = new TreeOutline(this.sidebarTreeElement);

    this.snapshotViews = document.createElement("div");
    this.snapshotViews.id = "heap-snapshot-views";
    this.element.appendChild(this.snapshotViews);

    this.snapshotButton = new WebInspector.StatusBarButton(WebInspector.UIString("Take heap snapshot."), "heap-snapshot-status-bar-item");
    this.snapshotButton.addEventListener("click", this._snapshotClicked.bind(this), false);

    this.snapshotViewStatusBarItemsContainer = document.createElement("div");
    this.snapshotViewStatusBarItemsContainer.id = "heap-snapshot-status-bar-items";

    this.reset();
};

WebInspector.HeapProfilerPanel.prototype = {
    toolbarItemClass: "heap-profiler",

    get toolbarItemLabel() {
        return WebInspector.UIString("Heap");
    },

    get statusBarItems() {
        return [this.snapshotButton.element, this.snapshotViewStatusBarItemsContainer];
    },

    show: function() {
        WebInspector.Panel.prototype.show.call(this);
        this._updateSidebarWidth();
    },

    reset: function() {
        if (this._snapshots) {
            var snapshotsLength = this._snapshots.length;
            for (var i = 0; i < snapshotsLength; ++i) {
                var snapshot = this._snapshots[i];
                delete snapshot._snapshotView;
            }
        }

        this._snapshots = [];

        this.sidebarTreeElement.removeStyleClass("some-expandable");

        this.sidebarTree.removeChildren();
        this.snapshotViews.removeChildren();

        this.snapshotViewStatusBarItemsContainer.removeChildren();
    },

    handleKeyEvent: function(event) {
        this.sidebarTree.handleKeyEvent(event);
    },

    addSnapshot: function(snapshot) {
        this._snapshots.push(snapshot);
        snapshot.list = this._snapshots;
        snapshot.listIndex = this._snapshots.length - 1;

        var snapshotsTreeElement = new WebInspector.HeapSnapshotSidebarTreeElement(snapshot);
        snapshotsTreeElement.small = false;
        snapshot._snapshotsTreeElement = snapshotsTreeElement;

        this.sidebarTree.appendChild(snapshotsTreeElement);

        this.dispatchEventToListeners("snapshot added");
    },

    showSnapshot: function(snapshot) {
        if (!snapshot)
            return;

        if (this.visibleView)
            this.visibleView.hide();
        var view = this.snapshotViewForSnapshot(snapshot);
        view.show(this.snapshotViews);
        this.visibleView = view;

        this.snapshotViewStatusBarItemsContainer.removeChildren();
        var statusBarItems = view.statusBarItems;
        for (var i = 0; i < statusBarItems.length; ++i)
            this.snapshotViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
    },

    showView: function(view)
    {
        this.showSnapshot(view.snapshot);
    },

    snapshotViewForSnapshot: function(snapshot)
    {
        if (!snapshot)
            return null;
        if (!snapshot._snapshotView)
            snapshot._snapshotView = new WebInspector.HeapSnapshotView(this, snapshot);
        return snapshot._snapshotView;
    },

    closeVisibleView: function()
    {
        if (this.visibleView)
            this.visibleView.hide();
        delete this.visibleView;
    },

    _snapshotClicked: function() {
        devtools.tools.getDebuggerAgent().startProfiling(
            devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_HEAP_SNAPSHOT |
                devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_HEAP_STATS |
                devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_JS_CONSTRUCTORS);
    },

    _startSidebarDragging: function(event)
    {
        WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize");
    },

    _sidebarDragging: function(event)
    {
        this._updateSidebarWidth(event.pageX);

        event.preventDefault();
    },

    _endSidebarDragging: function(event)
    {
        WebInspector.elementDragEnd(event);
    },

    _updateSidebarWidth: function(width)
    {
        if (this.sidebarElement.offsetWidth <= 0) {
            // The stylesheet hasn"t loaded yet or the window is closed,
            // so we can"t calculate what is need. Return early.
            return;
        }

        if (!("_currentSidebarWidth" in this))
            this._currentSidebarWidth = this.sidebarElement.offsetWidth;
        if (typeof width === "undefined")
            width = this._currentSidebarWidth;

        width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2);
        this._currentSidebarWidth = width;
        this.sidebarElement.style.width = width + "px";
        this.snapshotViews.style.left = width + "px";
        this.snapshotViewStatusBarItemsContainer.style.left = width + "px";
        this.sidebarResizeElement.style.left = (width - 3) + "px";
    }
};

WebInspector.HeapProfilerPanel.prototype.__proto__ = WebInspector.Panel.prototype;

WebInspector.HeapSnapshotView = function(parent, snapshot)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("heap-snapshot-view");

    this.parent = parent;
    this.parent.addEventListener("snapshot added", this._updateBaseOptions, this);

    this.showCountAsPercent = true;
    this.showSizeAsPercent = true;
    this.showCountDeltaAsPercent = true;
    this.showSizeDeltaAsPercent = true;

    this.summaryBar = new WebInspector.SummaryBar(this.categories);
    this.summaryBar.element.id = "heap-snapshot-summary";
    this.summaryBar.calculator = new WebInspector.HeapSummaryCalculator(snapshot.used);
    this.element.appendChild(this.summaryBar.element);

    var columns = { "cons": { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true },
                    "count": { title: WebInspector.UIString("Count"), width: "54px", sortable: true },
                    "size": { title: WebInspector.UIString("Size"), width: "72px", sort: "descending", sortable: true },
                    "countDelta": { title: WebInspector.UIString("\xb1 Count"), width: "72px", sortable: true },
                    "sizeDelta": { title: WebInspector.UIString("\xb1 Size"), width: "72px", sortable: true } };

    this.dataGrid = new WebInspector.DataGrid(columns);
    this.dataGrid.addEventListener("sorting changed", this._sortData, this);
    this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
    this.element.appendChild(this.dataGrid.element);

    this.snapshot = snapshot;

    this.baseSelectElement = document.createElement("select");
    this.baseSelectElement.className = "status-bar-item";
    this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false);
    this._updateBaseOptions();
    if (this.snapshot.listIndex > 0)
        this.baseSelectElement.selectedIndex = this.snapshot.listIndex - 1;
    else
        this.baseSelectElement.selectedIndex = this.snapshot.listIndex;
    this._resetDataGridList();

    this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item");
    this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);

    this.refresh();

    this._updatePercentButton();
};

WebInspector.HeapSnapshotView.prototype = {

    get categories()
    {
        return {code: {title: WebInspector.UIString("Code"), color: {r: 255, g: 121, b: 0}}, data: {title: WebInspector.UIString("Objects and Data"), color: {r: 47, g: 102, b: 236}}, other: {title: WebInspector.UIString("Other"), color: {r: 186, g: 186, b: 186}}};
    },

    get statusBarItems()
    {
        return [this.baseSelectElement, this.percentButton.element];
    },

    get snapshot()
    {
        return this._snapshot;
    },

    set snapshot(snapshot)
    {
        this._snapshot = snapshot;
    },

    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);
        this.dataGrid.updateWidths();
    },

    resize: function()
    {
        if (this.dataGrid)
            this.dataGrid.updateWidths();
    },

    refresh: function()
    {
        this.dataGrid.removeChildren();

        var children = this.snapshotDataGridList.children;
        var count = children.length;
        for (var index = 0; index < count; ++index)
            this.dataGrid.appendChild(children[index]);

        this._updateSummaryGraph();
    },

    refreshShowAsPercents: function()
    {
        this._updatePercentButton();
        this.refreshVisibleData();
    },

    refreshVisibleData: function()
    {
        var child = this.dataGrid.children[0];
        while (child) {
            child.refresh();
            child = child.traverseNextNode(false, null, true);
        }
        this._updateSummaryGraph();
    },

    _changeBase: function() {
        if (this.baseSnapshot === this.snapshot.list[this.baseSelectElement.selectedIndex])
            return;

        this._resetDataGridList();
        this.refresh();
    },

    _createSnapshotDataGridList: function()
    {
        if (this._snapshotDataGridList)
          delete this._snapshotDataGridList;

        this._snapshotDataGridList = new WebInspector.HeapSnapshotDataGridList(this, this.baseSnapshot.entries, this.snapshot.entries);
        return this._snapshotDataGridList;
    },

    _mouseDownInDataGrid: function(event)
    {
        if (event.detail < 2)
            return;

        var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
        if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("size-column") && !cell.hasStyleClass("countDelta-column") && !cell.hasStyleClass("sizeDelta-column")))
            return;

        if (cell.hasStyleClass("count-column"))
            this.showCountAsPercent = !this.showCountAsPercent;
        else if (cell.hasStyleClass("size-column"))
            this.showSizeAsPercent = !this.showSizeAsPercent;
        else if (cell.hasStyleClass("countDelta-column"))
            this.showCountDeltaAsPercent = !this.showCountDeltaAsPercent;
        else if (cell.hasStyleClass("sizeDelta-column"))
            this.showSizeDeltaAsPercent = !this.showSizeDeltaAsPercent;

        this.refreshShowAsPercents();

        event.preventDefault();
        event.stopPropagation();
    },

    get _isShowingAsPercent()
    {
        return this.showCountAsPercent && this.showSizeAsPercent && this.showCountDeltaAsPercent && this.showSizeDeltaAsPercent;
    },

    _percentClicked: function(event)
    {
        var currentState = this._isShowingAsPercent;
        this.showCountAsPercent = !currentState;
        this.showSizeAsPercent = !currentState;
        this.showCountDeltaAsPercent = !currentState;
        this.showSizeDeltaAsPercent = !currentState;
        this.refreshShowAsPercents();
    },

    _resetDataGridList: function()
    {
        this.baseSnapshot = this.snapshot.list[this.baseSelectElement.selectedIndex];
        var lastComparator = WebInspector.HeapSnapshotDataGridList.propertyComparator("size", false);
        if (this.snapshotDataGridList) {
            lastComparator = this.snapshotDataGridList.lastComparator;
        }
        this.snapshotDataGridList = this._createSnapshotDataGridList();
        this.snapshotDataGridList.sort(lastComparator, true);
    },

    _sortData: function()
    {
        var sortAscending = this.dataGrid.sortOrder === "ascending";
        var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
        var sortProperty = {
                "cons": "constructorName",
                "count": "count",
                "size": "size",
                "countDelta": this.showCountDeltaAsPercent ? "countDeltaPercent" : "countDelta",
                "sizeDelta": this.showSizeDeltaAsPercent ? "sizeDeltaPercent" : "sizeDelta"
        }[sortColumnIdentifier];

        this.snapshotDataGridList.sort(WebInspector.HeapSnapshotDataGridList.propertyComparator(sortProperty, sortAscending));

        this.refresh();
    },

    _updateBaseOptions: function()
    {
        // We're assuming that snapshots can only be added.
        if (this.baseSelectElement.length == this.snapshot.list.length)
            return;

        for (var i = this.baseSelectElement.length, n = this.snapshot.list.length; i < n; ++i) {
            var baseOption = document.createElement("option");
            baseOption.label = WebInspector.UIString("Compared to %s", this.snapshot.list[i].title);
            this.baseSelectElement.appendChild(baseOption);
        }
    },

    _updatePercentButton: function()
    {
        if (this._isShowingAsPercent) {
            this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes.");
            this.percentButton.toggled = true;
        } else {
            this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages.");
            this.percentButton.toggled = false;
        }
    },

    _updateSummaryGraph: function()
    {
        this.summaryBar.calculator.showAsPercent = this._isShowingAsPercent;
        this.summaryBar.update(this.snapshot.lowlevels);
    }
};

WebInspector.HeapSnapshotView.prototype.__proto__ = WebInspector.View.prototype;

WebInspector.HeapSummaryCalculator = function(total)
{
    this.total = total;
}

WebInspector.HeapSummaryCalculator.prototype = {
    computeSummaryValues: function(lowLevels)
    {
        function highFromLow(type) {
            if (type == "CODE_TYPE" || type == "SHARED_FUNCTION_INFO_TYPE" || type == "SCRIPT_TYPE") return "code";
            if (type == "STRING_TYPE" || type == "HEAP_NUMBER_TYPE" || type.match(/^JS_/) || type.match(/_ARRAY_TYPE$/)) return "data";
            return "other";
        }
        var highLevels = {data: 0, code: 0, other: 0};
        for (var item in lowLevels) {
            var highItem = highFromLow(item);
            highLevels[highItem] += lowLevels[item].size;
        }
        return {categoryValues: highLevels};
    },

    formatValue: function(value)
    {
        if (this.showAsPercent)
            return WebInspector.UIString("%.2f%%", value / this.total * 100.0);
        else
            return Number.bytesToString(value);
    },

    get showAsPercent()
    {
        return this._showAsPercent;
    },

    set showAsPercent(x)
    {
        this._showAsPercent = x;
    }
}

WebInspector.HeapSnapshotSidebarTreeElement = function(snapshot)
{
    this.snapshot = snapshot;
    this.snapshot.title = WebInspector.UIString("Snapshot %d", this.snapshot.number);

    WebInspector.SidebarTreeElement.call(this, "heap-snapshot-sidebar-tree-item", "", "", snapshot, false);

    this.refreshTitles();
};

WebInspector.HeapSnapshotSidebarTreeElement.prototype = {
    onselect: function()
    {
        WebInspector.panels.heap.showSnapshot(this.snapshot);
    },

    get mainTitle()
    {
        if (this._mainTitle)
            return this._mainTitle;
        return this.snapshot.title;
    },

    set mainTitle(x)
    {
        this._mainTitle = x;
        this.refreshTitles();
    },

    get subtitle()
    {
        if (this._subTitle)
            return this._subTitle;
        return WebInspector.UIString("Used %s of %s (%.0f%%)", Number.bytesToString(this.snapshot.used, null, false), Number.bytesToString(this.snapshot.capacity, null, false), this.snapshot.used / this.snapshot.capacity * 100.0);
    },

    set subtitle(x)
    {
        this._subTitle = x;
        this.refreshTitles();
    }
};

WebInspector.HeapSnapshotSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

WebInspector.HeapSnapshotDataGridNodeWithRetainers = function(owningTree)
{
    this.tree = owningTree;

    WebInspector.DataGridNode.call(this, null, this._hasRetainers);

    this.addEventListener("populate", this._populate, this);
};

WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype = {
    isEmptySet: function(set)
    {
        for (var x in set)
            return false;
        return true;
    },

    get _hasRetainers()
    {
        return !this.isEmptySet(this.retainers);
    },

    get _parent()
    {
        // For top-level nodes, return owning tree as a parent, not data grid.
        return this.parent !== this.dataGrid ? this.parent : this.tree;
    },

    _populate: function(event)
    {
        for (var retainer in this.retainers) {
            this.appendChild(new WebInspector.HeapSnapshotDataGridRetainerNode(this.snapshotView, null, this.retainers[retainer], this.tree));
        }

        if (this._parent) {
            var currentComparator = this._parent.lastComparator;
            if (currentComparator)
                this.sort(currentComparator, true);
        }

        this.removeEventListener("populate", this._populate, this);
    },

    sort: function(comparator, force) {
        if (!force && this.lastComparator === comparator)
            return;

        this.children.sort(comparator);
        var childCount = this.children.length;           
        for (var childIndex = 0; childIndex < childCount; ++childIndex)
            this.children[childIndex]._recalculateSiblings(childIndex);            
        for (var i = 0; i < this.children.length; ++i) {
            var child = this.children[i];
            if (!force && (!child.expanded || child.lastComparator === comparator))
                continue;
            child.sort(comparator, force);
        }
        this.lastComparator = comparator;
    },

    getTotalCount: function() {
        if (!this._count) {
            this._count = 0;
            for (var i = 0, n = this.children.length; i < n; ++i) {
                this._count += this.children[i].count;
            }
        }
        return this._count;
    },

    getTotalSize: function() {
        if (!this._size) {
            this._size = 0;
            for (var i = 0, n = this.children.length; i < n; ++i) {
                this._size += this.children[i].size;
            }
        }
        return this._size;
    },

    get countPercent()
    {
        return this.count / this._parent.getTotalCount() * 100.0;
    },

    get sizePercent()
    {
        return this.size / this._parent.getTotalSize() * 100.0;
    }
};

WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.__proto__ = WebInspector.DataGridNode.prototype;

WebInspector.HeapSnapshotDataGridNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
{
    this.snapshotView = snapshotView;

    if (!snapshotEntry)
        snapshotEntry = { cons: baseEntry.cons, count: 0, size: 0, retainers: {} };
    this.constructorName = snapshotEntry.cons;
    this.count = snapshotEntry.count;
    this.size = snapshotEntry.size;
    this.retainers = snapshotEntry.retainers;

    if (!baseEntry)
        baseEntry = { count: 0, size: 0, retainers: {} };
    this.baseCount = baseEntry.count;
    this.countDelta = this.count - this.baseCount;
    this.baseSize = baseEntry.size;
    this.sizeDelta = this.size - this.baseSize;
    this.baseRetainers = baseEntry.retainers;

    WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
};

WebInspector.HeapSnapshotDataGridNode.prototype = {
    get data()
    {
        var data = {};

        data["cons"] = this.constructorName;

        if (this.snapshotView.showCountAsPercent)
            data["count"] = WebInspector.UIString("%.2f%%", this.countPercent);
        else
            data["count"] = this.count;

        if (this.snapshotView.showSizeAsPercent)
            data["size"] = WebInspector.UIString("%.2f%%", this.sizePercent);
        else
            data["size"] = Number.bytesToString(this.size);

        function signForDelta(delta) {
            if (delta == 0)
                return "";
            if (delta > 0)
                return "+";
            else
                // Math minus sign, same width as plus.
                return "\u2212";
        }

        function showDeltaAsPercent(value) {
            if (value === Number.POSITIVE_INFINITY)
                return WebInspector.UIString("new");
            else if (value === Number.NEGATIVE_INFINITY)
                return WebInspector.UIString("deleted");
            if (value > 1000.0)
                return WebInspector.UIString("%s >1000%%", signForDelta(value));
            return WebInspector.UIString("%s%.2f%%", signForDelta(value), Math.abs(value));
        }

        if (this.snapshotView.showCountDeltaAsPercent)
            data["countDelta"] = showDeltaAsPercent(this.countDeltaPercent);
        else
            data["countDelta"] = WebInspector.UIString("%s%d", signForDelta(this.countDelta), Math.abs(this.countDelta));

        if (this.snapshotView.showSizeDeltaAsPercent)
            data["sizeDelta"] = showDeltaAsPercent(this.sizeDeltaPercent);
        else
            data["sizeDelta"] = WebInspector.UIString("%s%s", signForDelta(this.sizeDelta), Number.bytesToString(Math.abs(this.sizeDelta)));

        return data;
    },

    get countDeltaPercent()
    {
        if (this.baseCount > 0) {
            if (this.count > 0)
                return this.countDelta / this.baseCount * 100.0;
            else
                return Number.NEGATIVE_INFINITY;
        } else
            return Number.POSITIVE_INFINITY;
    },

    get sizeDeltaPercent()
    {
        if (this.baseSize > 0) {
            if (this.size > 0)
                return this.sizeDelta / this.baseSize * 100.0;
            else
                return Number.NEGATIVE_INFINITY;
        } else
            return Number.POSITIVE_INFINITY;
    }
};

WebInspector.HeapSnapshotDataGridNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;

WebInspector.HeapSnapshotDataGridList = function(snapshotView, baseEntries, snapshotEntries)
{
    this.tree = this;
    this.snapshotView = snapshotView;
    this.children = [];
    this.lastComparator = null;
    this.populateChildren(baseEntries, snapshotEntries);
};

WebInspector.HeapSnapshotDataGridList.prototype = {
    appendChild: function(child)
    {
        this.insertChild(child, this.children.length);
    },

    insertChild: function(child, index)
    {
        this.children.splice(index, 0, child);
    },

    removeChildren: function()
    {
        this.children = [];
    },

    produceDiff: function(baseEntries, currentEntries, callback)
    {
        for (var item in currentEntries)
            callback(baseEntries[item], currentEntries[item]);

        for (item in baseEntries) {
            if (!(item in currentEntries))
                callback(baseEntries[item], null);
        }
    },

    populateChildren: function(baseEntries, snapshotEntries)
    {
        var self = this;
        this.produceDiff(baseEntries, snapshotEntries, function(baseItem, snapshotItem) {
            self.appendChild(new WebInspector.HeapSnapshotDataGridNode(self.snapshotView, baseItem, snapshotItem, self));
        });
    },

    sort: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.sort,
    getTotalCount: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalCount,
    getTotalSize: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalSize
};

WebInspector.HeapSnapshotDataGridList.propertyComparators = [{}, {}];

WebInspector.HeapSnapshotDataGridList.propertyComparator = function(property, isAscending)
{
    var comparator = this.propertyComparators[(isAscending ? 1 : 0)][property];
    if (!comparator) {
        comparator = function(lhs, rhs) {
            var l = lhs[property], r = rhs[property];
            var result = l < r ? -1 : (l > r ? 1 : 0);
            return isAscending ? result : -result;
        };
        this.propertyComparators[(isAscending ? 1 : 0)][property] = comparator;
    }
    return comparator;
};

WebInspector.HeapSnapshotDataGridRetainerNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
{
    this.snapshotView = snapshotView;

    if (!snapshotEntry)
        snapshotEntry = { cons: baseEntry.cons, count: 0, clusters: {} };
    this.constructorName = snapshotEntry.cons;
    this.count = snapshotEntry.count;
    this.retainers = {};
    if (this.isEmptySet(snapshotEntry.clusters)) {
        if (this.constructorName in this.snapshotView.snapshot.entries)
            this.retainers = this.snapshotView.snapshot.entries[this.constructorName].retainers;
    } else {
        // In case when an entry is retained by clusters, we need to gather up the list
        // of retainers by merging retainers of every cluster.
        // E.g. having such a tree:
        //   A
        //     Object:1  10
        //       X       3
        //       Y       4
        //     Object:2  5
        //       X       6
        //
        // will result in a following retainers list: X 9, Y 4.
        for (var clusterName in snapshotEntry.clusters) {
            if (clusterName in this.snapshotView.snapshot.clusters) {
                var clusterRetainers = this.snapshotView.snapshot.clusters[clusterName].retainers;
                for (var clusterRetainer in clusterRetainers) {
                    var clusterRetainerEntry = clusterRetainers[clusterRetainer];
                    if (!(clusterRetainer in this.retainers))
                        this.retainers[clusterRetainer] = { cons: clusterRetainerEntry.cons, count: 0, clusters: {} };
                    this.retainers[clusterRetainer].count += clusterRetainerEntry.count;
                    for (var clusterRetainerCluster in clusterRetainerEntry.clusters)
                        this.retainers[clusterRetainer].clusters[clusterRetainerCluster] = true;
                }
            }
        }
    }  

    if (!baseEntry)
        baseEntry = { count: 0, clusters: {} };
    this.baseCount = baseEntry.count;
    this.countDelta = this.count - this.baseCount;

    this.size = this.count;  // This way, when sorting by sizes entries will be sorted by references count.

    WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
}

WebInspector.HeapSnapshotDataGridRetainerNode.prototype = {
    get data()
    {
        var data = {};

        data["cons"] = this.constructorName;
        if (this.snapshotView.showCountAsPercent)
            data["count"] = WebInspector.UIString("%.2f%%", this.countPercent);
        else
            data["count"] = this.count;
        data["size"] = "";
        data["countDelta"] = "";
        data["sizeDelta"] = "";

        return data;
    }
};

WebInspector.HeapSnapshotDataGridRetainerNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;

