• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2011 Google 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 are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * @constructor
33 */
34WebInspector.HeapSnapshotArraySlice = function(array, start, end)
35{
36    this._array = array;
37    this._start = start;
38    this.length = end - start;
39}
40
41WebInspector.HeapSnapshotArraySlice.prototype = {
42    item: function(index)
43    {
44        return this._array[this._start + index];
45    },
46
47    slice: function(start, end)
48    {
49        if (typeof end === "undefined")
50            end = this.length;
51        return this._array.subarray(this._start + start, this._start + end);
52    }
53}
54
55/**
56 * @constructor
57 * @param {number=} edgeIndex
58 */
59WebInspector.HeapSnapshotEdge = function(snapshot, edges, edgeIndex)
60{
61    this._snapshot = snapshot;
62    this._edges = edges;
63    this.edgeIndex = edgeIndex || 0;
64}
65
66WebInspector.HeapSnapshotEdge.prototype = {
67    clone: function()
68    {
69        return new WebInspector.HeapSnapshotEdge(this._snapshot, this._edges, this.edgeIndex);
70    },
71
72    hasStringName: function()
73    {
74        throw new Error("Not implemented");
75    },
76
77    name: function()
78    {
79        throw new Error("Not implemented");
80    },
81
82    node: function()
83    {
84        return this._snapshot.createNode(this.nodeIndex());
85    },
86
87    nodeIndex: function()
88    {
89        return this._edges.item(this.edgeIndex + this._snapshot._edgeToNodeOffset);
90    },
91
92    rawEdges: function()
93    {
94        return this._edges;
95    },
96
97    toString: function()
98    {
99        return "HeapSnapshotEdge: " + this.name();
100    },
101
102    type: function()
103    {
104        return this._snapshot._edgeTypes[this._type()];
105    },
106
107    serialize: function()
108    {
109        var node = this.node();
110        return {
111            name: this.name(),
112            node: node.serialize(),
113            nodeIndex: this.nodeIndex(),
114            type: this.type(),
115            distance: node.distance()
116        };
117    },
118
119    _type: function()
120    {
121        return this._edges.item(this.edgeIndex + this._snapshot._edgeTypeOffset);
122    }
123};
124
125/**
126 * @constructor
127 */
128WebInspector.HeapSnapshotEdgeIterator = function(edge)
129{
130    this.edge = edge;
131}
132
133WebInspector.HeapSnapshotEdgeIterator.prototype = {
134    rewind: function()
135    {
136        this.edge.edgeIndex = 0;
137    },
138
139    hasNext: function()
140    {
141        return this.edge.edgeIndex < this.edge._edges.length;
142    },
143
144    index: function()
145    {
146        return this.edge.edgeIndex;
147    },
148
149    setIndex: function(newIndex)
150    {
151        this.edge.edgeIndex = newIndex;
152    },
153
154    item: function()
155    {
156        return this.edge;
157    },
158
159    next: function()
160    {
161        this.edge.edgeIndex += this.edge._snapshot._edgeFieldsCount;
162    }
163};
164
165/**
166 * @constructor
167 */
168WebInspector.HeapSnapshotRetainerEdge = function(snapshot, retainedNodeIndex, retainerIndex)
169{
170    this._snapshot = snapshot;
171    this._retainedNodeIndex = retainedNodeIndex;
172
173    var retainedNodeOrdinal = retainedNodeIndex / snapshot._nodeFieldCount;
174    this._firstRetainer = snapshot._firstRetainerIndex[retainedNodeOrdinal];
175    this._retainersCount = snapshot._firstRetainerIndex[retainedNodeOrdinal + 1] - this._firstRetainer;
176
177    this.setRetainerIndex(retainerIndex);
178}
179
180WebInspector.HeapSnapshotRetainerEdge.prototype = {
181    clone: function()
182    {
183        return new WebInspector.HeapSnapshotRetainerEdge(this._snapshot, this._retainedNodeIndex, this.retainerIndex());
184    },
185
186    hasStringName: function()
187    {
188        return this._edge().hasStringName();
189    },
190
191    name: function()
192    {
193        return this._edge().name();
194    },
195
196    node: function()
197    {
198        return this._node();
199    },
200
201    nodeIndex: function()
202    {
203        return this._nodeIndex;
204    },
205
206    retainerIndex: function()
207    {
208        return this._retainerIndex;
209    },
210
211    setRetainerIndex: function(newIndex)
212    {
213        if (newIndex !== this._retainerIndex) {
214            this._retainerIndex = newIndex;
215            this.edgeIndex = newIndex;
216        }
217    },
218
219    set edgeIndex(edgeIndex)
220    {
221        var retainerIndex = this._firstRetainer + edgeIndex;
222        this._globalEdgeIndex = this._snapshot._retainingEdges[retainerIndex];
223        this._nodeIndex = this._snapshot._retainingNodes[retainerIndex];
224        delete this._edgeInstance;
225        delete this._nodeInstance;
226    },
227
228    _node: function()
229    {
230        if (!this._nodeInstance)
231            this._nodeInstance = this._snapshot.createNode(this._nodeIndex);
232        return this._nodeInstance;
233    },
234
235    _edge: function()
236    {
237        if (!this._edgeInstance) {
238            var edgeIndex = this._globalEdgeIndex - this._node()._edgeIndexesStart();
239            this._edgeInstance = this._snapshot.createEdge(this._node().rawEdges(), edgeIndex);
240        }
241        return this._edgeInstance;
242    },
243
244    toString: function()
245    {
246        return this._edge().toString();
247    },
248
249    serialize: function()
250    {
251        var node = this.node();
252        return {
253            name: this.name(),
254            node: node.serialize(),
255            nodeIndex: this.nodeIndex(),
256            type: this.type(),
257            distance: node.distance()
258        };
259    },
260
261    type: function()
262    {
263        return this._edge().type();
264    }
265}
266
267/**
268 * @constructor
269 */
270WebInspector.HeapSnapshotRetainerEdgeIterator = function(retainer)
271{
272    this.retainer = retainer;
273}
274
275WebInspector.HeapSnapshotRetainerEdgeIterator.prototype = {
276    rewind: function()
277    {
278        this.retainer.setRetainerIndex(0);
279    },
280
281    hasNext: function()
282    {
283        return this.retainer.retainerIndex() < this.retainer._retainersCount;
284    },
285
286    index: function()
287    {
288        return this.retainer.retainerIndex();
289    },
290
291    setIndex: function(newIndex)
292    {
293        this.retainer.setRetainerIndex(newIndex);
294    },
295
296    item: function()
297    {
298        return this.retainer;
299    },
300
301    next: function()
302    {
303        this.retainer.setRetainerIndex(this.retainer.retainerIndex() + 1);
304    }
305};
306
307/**
308 * @constructor
309 * @param {number=} nodeIndex
310 */
311WebInspector.HeapSnapshotNode = function(snapshot, nodeIndex)
312{
313    this._snapshot = snapshot;
314    this._firstNodeIndex = nodeIndex;
315    this.nodeIndex = nodeIndex;
316}
317
318WebInspector.HeapSnapshotNode.prototype = {
319    distance: function()
320    {
321        return this._snapshot._nodeDistances[this.nodeIndex / this._snapshot._nodeFieldCount];
322    },
323
324    className: function()
325    {
326        throw new Error("Not implemented");
327    },
328
329    classIndex: function()
330    {
331        throw new Error("Not implemented");
332    },
333
334    dominatorIndex: function()
335    {
336        var nodeFieldCount = this._snapshot._nodeFieldCount;
337        return this._snapshot._dominatorsTree[this.nodeIndex / this._snapshot._nodeFieldCount] * nodeFieldCount;
338    },
339
340    edges: function()
341    {
342        return new WebInspector.HeapSnapshotEdgeIterator(this._snapshot.createEdge(this.rawEdges(), 0));
343    },
344
345    edgesCount: function()
346    {
347        return (this._edgeIndexesEnd() - this._edgeIndexesStart()) / this._snapshot._edgeFieldsCount;
348    },
349
350    id: function()
351    {
352        throw new Error("Not implemented");
353    },
354
355    isRoot: function()
356    {
357        return this.nodeIndex === this._snapshot._rootNodeIndex;
358    },
359
360    name: function()
361    {
362        return this._snapshot._strings[this._name()];
363    },
364
365    rawEdges: function()
366    {
367        return new WebInspector.HeapSnapshotArraySlice(this._snapshot._containmentEdges, this._edgeIndexesStart(), this._edgeIndexesEnd());
368    },
369
370    retainedSize: function()
371    {
372        var snapshot = this._snapshot;
373        return snapshot._nodes[this.nodeIndex + snapshot._nodeRetainedSizeOffset];
374    },
375
376    retainers: function()
377    {
378        return new WebInspector.HeapSnapshotRetainerEdgeIterator(this._snapshot.createRetainingEdge(this.nodeIndex, 0));
379    },
380
381    selfSize: function()
382    {
383        var snapshot = this._snapshot;
384        return snapshot._nodes[this.nodeIndex + snapshot._nodeSelfSizeOffset];
385    },
386
387    type: function()
388    {
389        return this._snapshot._nodeTypes[this._type()];
390    },
391
392    serialize: function()
393    {
394        return {
395            id: this.id(),
396            name: this.name(),
397            distance: this.distance(),
398            nodeIndex: this.nodeIndex,
399            retainedSize: this.retainedSize(),
400            selfSize: this.selfSize(),
401            type: this.type(),
402        };
403    },
404
405    _name: function()
406    {
407        var snapshot = this._snapshot;
408        return snapshot._nodes[this.nodeIndex + snapshot._nodeNameOffset];
409    },
410
411    _edgeIndexesStart: function()
412    {
413        return this._snapshot._firstEdgeIndexes[this._ordinal()];
414    },
415
416    _edgeIndexesEnd: function()
417    {
418        return this._snapshot._firstEdgeIndexes[this._ordinal() + 1];
419    },
420
421    _ordinal: function()
422    {
423        return this.nodeIndex / this._snapshot._nodeFieldCount;
424    },
425
426    _nextNodeIndex: function()
427    {
428        return this.nodeIndex + this._snapshot._nodeFieldCount;
429    },
430
431    _type: function()
432    {
433        var snapshot = this._snapshot;
434        return snapshot._nodes[this.nodeIndex + snapshot._nodeTypeOffset];
435    }
436};
437
438/**
439 * @constructor
440 */
441WebInspector.HeapSnapshotNodeIterator = function(node)
442{
443    this.node = node;
444    this._nodesLength = node._snapshot._nodes.length;
445}
446
447WebInspector.HeapSnapshotNodeIterator.prototype = {
448    rewind: function()
449    {
450        this.node.nodeIndex = this.node._firstNodeIndex;
451    },
452
453    hasNext: function()
454    {
455        return this.node.nodeIndex < this._nodesLength;
456    },
457
458    index: function()
459    {
460        return this.node.nodeIndex;
461    },
462
463    setIndex: function(newIndex)
464    {
465        this.node.nodeIndex = newIndex;
466    },
467
468    item: function()
469    {
470        return this.node;
471    },
472
473    next: function()
474    {
475        this.node.nodeIndex = this.node._nextNodeIndex();
476    }
477}
478
479
480/**
481 * @param {!WebInspector.HeapSnapshotWorkerDispatcher=} dispatcher
482 * @constructor
483 */
484WebInspector.HeapSnapshotProgress = function(dispatcher)
485{
486    this._dispatcher = dispatcher;
487}
488
489WebInspector.HeapSnapshotProgress.Event = {
490    Update: "ProgressUpdate"
491};
492
493WebInspector.HeapSnapshotProgress.prototype = {
494    /**
495     * @param {string} status
496     */
497    updateStatus: function(status)
498    {
499        this._sendUpdateEvent(WebInspector.UIString(status));
500    },
501
502    /**
503     * @param {string} title
504     * @param {number} value
505     * @param {number} total
506     */
507    updateProgress: function(title, value, total)
508    {
509        var percentValue = ((total ? (value / total) : 0) * 100).toFixed(0);
510        this._sendUpdateEvent(WebInspector.UIString(title, percentValue));
511    },
512
513    /**
514     * @param {string} text
515     */
516    _sendUpdateEvent: function(text)
517    {
518        // May be undefined in tests.
519        if (this._dispatcher)
520            this._dispatcher.sendEvent(WebInspector.HeapSnapshotProgress.Event.Update, text);
521    }
522}
523
524
525/**
526 * @param {!WebInspector.HeapSnapshotProgress} progress
527 * @constructor
528 */
529WebInspector.HeapSnapshot = function(profile, progress)
530{
531    this.uid = profile.snapshot.uid;
532    this._nodes = profile.nodes;
533    this._containmentEdges = profile.edges;
534    /** @type {!HeapSnapshotMetainfo} */
535    this._metaNode = profile.snapshot.meta;
536    this._strings = profile.strings;
537    this._progress = progress;
538
539    this._noDistance = -5;
540    this._rootNodeIndex = 0;
541    if (profile.snapshot.root_index)
542        this._rootNodeIndex = profile.snapshot.root_index;
543
544    this._snapshotDiffs = {};
545    this._aggregatesForDiff = null;
546
547    this._init();
548
549    if (WebInspector.HeapSnapshot.enableAllocationProfiler) {
550        this._progress.updateStatus("Buiding allocation statistics\u2026");
551        this._allocationProfile = new WebInspector.AllocationProfile(profile);
552        this._progress.updateStatus("Done");
553    }
554}
555
556WebInspector.HeapSnapshot.enableAllocationProfiler = false;
557
558/**
559 * @constructor
560 */
561function HeapSnapshotMetainfo()
562{
563    // New format.
564    this.node_fields = [];
565    this.node_types = [];
566    this.edge_fields = [];
567    this.edge_types = [];
568    this.trace_function_info_fields = [];
569    this.trace_node_fields = [];
570    this.type_strings = {};
571}
572
573/**
574 * @constructor
575 */
576function HeapSnapshotHeader()
577{
578    // New format.
579    this.title = "";
580    this.uid = 0;
581    this.meta = new HeapSnapshotMetainfo();
582    this.node_count = 0;
583    this.edge_count = 0;
584}
585
586WebInspector.HeapSnapshot.prototype = {
587    _init: function()
588    {
589        var meta = this._metaNode;
590
591        this._nodeTypeOffset = meta.node_fields.indexOf("type");
592        this._nodeNameOffset = meta.node_fields.indexOf("name");
593        this._nodeIdOffset = meta.node_fields.indexOf("id");
594        this._nodeSelfSizeOffset = meta.node_fields.indexOf("self_size");
595        this._nodeEdgeCountOffset = meta.node_fields.indexOf("edge_count");
596        this._nodeFieldCount = meta.node_fields.length;
597
598        this._nodeTypes = meta.node_types[this._nodeTypeOffset];
599        this._nodeHiddenType = this._nodeTypes.indexOf("hidden");
600        this._nodeObjectType = this._nodeTypes.indexOf("object");
601        this._nodeNativeType = this._nodeTypes.indexOf("native");
602        this._nodeConsStringType = this._nodeTypes.indexOf("concatenated string");
603        this._nodeSlicedStringType = this._nodeTypes.indexOf("sliced string");
604        this._nodeCodeType = this._nodeTypes.indexOf("code");
605        this._nodeSyntheticType = this._nodeTypes.indexOf("synthetic");
606
607        this._edgeFieldsCount = meta.edge_fields.length;
608        this._edgeTypeOffset = meta.edge_fields.indexOf("type");
609        this._edgeNameOffset = meta.edge_fields.indexOf("name_or_index");
610        this._edgeToNodeOffset = meta.edge_fields.indexOf("to_node");
611
612        this._edgeTypes = meta.edge_types[this._edgeTypeOffset];
613        this._edgeTypes.push("invisible");
614        this._edgeElementType = this._edgeTypes.indexOf("element");
615        this._edgeHiddenType = this._edgeTypes.indexOf("hidden");
616        this._edgeInternalType = this._edgeTypes.indexOf("internal");
617        this._edgeShortcutType = this._edgeTypes.indexOf("shortcut");
618        this._edgeWeakType = this._edgeTypes.indexOf("weak");
619        this._edgeInvisibleType = this._edgeTypes.indexOf("invisible");
620
621        this.nodeCount = this._nodes.length / this._nodeFieldCount;
622        this._edgeCount = this._containmentEdges.length / this._edgeFieldsCount;
623
624        this._progress.updateStatus("Building edge indexes\u2026");
625        this._buildEdgeIndexes();
626        this._progress.updateStatus("Marking invisible edges\u2026");
627        this._markInvisibleEdges();
628        this._progress.updateStatus("Building retainers\u2026");
629        this._buildRetainers();
630        this._progress.updateStatus("Calculating node flags\u2026");
631        this._calculateFlags();
632        this._progress.updateStatus("Calculating distances\u2026");
633        this._calculateDistances();
634        this._progress.updateStatus("Building postorder index\u2026");
635        var result = this._buildPostOrderIndex();
636        // Actually it is array that maps node ordinal number to dominator node ordinal number.
637        this._progress.updateStatus("Building dominator tree\u2026");
638        this._dominatorsTree = this._buildDominatorTree(result.postOrderIndex2NodeOrdinal, result.nodeOrdinal2PostOrderIndex);
639        this._progress.updateStatus("Calculating retained sizes\u2026");
640        this._calculateRetainedSizes(result.postOrderIndex2NodeOrdinal);
641        this._progress.updateStatus("Buiding dominated nodes\u2026");
642        this._buildDominatedNodes();
643        this._progress.updateStatus("Finished processing.");
644    },
645
646    _buildEdgeIndexes: function()
647    {
648        var nodes = this._nodes;
649        var nodeCount = this.nodeCount;
650        var firstEdgeIndexes = this._firstEdgeIndexes = new Uint32Array(nodeCount + 1);
651        var nodeFieldCount = this._nodeFieldCount;
652        var edgeFieldsCount = this._edgeFieldsCount;
653        var nodeEdgeCountOffset = this._nodeEdgeCountOffset;
654        firstEdgeIndexes[nodeCount] = this._containmentEdges.length;
655        for (var nodeOrdinal = 0, edgeIndex = 0; nodeOrdinal < nodeCount; ++nodeOrdinal) {
656            firstEdgeIndexes[nodeOrdinal] = edgeIndex;
657            edgeIndex += nodes[nodeOrdinal * nodeFieldCount + nodeEdgeCountOffset] * edgeFieldsCount;
658        }
659    },
660
661    _buildRetainers: function()
662    {
663        var retainingNodes = this._retainingNodes = new Uint32Array(this._edgeCount);
664        var retainingEdges = this._retainingEdges = new Uint32Array(this._edgeCount);
665        // Index of the first retainer in the _retainingNodes and _retainingEdges
666        // arrays. Addressed by retained node index.
667        var firstRetainerIndex = this._firstRetainerIndex = new Uint32Array(this.nodeCount + 1);
668
669        var containmentEdges = this._containmentEdges;
670        var edgeFieldsCount = this._edgeFieldsCount;
671        var nodeFieldCount = this._nodeFieldCount;
672        var edgeToNodeOffset = this._edgeToNodeOffset;
673        var firstEdgeIndexes = this._firstEdgeIndexes;
674        var nodeCount = this.nodeCount;
675
676        for (var toNodeFieldIndex = edgeToNodeOffset, l = containmentEdges.length; toNodeFieldIndex < l; toNodeFieldIndex += edgeFieldsCount) {
677            var toNodeIndex = containmentEdges[toNodeFieldIndex];
678            if (toNodeIndex % nodeFieldCount)
679                throw new Error("Invalid toNodeIndex " + toNodeIndex);
680            ++firstRetainerIndex[toNodeIndex / nodeFieldCount];
681        }
682        for (var i = 0, firstUnusedRetainerSlot = 0; i < nodeCount; i++) {
683            var retainersCount = firstRetainerIndex[i];
684            firstRetainerIndex[i] = firstUnusedRetainerSlot;
685            retainingNodes[firstUnusedRetainerSlot] = retainersCount;
686            firstUnusedRetainerSlot += retainersCount;
687        }
688        firstRetainerIndex[nodeCount] = retainingNodes.length;
689
690        var nextNodeFirstEdgeIndex = firstEdgeIndexes[0];
691        for (var srcNodeOrdinal = 0; srcNodeOrdinal < nodeCount; ++srcNodeOrdinal) {
692            var firstEdgeIndex = nextNodeFirstEdgeIndex;
693            nextNodeFirstEdgeIndex = firstEdgeIndexes[srcNodeOrdinal + 1];
694            var srcNodeIndex = srcNodeOrdinal * nodeFieldCount;
695            for (var edgeIndex = firstEdgeIndex; edgeIndex < nextNodeFirstEdgeIndex; edgeIndex += edgeFieldsCount) {
696                var toNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
697                if (toNodeIndex % nodeFieldCount)
698                    throw new Error("Invalid toNodeIndex " + toNodeIndex);
699                var firstRetainerSlotIndex = firstRetainerIndex[toNodeIndex / nodeFieldCount];
700                var nextUnusedRetainerSlotIndex = firstRetainerSlotIndex + (--retainingNodes[firstRetainerSlotIndex]);
701                retainingNodes[nextUnusedRetainerSlotIndex] = srcNodeIndex;
702                retainingEdges[nextUnusedRetainerSlotIndex] = edgeIndex;
703            }
704        }
705    },
706
707    /**
708     * @param {number=} nodeIndex
709     */
710    createNode: function(nodeIndex)
711    {
712        throw new Error("Not implemented");
713    },
714
715    createEdge: function(edges, edgeIndex)
716    {
717        throw new Error("Not implemented");
718    },
719
720    createRetainingEdge: function(retainedNodeIndex, retainerIndex)
721    {
722        throw new Error("Not implemented");
723    },
724
725    dispose: function()
726    {
727        delete this._nodes;
728        delete this._strings;
729        delete this._retainingEdges;
730        delete this._retainingNodes;
731        delete this._firstRetainerIndex;
732        if (this._aggregates) {
733            delete this._aggregates;
734            delete this._aggregatesSortedFlags;
735        }
736        delete this._dominatedNodes;
737        delete this._firstDominatedNodeIndex;
738        delete this._nodeDistances;
739        delete this._dominatorsTree;
740    },
741
742    _allNodes: function()
743    {
744        return new WebInspector.HeapSnapshotNodeIterator(this.rootNode());
745    },
746
747    rootNode: function()
748    {
749        return this.createNode(this._rootNodeIndex);
750    },
751
752    get rootNodeIndex()
753    {
754        return this._rootNodeIndex;
755    },
756
757    get totalSize()
758    {
759        return this.rootNode().retainedSize();
760    },
761
762    _getDominatedIndex: function(nodeIndex)
763    {
764        if (nodeIndex % this._nodeFieldCount)
765            throw new Error("Invalid nodeIndex: " + nodeIndex);
766        return this._firstDominatedNodeIndex[nodeIndex / this._nodeFieldCount];
767    },
768
769    _dominatedNodesOfNode: function(node)
770    {
771        var dominatedIndexFrom = this._getDominatedIndex(node.nodeIndex);
772        var dominatedIndexTo = this._getDominatedIndex(node._nextNodeIndex());
773        return new WebInspector.HeapSnapshotArraySlice(this._dominatedNodes, dominatedIndexFrom, dominatedIndexTo);
774    },
775
776    /**
777     * @param {boolean} sortedIndexes
778     * @param {string} key
779     * @param {string=} filterString
780     */
781    aggregates: function(sortedIndexes, key, filterString)
782    {
783        if (!this._aggregates) {
784            this._aggregates = {};
785            this._aggregatesSortedFlags = {};
786        }
787
788        var aggregatesByClassName = this._aggregates[key];
789        if (aggregatesByClassName) {
790            if (sortedIndexes && !this._aggregatesSortedFlags[key]) {
791                this._sortAggregateIndexes(aggregatesByClassName);
792                this._aggregatesSortedFlags[key] = sortedIndexes;
793            }
794            return aggregatesByClassName;
795        }
796
797        var filter;
798        if (filterString)
799            filter = this._parseFilter(filterString);
800
801        var aggregates = this._buildAggregates(filter);
802        this._calculateClassesRetainedSize(aggregates.aggregatesByClassIndex, filter);
803        aggregatesByClassName = aggregates.aggregatesByClassName;
804
805        if (sortedIndexes)
806            this._sortAggregateIndexes(aggregatesByClassName);
807
808        this._aggregatesSortedFlags[key] = sortedIndexes;
809        this._aggregates[key] = aggregatesByClassName;
810
811        return aggregatesByClassName;
812    },
813
814    allocationTracesTops: function()
815    {
816        return this._allocationProfile.serializeTraceTops();
817    },
818
819    allocationNodeCallers: function(nodeId)
820    {
821        return this._allocationProfile.serializeCallers(nodeId);
822    },
823
824    aggregatesForDiff: function()
825    {
826        if (this._aggregatesForDiff)
827            return this._aggregatesForDiff;
828
829        var aggregatesByClassName = this.aggregates(true, "allObjects");
830        this._aggregatesForDiff  = {};
831
832        var node = this.createNode();
833        for (var className in aggregatesByClassName) {
834            var aggregate = aggregatesByClassName[className];
835            var indexes = aggregate.idxs;
836            var ids = new Array(indexes.length);
837            var selfSizes = new Array(indexes.length);
838            for (var i = 0; i < indexes.length; i++) {
839                node.nodeIndex = indexes[i];
840                ids[i] = node.id();
841                selfSizes[i] = node.selfSize();
842            }
843
844            this._aggregatesForDiff[className] = {
845                indexes: indexes,
846                ids: ids,
847                selfSizes: selfSizes
848            };
849        }
850        return this._aggregatesForDiff;
851    },
852
853    /**
854     * @param {!WebInspector.HeapSnapshotNode} node
855     * @return {!boolean}
856     */
857    _isUserRoot: function(node)
858    {
859        return true;
860    },
861
862    /**
863     * @param {function(!WebInspector.HeapSnapshotNode)} action
864     * @param {boolean=} userRootsOnly
865     */
866    forEachRoot: function(action, userRootsOnly)
867    {
868        for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
869            var node = iter.edge.node();
870            if (!userRootsOnly || this._isUserRoot(node))
871                action(node);
872        }
873    },
874
875    _calculateDistances: function()
876    {
877        var nodeFieldCount = this._nodeFieldCount;
878        var nodeCount = this.nodeCount;
879        var distances = new Int32Array(nodeCount);
880        var noDistance = this._noDistance;
881        for (var i = 0; i < nodeCount; ++i)
882            distances[i] = noDistance;
883
884        var nodesToVisit = new Uint32Array(this.nodeCount);
885        var nodesToVisitLength = 0;
886
887        /**
888         * @param {!WebInspector.HeapSnapshotNode} node
889         */
890        function enqueueNode(node)
891        {
892            var ordinal = node._ordinal();
893            if (distances[ordinal] !== noDistance)
894                return;
895            distances[ordinal] = 0;
896            nodesToVisit[nodesToVisitLength++] = node.nodeIndex;
897        }
898
899        this.forEachRoot(enqueueNode, true);
900        this._bfs(nodesToVisit, nodesToVisitLength, distances);
901
902        // bfs for the rest of objects
903        nodesToVisitLength = 0;
904        this.forEachRoot(enqueueNode);
905        this._bfs(nodesToVisit, nodesToVisitLength, distances);
906
907        this._nodeDistances = distances;
908    },
909
910    /**
911     * @param {!Uint32Array} nodesToVisit
912     * @param {!number} nodesToVisitLength
913     * @param {!Int32Array} distances
914     */
915    _bfs: function(nodesToVisit, nodesToVisitLength, distances)
916    {
917        // Preload fields into local variables for better performance.
918        var edgeFieldsCount = this._edgeFieldsCount;
919        var nodeFieldCount = this._nodeFieldCount;
920        var containmentEdges = this._containmentEdges;
921        var firstEdgeIndexes = this._firstEdgeIndexes;
922        var edgeToNodeOffset = this._edgeToNodeOffset;
923        var edgeTypeOffset = this._edgeTypeOffset;
924        var nodeCount = this.nodeCount;
925        var containmentEdgesLength = containmentEdges.length;
926        var edgeWeakType = this._edgeWeakType;
927        var noDistance = this._noDistance;
928
929        var index = 0;
930        while (index < nodesToVisitLength) {
931            var nodeIndex = nodesToVisit[index++]; // shift generates too much garbage.
932            var nodeOrdinal = nodeIndex / nodeFieldCount;
933            var distance = distances[nodeOrdinal] + 1;
934            var firstEdgeIndex = firstEdgeIndexes[nodeOrdinal];
935            var edgesEnd = firstEdgeIndexes[nodeOrdinal + 1];
936            for (var edgeIndex = firstEdgeIndex; edgeIndex < edgesEnd; edgeIndex += edgeFieldsCount) {
937                var edgeType = containmentEdges[edgeIndex + edgeTypeOffset];
938                if (edgeType == edgeWeakType)
939                    continue;
940                var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
941                var childNodeOrdinal = childNodeIndex / nodeFieldCount;
942                if (distances[childNodeOrdinal] !== noDistance)
943                    continue;
944                distances[childNodeOrdinal] = distance;
945                nodesToVisit[nodesToVisitLength++] = childNodeIndex;
946            }
947        }
948        if (nodesToVisitLength > nodeCount)
949            throw new Error("BFS failed. Nodes to visit (" + nodesToVisitLength + ") is more than nodes count (" + nodeCount + ")");
950    },
951
952    _buildAggregates: function(filter)
953    {
954        var aggregates = {};
955        var aggregatesByClassName = {};
956        var classIndexes = [];
957        var nodes = this._nodes;
958        var mapAndFlag = this.userObjectsMapAndFlag();
959        var flags = mapAndFlag ? mapAndFlag.map : null;
960        var flag = mapAndFlag ? mapAndFlag.flag : 0;
961        var nodesLength = nodes.length;
962        var nodeNativeType = this._nodeNativeType;
963        var nodeFieldCount = this._nodeFieldCount;
964        var selfSizeOffset = this._nodeSelfSizeOffset;
965        var nodeTypeOffset = this._nodeTypeOffset;
966        var node = this.rootNode();
967        var nodeDistances = this._nodeDistances;
968
969        for (var nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) {
970            var nodeOrdinal = nodeIndex / nodeFieldCount;
971            if (flags && !(flags[nodeOrdinal] & flag))
972                continue;
973            node.nodeIndex = nodeIndex;
974            if (filter && !filter(node))
975                continue;
976            var selfSize = nodes[nodeIndex + selfSizeOffset];
977            if (!selfSize && nodes[nodeIndex + nodeTypeOffset] !== nodeNativeType)
978                continue;
979            var classIndex = node.classIndex();
980            if (!(classIndex in aggregates)) {
981                var nodeType = node.type();
982                var nameMatters = nodeType === "object" || nodeType === "native";
983                var value = {
984                    count: 1,
985                    distance: nodeDistances[nodeOrdinal],
986                    self: selfSize,
987                    maxRet: 0,
988                    type: nodeType,
989                    name: nameMatters ? node.name() : null,
990                    idxs: [nodeIndex]
991                };
992                aggregates[classIndex] = value;
993                classIndexes.push(classIndex);
994                aggregatesByClassName[node.className()] = value;
995            } else {
996                var clss = aggregates[classIndex];
997                clss.distance = Math.min(clss.distance, nodeDistances[nodeOrdinal]);
998                ++clss.count;
999                clss.self += selfSize;
1000                clss.idxs.push(nodeIndex);
1001            }
1002        }
1003
1004        // Shave off provisionally allocated space.
1005        for (var i = 0, l = classIndexes.length; i < l; ++i) {
1006            var classIndex = classIndexes[i];
1007            aggregates[classIndex].idxs = aggregates[classIndex].idxs.slice();
1008        }
1009        return {aggregatesByClassName: aggregatesByClassName, aggregatesByClassIndex: aggregates};
1010    },
1011
1012    _calculateClassesRetainedSize: function(aggregates, filter)
1013    {
1014        var rootNodeIndex = this._rootNodeIndex;
1015        var node = this.createNode(rootNodeIndex);
1016        var list = [rootNodeIndex];
1017        var sizes = [-1];
1018        var classes = [];
1019        var seenClassNameIndexes = {};
1020        var nodeFieldCount = this._nodeFieldCount;
1021        var nodeTypeOffset = this._nodeTypeOffset;
1022        var nodeNativeType = this._nodeNativeType;
1023        var dominatedNodes = this._dominatedNodes;
1024        var nodes = this._nodes;
1025        var mapAndFlag = this.userObjectsMapAndFlag();
1026        var flags = mapAndFlag ? mapAndFlag.map : null;
1027        var flag = mapAndFlag ? mapAndFlag.flag : 0;
1028        var firstDominatedNodeIndex = this._firstDominatedNodeIndex;
1029
1030        while (list.length) {
1031            var nodeIndex = list.pop();
1032            node.nodeIndex = nodeIndex;
1033            var classIndex = node.classIndex();
1034            var seen = !!seenClassNameIndexes[classIndex];
1035            var nodeOrdinal = nodeIndex / nodeFieldCount;
1036            var dominatedIndexFrom = firstDominatedNodeIndex[nodeOrdinal];
1037            var dominatedIndexTo = firstDominatedNodeIndex[nodeOrdinal + 1];
1038
1039            if (!seen &&
1040                (!flags || (flags[nodeOrdinal] & flag)) &&
1041                (!filter || filter(node)) &&
1042                (node.selfSize() || nodes[nodeIndex + nodeTypeOffset] === nodeNativeType)
1043               ) {
1044                aggregates[classIndex].maxRet += node.retainedSize();
1045                if (dominatedIndexFrom !== dominatedIndexTo) {
1046                    seenClassNameIndexes[classIndex] = true;
1047                    sizes.push(list.length);
1048                    classes.push(classIndex);
1049                }
1050            }
1051            for (var i = dominatedIndexFrom; i < dominatedIndexTo; i++)
1052                list.push(dominatedNodes[i]);
1053
1054            var l = list.length;
1055            while (sizes[sizes.length - 1] === l) {
1056                sizes.pop();
1057                classIndex = classes.pop();
1058                seenClassNameIndexes[classIndex] = false;
1059            }
1060        }
1061    },
1062
1063    _sortAggregateIndexes: function(aggregates)
1064    {
1065        var nodeA = this.createNode();
1066        var nodeB = this.createNode();
1067        for (var clss in aggregates)
1068            aggregates[clss].idxs.sort(
1069                function(idxA, idxB) {
1070                    nodeA.nodeIndex = idxA;
1071                    nodeB.nodeIndex = idxB;
1072                    return nodeA.id() < nodeB.id() ? -1 : 1;
1073                });
1074    },
1075
1076    _buildPostOrderIndex: function()
1077    {
1078        var nodeFieldCount = this._nodeFieldCount;
1079        var nodes = this._nodes;
1080        var nodeCount = this.nodeCount;
1081        var rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount;
1082
1083        var edgeFieldsCount = this._edgeFieldsCount;
1084        var edgeTypeOffset = this._edgeTypeOffset;
1085        var edgeToNodeOffset = this._edgeToNodeOffset;
1086        var edgeShortcutType = this._edgeShortcutType;
1087        var firstEdgeIndexes = this._firstEdgeIndexes;
1088        var containmentEdges = this._containmentEdges;
1089        var containmentEdgesLength = this._containmentEdges.length;
1090
1091        var mapAndFlag = this.userObjectsMapAndFlag();
1092        var flags = mapAndFlag ? mapAndFlag.map : null;
1093        var flag = mapAndFlag ? mapAndFlag.flag : 0;
1094
1095        var nodesToVisit = new Uint32Array(nodeCount);
1096        var postOrderIndex2NodeOrdinal = new Uint32Array(nodeCount);
1097        var nodeOrdinal2PostOrderIndex = new Uint32Array(nodeCount);
1098        var painted = new Uint8Array(nodeCount);
1099        var nodesToVisitLength = 0;
1100        var postOrderIndex = 0;
1101        var grey = 1;
1102        var black = 2;
1103
1104        nodesToVisit[nodesToVisitLength++] = rootNodeOrdinal;
1105        painted[rootNodeOrdinal] = grey;
1106
1107        while (nodesToVisitLength) {
1108            var nodeOrdinal = nodesToVisit[nodesToVisitLength - 1];
1109
1110            if (painted[nodeOrdinal] === grey) {
1111                painted[nodeOrdinal] = black;
1112                var nodeFlag = !flags || (flags[nodeOrdinal] & flag);
1113                var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
1114                var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
1115                for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
1116                    if (nodeOrdinal !== rootNodeOrdinal && containmentEdges[edgeIndex + edgeTypeOffset] === edgeShortcutType)
1117                        continue;
1118                    var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
1119                    var childNodeOrdinal = childNodeIndex / nodeFieldCount;
1120                    var childNodeFlag = !flags || (flags[childNodeOrdinal] & flag);
1121                    // We are skipping the edges from non-page-owned nodes to page-owned nodes.
1122                    // Otherwise the dominators for the objects that also were retained by debugger would be affected.
1123                    if (nodeOrdinal !== rootNodeOrdinal && childNodeFlag && !nodeFlag)
1124                        continue;
1125                    if (!painted[childNodeOrdinal]) {
1126                        painted[childNodeOrdinal] = grey;
1127                        nodesToVisit[nodesToVisitLength++] = childNodeOrdinal;
1128                    }
1129                }
1130            } else {
1131                nodeOrdinal2PostOrderIndex[nodeOrdinal] = postOrderIndex;
1132                postOrderIndex2NodeOrdinal[postOrderIndex++] = nodeOrdinal;
1133                --nodesToVisitLength;
1134            }
1135        }
1136
1137        if (postOrderIndex !== nodeCount) {
1138            console.log("Error: Corrupted snapshot. " + (nodeCount - postOrderIndex) + " nodes are unreachable from the root:");
1139            var dumpNode = this.rootNode();
1140            for (var i = 0; i < nodeCount; ++i) {
1141                if (painted[i] !== black) {
1142                    // Fix it by giving the node a postorder index anyway.
1143                    nodeOrdinal2PostOrderIndex[i] = postOrderIndex;
1144                    postOrderIndex2NodeOrdinal[postOrderIndex++] = i;
1145                    dumpNode.nodeIndex = i * nodeFieldCount;
1146                    console.log(JSON.stringify(dumpNode.serialize()));
1147                    for (var retainers = dumpNode.retainers(); retainers.hasNext(); retainers = retainers.item().node().retainers())
1148                        console.log("  edgeName: " + retainers.item().name() + " nodeClassName: " + retainers.item().node().className());
1149                }
1150            }
1151        }
1152
1153        return {postOrderIndex2NodeOrdinal: postOrderIndex2NodeOrdinal, nodeOrdinal2PostOrderIndex: nodeOrdinal2PostOrderIndex};
1154    },
1155
1156    // The algorithm is based on the article:
1157    // K. Cooper, T. Harvey and K. Kennedy "A Simple, Fast Dominance Algorithm"
1158    // Softw. Pract. Exper. 4 (2001), pp. 1-10.
1159    /**
1160     * @param {!Array.<number>} postOrderIndex2NodeOrdinal
1161     * @param {!Array.<number>} nodeOrdinal2PostOrderIndex
1162     */
1163    _buildDominatorTree: function(postOrderIndex2NodeOrdinal, nodeOrdinal2PostOrderIndex)
1164    {
1165        var nodeFieldCount = this._nodeFieldCount;
1166        var nodes = this._nodes;
1167        var firstRetainerIndex = this._firstRetainerIndex;
1168        var retainingNodes = this._retainingNodes;
1169        var retainingEdges = this._retainingEdges;
1170        var edgeFieldsCount = this._edgeFieldsCount;
1171        var edgeTypeOffset = this._edgeTypeOffset;
1172        var edgeToNodeOffset = this._edgeToNodeOffset;
1173        var edgeShortcutType = this._edgeShortcutType;
1174        var firstEdgeIndexes = this._firstEdgeIndexes;
1175        var containmentEdges = this._containmentEdges;
1176        var containmentEdgesLength = this._containmentEdges.length;
1177        var rootNodeIndex = this._rootNodeIndex;
1178
1179        var mapAndFlag = this.userObjectsMapAndFlag();
1180        var flags = mapAndFlag ? mapAndFlag.map : null;
1181        var flag = mapAndFlag ? mapAndFlag.flag : 0;
1182
1183        var nodesCount = postOrderIndex2NodeOrdinal.length;
1184        var rootPostOrderedIndex = nodesCount - 1;
1185        var noEntry = nodesCount;
1186        var dominators = new Uint32Array(nodesCount);
1187        for (var i = 0; i < rootPostOrderedIndex; ++i)
1188            dominators[i] = noEntry;
1189        dominators[rootPostOrderedIndex] = rootPostOrderedIndex;
1190
1191        // The affected array is used to mark entries which dominators
1192        // have to be racalculated because of changes in their retainers.
1193        var affected = new Uint8Array(nodesCount);
1194        var nodeOrdinal;
1195
1196        { // Mark the root direct children as affected.
1197            nodeOrdinal = this._rootNodeIndex / nodeFieldCount;
1198            var beginEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal] + edgeToNodeOffset;
1199            var endEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal + 1];
1200            for (var toNodeFieldIndex = beginEdgeToNodeFieldIndex;
1201                 toNodeFieldIndex < endEdgeToNodeFieldIndex;
1202                 toNodeFieldIndex += edgeFieldsCount) {
1203                var childNodeOrdinal = containmentEdges[toNodeFieldIndex] / nodeFieldCount;
1204                affected[nodeOrdinal2PostOrderIndex[childNodeOrdinal]] = 1;
1205            }
1206        }
1207
1208        var changed = true;
1209        while (changed) {
1210            changed = false;
1211            for (var postOrderIndex = rootPostOrderedIndex - 1; postOrderIndex >= 0; --postOrderIndex) {
1212                if (affected[postOrderIndex] === 0)
1213                    continue;
1214                affected[postOrderIndex] = 0;
1215                // If dominator of the entry has already been set to root,
1216                // then it can't propagate any further.
1217                if (dominators[postOrderIndex] === rootPostOrderedIndex)
1218                    continue;
1219                nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex];
1220                var nodeFlag = !flags || (flags[nodeOrdinal] & flag);
1221                var newDominatorIndex = noEntry;
1222                var beginRetainerIndex = firstRetainerIndex[nodeOrdinal];
1223                var endRetainerIndex = firstRetainerIndex[nodeOrdinal + 1];
1224                for (var retainerIndex = beginRetainerIndex; retainerIndex < endRetainerIndex; ++retainerIndex) {
1225                    var retainerEdgeIndex = retainingEdges[retainerIndex];
1226                    var retainerEdgeType = containmentEdges[retainerEdgeIndex + edgeTypeOffset];
1227                    var retainerNodeIndex = retainingNodes[retainerIndex];
1228                    if (retainerNodeIndex !== rootNodeIndex && retainerEdgeType === edgeShortcutType)
1229                        continue;
1230                    var retainerNodeOrdinal = retainerNodeIndex / nodeFieldCount;
1231                    var retainerNodeFlag = !flags || (flags[retainerNodeOrdinal] & flag);
1232                    // We are skipping the edges from non-page-owned nodes to page-owned nodes.
1233                    // Otherwise the dominators for the objects that also were retained by debugger would be affected.
1234                    if (retainerNodeIndex !== rootNodeIndex && nodeFlag && !retainerNodeFlag)
1235                        continue;
1236                    var retanerPostOrderIndex = nodeOrdinal2PostOrderIndex[retainerNodeOrdinal];
1237                    if (dominators[retanerPostOrderIndex] !== noEntry) {
1238                        if (newDominatorIndex === noEntry)
1239                            newDominatorIndex = retanerPostOrderIndex;
1240                        else {
1241                            while (retanerPostOrderIndex !== newDominatorIndex) {
1242                                while (retanerPostOrderIndex < newDominatorIndex)
1243                                    retanerPostOrderIndex = dominators[retanerPostOrderIndex];
1244                                while (newDominatorIndex < retanerPostOrderIndex)
1245                                    newDominatorIndex = dominators[newDominatorIndex];
1246                            }
1247                        }
1248                        // If idom has already reached the root, it doesn't make sense
1249                        // to check other retainers.
1250                        if (newDominatorIndex === rootPostOrderedIndex)
1251                            break;
1252                    }
1253                }
1254                if (newDominatorIndex !== noEntry && dominators[postOrderIndex] !== newDominatorIndex) {
1255                    dominators[postOrderIndex] = newDominatorIndex;
1256                    changed = true;
1257                    nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex];
1258                    beginEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal] + edgeToNodeOffset;
1259                    endEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal + 1];
1260                    for (var toNodeFieldIndex = beginEdgeToNodeFieldIndex;
1261                         toNodeFieldIndex < endEdgeToNodeFieldIndex;
1262                         toNodeFieldIndex += edgeFieldsCount) {
1263                        var childNodeOrdinal = containmentEdges[toNodeFieldIndex] / nodeFieldCount;
1264                        affected[nodeOrdinal2PostOrderIndex[childNodeOrdinal]] = 1;
1265                    }
1266                }
1267            }
1268        }
1269
1270        var dominatorsTree = new Uint32Array(nodesCount);
1271        for (var postOrderIndex = 0, l = dominators.length; postOrderIndex < l; ++postOrderIndex) {
1272            nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex];
1273            dominatorsTree[nodeOrdinal] = postOrderIndex2NodeOrdinal[dominators[postOrderIndex]];
1274        }
1275        return dominatorsTree;
1276    },
1277
1278    _calculateRetainedSizes: function(postOrderIndex2NodeOrdinal)
1279    {
1280        var nodeCount = this.nodeCount;
1281        var nodes = this._nodes;
1282        var nodeSelfSizeOffset = this._nodeSelfSizeOffset;
1283        var nodeFieldCount = this._nodeFieldCount;
1284        var dominatorsTree = this._dominatorsTree;
1285        // Reuse now unused edge_count field to store retained size.
1286        var nodeRetainedSizeOffset = this._nodeRetainedSizeOffset = this._nodeEdgeCountOffset;
1287        delete this._nodeEdgeCountOffset;
1288
1289        for (var nodeIndex = 0, l = nodes.length; nodeIndex < l; nodeIndex += nodeFieldCount)
1290            nodes[nodeIndex + nodeRetainedSizeOffset] = nodes[nodeIndex + nodeSelfSizeOffset];
1291
1292        // Propagate retained sizes for each node excluding root.
1293        for (var postOrderIndex = 0; postOrderIndex < nodeCount - 1; ++postOrderIndex) {
1294            var nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex];
1295            var nodeIndex = nodeOrdinal * nodeFieldCount;
1296            var dominatorIndex = dominatorsTree[nodeOrdinal] * nodeFieldCount;
1297            nodes[dominatorIndex + nodeRetainedSizeOffset] += nodes[nodeIndex + nodeRetainedSizeOffset];
1298        }
1299    },
1300
1301    _buildDominatedNodes: function()
1302    {
1303        // Builds up two arrays:
1304        //  - "dominatedNodes" is a continuous array, where each node owns an
1305        //    interval (can be empty) with corresponding dominated nodes.
1306        //  - "indexArray" is an array of indexes in the "dominatedNodes"
1307        //    with the same positions as in the _nodeIndex.
1308        var indexArray = this._firstDominatedNodeIndex = new Uint32Array(this.nodeCount + 1);
1309        // All nodes except the root have dominators.
1310        var dominatedNodes = this._dominatedNodes = new Uint32Array(this.nodeCount - 1);
1311
1312        // Count the number of dominated nodes for each node. Skip the root (node at
1313        // index 0) as it is the only node that dominates itself.
1314        var nodeFieldCount = this._nodeFieldCount;
1315        var dominatorsTree = this._dominatorsTree;
1316
1317        var fromNodeOrdinal = 0;
1318        var toNodeOrdinal = this.nodeCount;
1319        var rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount;
1320        if (rootNodeOrdinal === fromNodeOrdinal)
1321            fromNodeOrdinal = 1;
1322        else if (rootNodeOrdinal === toNodeOrdinal - 1)
1323            toNodeOrdinal = toNodeOrdinal - 1;
1324        else
1325            throw new Error("Root node is expected to be either first or last");
1326        for (var nodeOrdinal = fromNodeOrdinal; nodeOrdinal < toNodeOrdinal; ++nodeOrdinal)
1327            ++indexArray[dominatorsTree[nodeOrdinal]];
1328        // Put in the first slot of each dominatedNodes slice the count of entries
1329        // that will be filled.
1330        var firstDominatedNodeIndex = 0;
1331        for (var i = 0, l = this.nodeCount; i < l; ++i) {
1332            var dominatedCount = dominatedNodes[firstDominatedNodeIndex] = indexArray[i];
1333            indexArray[i] = firstDominatedNodeIndex;
1334            firstDominatedNodeIndex += dominatedCount;
1335        }
1336        indexArray[this.nodeCount] = dominatedNodes.length;
1337        // Fill up the dominatedNodes array with indexes of dominated nodes. Skip the root (node at
1338        // index 0) as it is the only node that dominates itself.
1339        for (var nodeOrdinal = fromNodeOrdinal; nodeOrdinal < toNodeOrdinal; ++nodeOrdinal) {
1340            var dominatorOrdinal = dominatorsTree[nodeOrdinal];
1341            var dominatedRefIndex = indexArray[dominatorOrdinal];
1342            dominatedRefIndex += (--dominatedNodes[dominatedRefIndex]);
1343            dominatedNodes[dominatedRefIndex] = nodeOrdinal * nodeFieldCount;
1344        }
1345    },
1346
1347    _markInvisibleEdges: function()
1348    {
1349        throw new Error("Not implemented");
1350    },
1351
1352    _calculateFlags: function()
1353    {
1354        throw new Error("Not implemented");
1355    },
1356
1357    userObjectsMapAndFlag: function()
1358    {
1359        throw new Error("Not implemented");
1360    },
1361
1362    calculateSnapshotDiff: function(baseSnapshotId, baseSnapshotAggregates)
1363    {
1364        var snapshotDiff = this._snapshotDiffs[baseSnapshotId];
1365        if (snapshotDiff)
1366            return snapshotDiff;
1367        snapshotDiff = {};
1368
1369        var aggregates = this.aggregates(true, "allObjects");
1370        for (var className in baseSnapshotAggregates) {
1371            var baseAggregate = baseSnapshotAggregates[className];
1372            var diff = this._calculateDiffForClass(baseAggregate, aggregates[className]);
1373            if (diff)
1374                snapshotDiff[className] = diff;
1375        }
1376        var emptyBaseAggregate = { ids: [], indexes: [], selfSizes: [] };
1377        for (var className in aggregates) {
1378            if (className in baseSnapshotAggregates)
1379                continue;
1380            snapshotDiff[className] = this._calculateDiffForClass(emptyBaseAggregate, aggregates[className]);
1381        }
1382
1383        this._snapshotDiffs[baseSnapshotId] = snapshotDiff;
1384        return snapshotDiff;
1385    },
1386
1387    _calculateDiffForClass: function(baseAggregate, aggregate)
1388    {
1389        var baseIds = baseAggregate.ids;
1390        var baseIndexes = baseAggregate.indexes;
1391        var baseSelfSizes = baseAggregate.selfSizes;
1392
1393        var indexes = aggregate ? aggregate.idxs : [];
1394
1395        var i = 0, l = baseIds.length;
1396        var j = 0, m = indexes.length;
1397        var diff = { addedCount: 0,
1398                     removedCount: 0,
1399                     addedSize: 0,
1400                     removedSize: 0,
1401                     deletedIndexes: [],
1402                     addedIndexes: [] };
1403
1404        var nodeB = this.createNode(indexes[j]);
1405        while (i < l && j < m) {
1406            var nodeAId = baseIds[i];
1407            if (nodeAId < nodeB.id()) {
1408                diff.deletedIndexes.push(baseIndexes[i]);
1409                diff.removedCount++;
1410                diff.removedSize += baseSelfSizes[i];
1411                ++i;
1412            } else if (nodeAId > nodeB.id()) { // Native nodes(e.g. dom groups) may have ids less than max JS object id in the base snapshot
1413                diff.addedIndexes.push(indexes[j]);
1414                diff.addedCount++;
1415                diff.addedSize += nodeB.selfSize();
1416                nodeB.nodeIndex = indexes[++j];
1417            } else { // nodeAId === nodeB.id()
1418                ++i;
1419                nodeB.nodeIndex = indexes[++j];
1420            }
1421        }
1422        while (i < l) {
1423            diff.deletedIndexes.push(baseIndexes[i]);
1424            diff.removedCount++;
1425            diff.removedSize += baseSelfSizes[i];
1426            ++i;
1427        }
1428        while (j < m) {
1429            diff.addedIndexes.push(indexes[j]);
1430            diff.addedCount++;
1431            diff.addedSize += nodeB.selfSize();
1432            nodeB.nodeIndex = indexes[++j];
1433        }
1434        diff.countDelta = diff.addedCount - diff.removedCount;
1435        diff.sizeDelta = diff.addedSize - diff.removedSize;
1436        if (!diff.addedCount && !diff.removedCount)
1437            return null;
1438        return diff;
1439    },
1440
1441    _nodeForSnapshotObjectId: function(snapshotObjectId)
1442    {
1443        for (var it = this._allNodes(); it.hasNext(); it.next()) {
1444            if (it.node.id() === snapshotObjectId)
1445                return it.node;
1446        }
1447        return null;
1448    },
1449
1450    nodeClassName: function(snapshotObjectId)
1451    {
1452        var node = this._nodeForSnapshotObjectId(snapshotObjectId);
1453        if (node)
1454            return node.className();
1455        return null;
1456    },
1457
1458    dominatorIdsForNode: function(snapshotObjectId)
1459    {
1460        var node = this._nodeForSnapshotObjectId(snapshotObjectId);
1461        if (!node)
1462            return null;
1463        var result = [];
1464        while (!node.isRoot()) {
1465            result.push(node.id());
1466            node.nodeIndex = node.dominatorIndex();
1467        }
1468        return result;
1469    },
1470
1471    _parseFilter: function(filter)
1472    {
1473        if (!filter)
1474            return null;
1475        var parsedFilter = eval("(function(){return " + filter + "})()");
1476        return parsedFilter.bind(this);
1477    },
1478
1479    createEdgesProvider: function(nodeIndex, showHiddenData)
1480    {
1481        var node = this.createNode(nodeIndex);
1482        var filter = this.containmentEdgesFilter(showHiddenData);
1483        return new WebInspector.HeapSnapshotEdgesProvider(this, filter, node.edges());
1484    },
1485
1486    createEdgesProviderForTest: function(nodeIndex, filter)
1487    {
1488        var node = this.createNode(nodeIndex);
1489        return new WebInspector.HeapSnapshotEdgesProvider(this, filter, node.edges());
1490    },
1491
1492    retainingEdgesFilter: function(showHiddenData)
1493    {
1494        return null;
1495    },
1496
1497    containmentEdgesFilter: function(showHiddenData)
1498    {
1499        return null;
1500    },
1501
1502    createRetainingEdgesProvider: function(nodeIndex, showHiddenData)
1503    {
1504        var node = this.createNode(nodeIndex);
1505        var filter = this.retainingEdgesFilter(showHiddenData);
1506        return new WebInspector.HeapSnapshotEdgesProvider(this, filter, node.retainers());
1507    },
1508
1509    createAddedNodesProvider: function(baseSnapshotId, className)
1510    {
1511        var snapshotDiff = this._snapshotDiffs[baseSnapshotId];
1512        var diffForClass = snapshotDiff[className];
1513        return new WebInspector.HeapSnapshotNodesProvider(this, null, diffForClass.addedIndexes);
1514    },
1515
1516    createDeletedNodesProvider: function(nodeIndexes)
1517    {
1518        return new WebInspector.HeapSnapshotNodesProvider(this, null, nodeIndexes);
1519    },
1520
1521    classNodesFilter: function()
1522    {
1523        return null;
1524    },
1525
1526    createNodesProviderForClass: function(className, aggregatesKey)
1527    {
1528        return new WebInspector.HeapSnapshotNodesProvider(this, this.classNodesFilter(), this.aggregates(false, aggregatesKey)[className].idxs);
1529    },
1530
1531    createNodesProviderForDominator: function(nodeIndex)
1532    {
1533        var node = this.createNode(nodeIndex);
1534        return new WebInspector.HeapSnapshotNodesProvider(this, null, this._dominatedNodesOfNode(node));
1535    },
1536
1537    updateStaticData: function()
1538    {
1539        return {nodeCount: this.nodeCount, rootNodeIndex: this._rootNodeIndex, totalSize: this.totalSize, uid: this.uid};
1540    }
1541};
1542
1543/**
1544 * @constructor
1545 * @param {!Array.<number>=} unfilteredIterationOrder
1546 */
1547WebInspector.HeapSnapshotFilteredOrderedIterator = function(iterator, filter, unfilteredIterationOrder)
1548{
1549    this._filter = filter;
1550    this._iterator = iterator;
1551    this._unfilteredIterationOrder = unfilteredIterationOrder;
1552    this._iterationOrder = null;
1553    this._position = 0;
1554    this._currentComparator = null;
1555    this._sortedPrefixLength = 0;
1556    this._sortedSuffixLength = 0;
1557}
1558
1559WebInspector.HeapSnapshotFilteredOrderedIterator.prototype = {
1560    _createIterationOrder: function()
1561    {
1562        if (this._iterationOrder)
1563            return;
1564        if (this._unfilteredIterationOrder && !this._filter) {
1565            this._iterationOrder = this._unfilteredIterationOrder.slice(0);
1566            this._unfilteredIterationOrder = null;
1567            return;
1568        }
1569        this._iterationOrder = [];
1570        var iterator = this._iterator;
1571        if (!this._unfilteredIterationOrder && !this._filter) {
1572            for (iterator.rewind(); iterator.hasNext(); iterator.next())
1573                this._iterationOrder.push(iterator.index());
1574        } else if (!this._unfilteredIterationOrder) {
1575            for (iterator.rewind(); iterator.hasNext(); iterator.next()) {
1576                if (this._filter(iterator.item()))
1577                    this._iterationOrder.push(iterator.index());
1578            }
1579        } else {
1580            var order = this._unfilteredIterationOrder.constructor === Array ?
1581                this._unfilteredIterationOrder : this._unfilteredIterationOrder.slice(0);
1582            for (var i = 0, l = order.length; i < l; ++i) {
1583                iterator.setIndex(order[i]);
1584                if (this._filter(iterator.item()))
1585                    this._iterationOrder.push(iterator.index());
1586            }
1587            this._unfilteredIterationOrder = null;
1588        }
1589    },
1590
1591    rewind: function()
1592    {
1593        this._position = 0;
1594    },
1595
1596    hasNext: function()
1597    {
1598        return this._position < this._iterationOrder.length;
1599    },
1600
1601    isEmpty: function()
1602    {
1603        if (this._iterationOrder)
1604            return !this._iterationOrder.length;
1605        if (this._unfilteredIterationOrder && !this._filter)
1606            return !this._unfilteredIterationOrder.length;
1607        var iterator = this._iterator;
1608        if (!this._unfilteredIterationOrder && !this._filter) {
1609            iterator.rewind();
1610            return !iterator.hasNext();
1611        } else if (!this._unfilteredIterationOrder) {
1612            for (iterator.rewind(); iterator.hasNext(); iterator.next())
1613                if (this._filter(iterator.item()))
1614                    return false;
1615        } else {
1616            var order = this._unfilteredIterationOrder.constructor === Array ?
1617                this._unfilteredIterationOrder : this._unfilteredIterationOrder.slice(0);
1618            for (var i = 0, l = order.length; i < l; ++i) {
1619                iterator.setIndex(order[i]);
1620                if (this._filter(iterator.item()))
1621                    return false;
1622            }
1623        }
1624        return true;
1625    },
1626
1627    item: function()
1628    {
1629        this._iterator.setIndex(this._iterationOrder[this._position]);
1630        return this._iterator.item();
1631    },
1632
1633    get length()
1634    {
1635        this._createIterationOrder();
1636        return this._iterationOrder.length;
1637    },
1638
1639    next: function()
1640    {
1641        ++this._position;
1642    },
1643
1644    /**
1645     * @param {number} begin
1646     * @param {number} end
1647     */
1648    serializeItemsRange: function(begin, end)
1649    {
1650        this._createIterationOrder();
1651        if (begin > end)
1652            throw new Error("Start position > end position: " + begin + " > " + end);
1653        if (end > this._iterationOrder.length)
1654            end = this._iterationOrder.length;
1655        if (this._sortedPrefixLength < end && begin < this._iterationOrder.length - this._sortedSuffixLength) {
1656            this.sort(this._currentComparator, this._sortedPrefixLength, this._iterationOrder.length - 1 - this._sortedSuffixLength, begin, end - 1);
1657            if (begin <= this._sortedPrefixLength)
1658                this._sortedPrefixLength = end;
1659            if (end >= this._iterationOrder.length - this._sortedSuffixLength)
1660                this._sortedSuffixLength = this._iterationOrder.length - begin;
1661        }
1662
1663        this._position = begin;
1664        var startPosition = this._position;
1665        var count = end - begin;
1666        var result = new Array(count);
1667        for (var i = 0 ; i < count && this.hasNext(); ++i, this.next())
1668            result[i] = this.item().serialize();
1669        result.length = i;
1670        result.totalLength = this._iterationOrder.length;
1671
1672        result.startPosition = startPosition;
1673        result.endPosition = this._position;
1674        return result;
1675    },
1676
1677    sortAll: function()
1678    {
1679        this._createIterationOrder();
1680        if (this._sortedPrefixLength + this._sortedSuffixLength >= this._iterationOrder.length)
1681            return;
1682        this.sort(this._currentComparator, this._sortedPrefixLength, this._iterationOrder.length - 1 - this._sortedSuffixLength,
1683                  this._sortedPrefixLength, this._iterationOrder.length - 1 - this._sortedSuffixLength);
1684        this._sortedPrefixLength = this._iterationOrder.length;
1685        this._sortedSuffixLength = 0;
1686    },
1687
1688    sortAndRewind: function(comparator)
1689    {
1690        this._currentComparator = comparator;
1691        this._sortedPrefixLength = 0;
1692        this._sortedSuffixLength = 0;
1693        this.rewind();
1694    }
1695}
1696
1697WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator = function(fieldNames)
1698{
1699    return {fieldName1: fieldNames[0], ascending1: fieldNames[1], fieldName2: fieldNames[2], ascending2: fieldNames[3]};
1700}
1701
1702/**
1703 * @constructor
1704 * @extends {WebInspector.HeapSnapshotFilteredOrderedIterator}
1705 */
1706WebInspector.HeapSnapshotEdgesProvider = function(snapshot, filter, edgesIter)
1707{
1708    this.snapshot = snapshot;
1709    WebInspector.HeapSnapshotFilteredOrderedIterator.call(this, edgesIter, filter);
1710}
1711
1712WebInspector.HeapSnapshotEdgesProvider.prototype = {
1713    sort: function(comparator, leftBound, rightBound, windowLeft, windowRight)
1714    {
1715        var fieldName1 = comparator.fieldName1;
1716        var fieldName2 = comparator.fieldName2;
1717        var ascending1 = comparator.ascending1;
1718        var ascending2 = comparator.ascending2;
1719
1720        var edgeA = this._iterator.item().clone();
1721        var edgeB = edgeA.clone();
1722        var nodeA = this.snapshot.createNode();
1723        var nodeB = this.snapshot.createNode();
1724
1725        function compareEdgeFieldName(ascending, indexA, indexB)
1726        {
1727            edgeA.edgeIndex = indexA;
1728            edgeB.edgeIndex = indexB;
1729            if (edgeB.name() === "__proto__") return -1;
1730            if (edgeA.name() === "__proto__") return 1;
1731            var result =
1732                edgeA.hasStringName() === edgeB.hasStringName() ?
1733                (edgeA.name() < edgeB.name() ? -1 : (edgeA.name() > edgeB.name() ? 1 : 0)) :
1734                (edgeA.hasStringName() ? -1 : 1);
1735            return ascending ? result : -result;
1736        }
1737
1738        function compareNodeField(fieldName, ascending, indexA, indexB)
1739        {
1740            edgeA.edgeIndex = indexA;
1741            nodeA.nodeIndex = edgeA.nodeIndex();
1742            var valueA = nodeA[fieldName]();
1743
1744            edgeB.edgeIndex = indexB;
1745            nodeB.nodeIndex = edgeB.nodeIndex();
1746            var valueB = nodeB[fieldName]();
1747
1748            var result = valueA < valueB ? -1 : (valueA > valueB ? 1 : 0);
1749            return ascending ? result : -result;
1750        }
1751
1752        function compareEdgeAndNode(indexA, indexB) {
1753            var result = compareEdgeFieldName(ascending1, indexA, indexB);
1754            if (result === 0)
1755                result = compareNodeField(fieldName2, ascending2, indexA, indexB);
1756            if (result === 0)
1757                return indexA - indexB;
1758            return result;
1759        }
1760
1761        function compareNodeAndEdge(indexA, indexB) {
1762            var result = compareNodeField(fieldName1, ascending1, indexA, indexB);
1763            if (result === 0)
1764                result = compareEdgeFieldName(ascending2, indexA, indexB);
1765            if (result === 0)
1766                return indexA - indexB;
1767            return result;
1768        }
1769
1770        function compareNodeAndNode(indexA, indexB) {
1771            var result = compareNodeField(fieldName1, ascending1, indexA, indexB);
1772            if (result === 0)
1773                result = compareNodeField(fieldName2, ascending2, indexA, indexB);
1774            if (result === 0)
1775                return indexA - indexB;
1776            return result;
1777        }
1778
1779        if (fieldName1 === "!edgeName")
1780            this._iterationOrder.sortRange(compareEdgeAndNode, leftBound, rightBound, windowLeft, windowRight);
1781        else if (fieldName2 === "!edgeName")
1782            this._iterationOrder.sortRange(compareNodeAndEdge, leftBound, rightBound, windowLeft, windowRight);
1783        else
1784            this._iterationOrder.sortRange(compareNodeAndNode, leftBound, rightBound, windowLeft, windowRight);
1785    },
1786
1787    __proto__: WebInspector.HeapSnapshotFilteredOrderedIterator.prototype
1788}
1789
1790
1791/**
1792 * @constructor
1793 * @extends {WebInspector.HeapSnapshotFilteredOrderedIterator}
1794 * @param {!Array.<number>=} nodeIndexes
1795 */
1796WebInspector.HeapSnapshotNodesProvider = function(snapshot, filter, nodeIndexes)
1797{
1798    this.snapshot = snapshot;
1799    WebInspector.HeapSnapshotFilteredOrderedIterator.call(this, snapshot._allNodes(), filter, nodeIndexes);
1800}
1801
1802WebInspector.HeapSnapshotNodesProvider.prototype = {
1803    nodePosition: function(snapshotObjectId)
1804    {
1805        this._createIterationOrder();
1806        if (this.isEmpty())
1807            return -1;
1808        this.sortAll();
1809
1810        var node = this.snapshot.createNode();
1811        for (var i = 0; i < this._iterationOrder.length; i++) {
1812            node.nodeIndex = this._iterationOrder[i];
1813            if (node.id() === snapshotObjectId)
1814                return i;
1815        }
1816        return -1;
1817    },
1818
1819    sort: function(comparator, leftBound, rightBound, windowLeft, windowRight)
1820    {
1821        var fieldName1 = comparator.fieldName1;
1822        var fieldName2 = comparator.fieldName2;
1823        var ascending1 = comparator.ascending1;
1824        var ascending2 = comparator.ascending2;
1825
1826        var nodeA = this.snapshot.createNode();
1827        var nodeB = this.snapshot.createNode();
1828
1829        function sortByNodeField(fieldName, ascending)
1830        {
1831            var valueOrFunctionA = nodeA[fieldName];
1832            var valueA = typeof valueOrFunctionA !== "function" ? valueOrFunctionA : valueOrFunctionA.call(nodeA);
1833            var valueOrFunctionB = nodeB[fieldName];
1834            var valueB = typeof valueOrFunctionB !== "function" ? valueOrFunctionB : valueOrFunctionB.call(nodeB);
1835            var result = valueA < valueB ? -1 : (valueA > valueB ? 1 : 0);
1836            return ascending ? result : -result;
1837        }
1838
1839        function sortByComparator(indexA, indexB) {
1840            nodeA.nodeIndex = indexA;
1841            nodeB.nodeIndex = indexB;
1842            var result = sortByNodeField(fieldName1, ascending1);
1843            if (result === 0)
1844                result = sortByNodeField(fieldName2, ascending2);
1845            if (result === 0)
1846                return indexA - indexB;
1847            return result;
1848        }
1849
1850        this._iterationOrder.sortRange(sortByComparator, leftBound, rightBound, windowLeft, windowRight);
1851    },
1852
1853    __proto__: WebInspector.HeapSnapshotFilteredOrderedIterator.prototype
1854}
1855
1856