Home | History | Annotate | Download | only in front_end
      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  */
     34 WebInspector.HeapSnapshotArraySlice = function(array, start, end)
     35 {
     36     this._array = array;
     37     this._start = start;
     38     this.length = end - start;
     39 }
     40 
     41 WebInspector.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  */
     59 WebInspector.HeapSnapshotEdge = function(snapshot, edges, edgeIndex)
     60 {
     61     this._snapshot = snapshot;
     62     this._edges = edges;
     63     this.edgeIndex = edgeIndex || 0;
     64 }
     65 
     66 WebInspector.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  */
    128 WebInspector.HeapSnapshotEdgeIterator = function(edge)
    129 {
    130     this.edge = edge;
    131 }
    132 
    133 WebInspector.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  */
    168 WebInspector.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 
    180 WebInspector.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  */
    270 WebInspector.HeapSnapshotRetainerEdgeIterator = function(retainer)
    271 {
    272     this.retainer = retainer;
    273 }
    274 
    275 WebInspector.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  */
    311 WebInspector.HeapSnapshotNode = function(snapshot, nodeIndex)
    312 {
    313     this._snapshot = snapshot;
    314     this._firstNodeIndex = nodeIndex;
    315     this.nodeIndex = nodeIndex;
    316 }
    317 
    318 WebInspector.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  */
    441 WebInspector.HeapSnapshotNodeIterator = function(node)
    442 {
    443     this.node = node;
    444     this._nodesLength = node._snapshot._nodes.length;
    445 }
    446 
    447 WebInspector.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  */
    484 WebInspector.HeapSnapshotProgress = function(dispatcher)
    485 {
    486     this._dispatcher = dispatcher;
    487 }
    488 
    489 WebInspector.HeapSnapshotProgress.Event = {
    490     Update: "ProgressUpdate"
    491 };
    492 
    493 WebInspector.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  */
    529 WebInspector.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 
    556 WebInspector.HeapSnapshot.enableAllocationProfiler = false;
    557 
    558 /**
    559  * @constructor
    560  */
    561 function 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  */
    576 function 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 
    586 WebInspector.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  */
   1547 WebInspector.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 
   1559 WebInspector.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 
   1697 WebInspector.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  */
   1706 WebInspector.HeapSnapshotEdgesProvider = function(snapshot, filter, edgesIter)
   1707 {
   1708     this.snapshot = snapshot;
   1709     WebInspector.HeapSnapshotFilteredOrderedIterator.call(this, edgesIter, filter);
   1710 }
   1711 
   1712 WebInspector.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  */
   1796 WebInspector.HeapSnapshotNodesProvider = function(snapshot, filter, nodeIndexes)
   1797 {
   1798     this.snapshot = snapshot;
   1799     WebInspector.HeapSnapshotFilteredOrderedIterator.call(this, snapshot._allNodes(), filter, nodeIndexes);
   1800 }
   1801 
   1802 WebInspector.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