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 WebInspector.HeapSnapshotArraySlice = function(snapshot, arrayName, start, end)
     32 {
     33     // Note: we don't reference snapshot contents directly to avoid
     34     // holding references to big chunks of data.
     35     this._snapshot = snapshot;
     36     this._arrayName = arrayName;
     37     this._start = start;
     38     this.length = end - start;
     39 }
     40 
     41 WebInspector.HeapSnapshotArraySlice.prototype = {
     42     item: function(index)
     43     {
     44         return this._snapshot[this._arrayName][this._start + index];
     45     }
     46 }
     47 
     48 WebInspector.HeapSnapshotEdge = function(snapshot, edges, edgeIndex)
     49 {
     50     this._snapshot = snapshot;
     51     this._edges = edges;
     52     this.edgeIndex = edgeIndex || 0;
     53 }
     54 
     55 WebInspector.HeapSnapshotEdge.prototype = {
     56     clone: function()
     57     {
     58         return new WebInspector.HeapSnapshotEdge(this._snapshot, this._edges, this.edgeIndex);
     59     },
     60 
     61     get hasStringName()
     62     {
     63         if (!this.isShortcut)
     64             return this._hasStringName;
     65         return isNaN(parseInt(this._name, 10));
     66     },
     67 
     68     get isElement()
     69     {
     70         return this._type() === this._snapshot._edgeElementType;
     71     },
     72 
     73     get isHidden()
     74     {
     75         return this._type() === this._snapshot._edgeHiddenType;
     76     },
     77 
     78     get isInternal()
     79     {
     80         return this._type() === this._snapshot._edgeInternalType;
     81     },
     82 
     83     get isInvisible()
     84     {
     85         return this._type() === this._snapshot._edgeInvisibleType;
     86     },
     87 
     88     get isShortcut()
     89     {
     90         return this._type() === this._snapshot._edgeShortcutType;
     91     },
     92 
     93     get name()
     94     {
     95         if (!this.isShortcut)
     96             return this._name;
     97         var numName = parseInt(this._name, 10);
     98         return isNaN(numName) ? this._name : numName;
     99     },
    100 
    101     get node()
    102     {
    103         return new WebInspector.HeapSnapshotNode(this._snapshot, this.nodeIndex);
    104     },
    105 
    106     get nodeIndex()
    107     {
    108         return this._edges.item(this.edgeIndex + this._snapshot._edgeToNodeOffset);
    109     },
    110 
    111     get rawEdges()
    112     {
    113         return this._edges;
    114     },
    115 
    116     toString: function()
    117     {
    118         switch (this.type) {
    119         case "context": return "->" + this.name;
    120         case "element": return "[" + this.name + "]";
    121         case "property":
    122             return this.name.indexOf(" ") === -1 ? "." + this.name : "[\"" + this.name + "\"]";
    123         case "shortcut":
    124             var name = this.name;
    125             if (typeof name === "string")
    126                 return this.name.indexOf(" ") === -1 ? "." + this.name : "[\"" + this.name + "\"]";
    127             else
    128                 return "[" + this.name + "]";
    129         case "internal":
    130         case "hidden":
    131         case "invisible":
    132             return "{" + this.name + "}";
    133         };
    134         return "?" + this.name + "?";
    135     },
    136 
    137     get type()
    138     {
    139         return this._snapshot._edgeTypes[this._type()];
    140     },
    141 
    142     get _hasStringName()
    143     {
    144         return !this.isElement && !this.isHidden;
    145     },
    146 
    147     get _name()
    148     {
    149         return this._hasStringName ? this._snapshot._strings[this._nameOrIndex] : this._nameOrIndex;
    150     },
    151 
    152     get _nameOrIndex()
    153     {
    154         return this._edges.item(this.edgeIndex + this._snapshot._edgeNameOffset);
    155     },
    156 
    157     _type: function()
    158     {
    159         return this._edges.item(this.edgeIndex + this._snapshot._edgeTypeOffset);
    160     }
    161 };
    162 
    163 WebInspector.HeapSnapshotEdgeIterator = function(edge)
    164 {
    165     this.edge = edge;
    166 }
    167 
    168 WebInspector.HeapSnapshotEdgeIterator.prototype = {
    169     first: function()
    170     {
    171         this.edge.edgeIndex = 0;
    172     },
    173 
    174     hasNext: function()
    175     {
    176         return this.edge.edgeIndex < this.edge._edges.length;
    177     },
    178 
    179     get index()
    180     {
    181         return this.edge.edgeIndex;
    182     },
    183 
    184     set index(newIndex)
    185     {
    186         this.edge.edgeIndex = newIndex;
    187     },
    188 
    189     get item()
    190     {
    191         return this.edge;
    192     },
    193 
    194     next: function()
    195     {
    196         this.edge.edgeIndex += this.edge._snapshot._edgeFieldsCount;
    197     }
    198 };
    199 
    200 WebInspector.HeapSnapshotRetainerEdge = function(snapshot, retainers, retainerIndex)
    201 {
    202     this._snapshot = snapshot;
    203     this._retainers = retainers;
    204     this.retainerIndex = retainerIndex || 0;
    205 }
    206 
    207 WebInspector.HeapSnapshotRetainerEdge.prototype = {
    208     clone: function()
    209     {
    210         return new WebInspector.HeapSnapshotRetainerEdge(this._snapshot, this._retainers, this.retainerIndex);
    211     },
    212 
    213     get hasStringName()
    214     {
    215         return this._edge.hasStringName;
    216     },
    217 
    218     get isElement()
    219     {
    220         return this._edge.isElement;
    221     },
    222 
    223     get isHidden()
    224     {
    225         return this._edge.isHidden;
    226     },
    227 
    228     get isInternal()
    229     {
    230         return this._edge.isInternal;
    231     },
    232 
    233     get isInvisible()
    234     {
    235         return this._edge.isInvisible;
    236     },
    237 
    238     get isShortcut()
    239     {
    240         return this._edge.isShortcut;
    241     },
    242 
    243     get name()
    244     {
    245         return this._edge.name;
    246     },
    247 
    248     get node()
    249     {
    250         return this._node;
    251     },
    252 
    253     get nodeIndex()
    254     {
    255         return this._nodeIndex;
    256     },
    257 
    258     get retainerIndex()
    259     {
    260         return this._retainerIndex;
    261     },
    262 
    263     set retainerIndex(newIndex)
    264     {
    265         if (newIndex !== this._retainerIndex) {
    266             this._retainerIndex = newIndex;
    267             this._setupEdge();
    268         }
    269     },
    270 
    271     _setupEdge: function()
    272     {
    273         var globalEdgeIndex = this._retainers.item(this._retainerIndex);
    274         this._nodeIndex = this._snapshot._findNearestNodeIndex(globalEdgeIndex);
    275         this._node = new WebInspector.HeapSnapshotNode(this._snapshot, this._nodeIndex);
    276         var edgeIndex = globalEdgeIndex - this._nodeIndex - this._snapshot._firstEdgeOffset;
    277         this._edge = new WebInspector.HeapSnapshotEdge(this._snapshot, this._node.rawEdges, edgeIndex);
    278     },
    279 
    280     toString: function()
    281     {
    282         return this._edge.toString();
    283     },
    284 
    285     get type()
    286     {
    287         return this._edge.type;
    288     }
    289 }
    290 
    291 WebInspector.HeapSnapshotRetainerEdgeIterator = function(retainer)
    292 {
    293     this.retainer = retainer;
    294 }
    295 
    296 WebInspector.HeapSnapshotRetainerEdgeIterator.prototype = {
    297     first: function()
    298     {
    299         this.retainer.retainerIndex = 0;
    300     },
    301 
    302     hasNext: function()
    303     {
    304         return this.retainer.retainerIndex < this.retainer._retainers.length;
    305     },
    306 
    307     get index()
    308     {
    309         return this.retainer.retainerIndex;
    310     },
    311 
    312     set index(newIndex)
    313     {
    314         this.retainer.retainerIndex = newIndex;
    315     },
    316 
    317     get item()
    318     {
    319         return this.retainer;
    320     },
    321 
    322     next: function()
    323     {
    324         ++this.retainer.retainerIndex;
    325     }
    326 };
    327 
    328 WebInspector.HeapSnapshotNode = function(snapshot, nodeIndex)
    329 {
    330     this._snapshot = snapshot;
    331     this._firstNodeIndex = nodeIndex;
    332     this.nodeIndex = nodeIndex;
    333 }
    334 
    335 WebInspector.HeapSnapshotNode.prototype = {
    336     get className()
    337     {
    338         switch (this.type) {
    339         case "hidden":
    340             return WebInspector.UIString("(system)");
    341         case "object":
    342             return this.name;
    343         case "native": {
    344             var entitiesCountPos = this.name.indexOf("/");
    345             return entitiesCountPos !== -1 ? this.name.substring(0, entitiesCountPos).trimRight() : this.name;
    346         }
    347         case "code":
    348             return WebInspector.UIString("(compiled code)");
    349         default:
    350             return "(" + this.type + ")";
    351         }
    352     },
    353 
    354     get dominatorIndex()
    355     {
    356         return this._nodes[this.nodeIndex + this._snapshot._dominatorOffset];
    357     },
    358 
    359     get edges()
    360     {
    361         return new WebInspector.HeapSnapshotEdgeIterator(new WebInspector.HeapSnapshotEdge(this._snapshot, this.rawEdges));
    362     },
    363 
    364     get edgesCount()
    365     {
    366         return this._nodes[this.nodeIndex + this._snapshot._edgesCountOffset];
    367     },
    368 
    369     get id()
    370     {
    371         return this._nodes[this.nodeIndex + this._snapshot._nodeIdOffset];
    372     },
    373 
    374     get instancesCount()
    375     {
    376         return this._nodes[this.nodeIndex + this._snapshot._nodeInstancesCountOffset];
    377     },
    378 
    379     get isHidden()
    380     {
    381         return this._type() === this._snapshot._nodeHiddenType;
    382     },
    383 
    384     get isRoot()
    385     {
    386         return this.nodeIndex === this._snapshot._rootNodeIndex;
    387     },
    388 
    389     get name()
    390     {
    391         return this._snapshot._strings[this._name()];
    392     },
    393 
    394     get rawEdges()
    395     {
    396         var firstEdgeIndex = this._firstEdgeIndex();
    397         return new WebInspector.HeapSnapshotArraySlice(this._snapshot, "_nodes", firstEdgeIndex, firstEdgeIndex + this.edgesCount * this._snapshot._edgeFieldsCount);
    398     },
    399 
    400     get retainedSize()
    401     {
    402         return this._nodes[this.nodeIndex + this._snapshot._nodeRetainedSizeOffset];
    403     },
    404 
    405     get retainers()
    406     {
    407         return new WebInspector.HeapSnapshotRetainerEdgeIterator(new WebInspector.HeapSnapshotRetainerEdge(this._snapshot, this._snapshot._retainersForNode(this)));
    408     },
    409 
    410     get selfSize()
    411     {
    412         return this._nodes[this.nodeIndex + this._snapshot._nodeSelfSizeOffset];
    413     },
    414 
    415     get type()
    416     {
    417         return this._snapshot._nodeTypes[this._type()];
    418     },
    419 
    420     _name: function()
    421     {
    422         return this._nodes[this.nodeIndex + this._snapshot._nodeNameOffset];
    423     },
    424 
    425     get _nodes()
    426     {
    427         return this._snapshot._nodes;
    428     },
    429 
    430     _firstEdgeIndex: function()
    431     {
    432         return this.nodeIndex + this._snapshot._firstEdgeOffset;
    433     },
    434 
    435     get _nextNodeIndex()
    436     {
    437         return this._firstEdgeIndex() + this.edgesCount * this._snapshot._edgeFieldsCount;
    438     },
    439 
    440     _type: function()
    441     {
    442         return this._nodes[this.nodeIndex + this._snapshot._nodeTypeOffset];
    443     }
    444 };
    445 
    446 WebInspector.HeapSnapshotNodeIterator = function(node)
    447 {
    448     this.node = node;
    449 }
    450 
    451 WebInspector.HeapSnapshotNodeIterator.prototype = {
    452     first: function()
    453     {
    454         this.node.nodeIndex = this.node._firstNodeIndex;
    455     },
    456 
    457     hasNext: function()
    458     {
    459         return this.node.nodeIndex < this.node._nodes.length;
    460     },
    461 
    462     get index()
    463     {
    464         return this.node.nodeIndex;
    465     },
    466 
    467     set index(newIndex)
    468     {
    469         this.node.nodeIndex = newIndex;
    470     },
    471 
    472     get item()
    473     {
    474         return this.node;
    475     },
    476 
    477     next: function()
    478     {
    479         this.node.nodeIndex = this.node._nextNodeIndex;
    480     }
    481 }
    482 
    483 WebInspector.HeapSnapshot = function(profile)
    484 {
    485     this.uid = profile.snapshot.uid;
    486     this._nodes = profile.nodes;
    487     this._strings = profile.strings;
    488 
    489     this._init();
    490 }
    491 
    492 WebInspector.HeapSnapshot.prototype = {
    493     _init: function()
    494     {
    495         this._metaNodeIndex = 0;
    496         this._rootNodeIndex = 1;
    497         var meta = this._nodes[this._metaNodeIndex];
    498         this._nodeTypeOffset = meta.fields.indexOf("type");
    499         this._nodeNameOffset = meta.fields.indexOf("name");
    500         this._nodeIdOffset = meta.fields.indexOf("id");
    501         this._nodeInstancesCountOffset = this._nodeIdOffset;
    502         this._nodeSelfSizeOffset = meta.fields.indexOf("self_size");
    503         this._nodeRetainedSizeOffset = meta.fields.indexOf("retained_size");
    504         this._dominatorOffset = meta.fields.indexOf("dominator");
    505         this._edgesCountOffset = meta.fields.indexOf("children_count");
    506         this._firstEdgeOffset = meta.fields.indexOf("children");
    507         this._nodeTypes = meta.types[this._nodeTypeOffset];
    508         this._nodeHiddenType = this._nodeTypes.indexOf("hidden");
    509         var edgesMeta = meta.types[this._firstEdgeOffset];
    510         this._edgeFieldsCount = edgesMeta.fields.length;
    511         this._edgeTypeOffset = edgesMeta.fields.indexOf("type");
    512         this._edgeNameOffset = edgesMeta.fields.indexOf("name_or_index");
    513         this._edgeToNodeOffset = edgesMeta.fields.indexOf("to_node");
    514         this._edgeTypes = edgesMeta.types[this._edgeTypeOffset];
    515         this._edgeElementType = this._edgeTypes.indexOf("element");
    516         this._edgeHiddenType = this._edgeTypes.indexOf("hidden");
    517         this._edgeInternalType = this._edgeTypes.indexOf("internal");
    518         this._edgeShortcutType = this._edgeTypes.indexOf("shortcut");
    519         this._edgeInvisibleType = this._edgeTypes.length;
    520         this._edgeTypes.push("invisible");
    521 
    522         this._markInvisibleEdges();
    523     },
    524 
    525     dispose: function()
    526     {
    527         delete this._nodes;
    528         delete this._strings;
    529         delete this._idsList;
    530         delete this._retainers;
    531         delete this._retainerIndex;
    532         delete this._nodeIndex;
    533         if (this._aggregates) {
    534             delete this._aggregates;
    535             this._aggregatesWithIndexes = false;
    536         }
    537         delete this._baseNodeIds;
    538     },
    539 
    540     get _allNodes()
    541     {
    542         return new WebInspector.HeapSnapshotNodeIterator(this.rootNode);
    543     },
    544 
    545     get nodeCount()
    546     {
    547         if (this._nodeCount)
    548             return this._nodeCount;
    549 
    550         this._nodeCount = 0;
    551         for (var iter = this._allNodes; iter.hasNext(); iter.next())
    552             ++this._nodeCount;
    553         return this._nodeCount;
    554     },
    555 
    556     nodeFieldValuesByIndex: function(fieldName, indexes)
    557     {
    558         var node = new WebInspector.HeapSnapshotNode(this);
    559         var result = new Array(indexes.length);
    560         for (var i = 0, l = indexes.length; i < l; ++i) {
    561             node.nodeIndex = indexes[i];
    562             result[i] = node[fieldName];
    563         }
    564         return result;
    565     },
    566 
    567     get rootNode()
    568     {
    569         return new WebInspector.HeapSnapshotNode(this, this._rootNodeIndex);
    570     },
    571 
    572     get rootNodeIndex()
    573     {
    574         return this._rootNodeIndex;
    575     },
    576 
    577     get totalSize()
    578     {
    579         return this.rootNode.retainedSize;
    580     },
    581 
    582     hasId: function(id)
    583     {
    584         return this.nodeIds.binaryIndexOf(id, this._numbersComparator) >= 0;
    585     },
    586 
    587     get nodeIds()
    588     {
    589         if (!this._idsList)
    590             this._buildIdsList();
    591         return this._idsList;
    592     },
    593 
    594     _retainersForNode: function(node)
    595     {
    596         if (!this._retainers)
    597             this._buildRetainers();
    598 
    599         var retIndexFrom = this._getRetainerIndex(node.nodeIndex);
    600         var retIndexTo = this._getRetainerIndex(node._nextNodeIndex);
    601         return new WebInspector.HeapSnapshotArraySlice(this, "_retainers", retIndexFrom, retIndexTo);
    602     },
    603 
    604     aggregates: function(withNodeIndexes)
    605     {
    606         if (!this._aggregates)
    607             this._buildAggregates();
    608         if (withNodeIndexes && !this._aggregatesWithIndexes)
    609             this._buildAggregatesIndexes();
    610         return this._aggregates;
    611     },
    612 
    613     _buildRetainers: function()
    614     {
    615         if (!this._nodeIndex)
    616             this._buildNodeIndex();
    617 
    618         this._retainerIndex = new Array(this._nodeIndex.length);
    619         for (var i = 0, l = this._retainerIndex.length; i < l; ++i)
    620             this._retainerIndex[i] = 0;
    621         for (var nodesIter = this._allNodes; nodesIter.hasNext(); nodesIter.next()) {
    622             var node = nodesIter.node;
    623             for (var edgesIter = node.edges; edgesIter.hasNext(); edgesIter.next()) {
    624                 var edge = edgesIter.edge;
    625                 var nodeIndex = edge.nodeIndex;
    626                 var position = this._findNodePositionInIndex(nodeIndex);
    627                 ++this._retainerIndex[position];
    628             }
    629         }
    630         var retainerCount = 0;
    631         for (i = 0, l = this._retainerIndex.length; i < l; ++i)
    632             retainerCount += this._retainerIndex[i];
    633         this._retainers = new Array(retainerCount + 1);
    634         var retainerPosition = 0;
    635         for (i = 0, l = this._retainerIndex.length; i < l; ++i) {
    636             retainerCount = this._retainers[retainerPosition] = this._retainerIndex[i];
    637             this._retainerIndex[i] = retainerPosition;
    638             retainerPosition += retainerCount;
    639         }
    640         for (nodesIter = this._allNodes; nodesIter.hasNext(); nodesIter.next()) {
    641             var node = nodesIter.node;
    642             for (var edgesIter = node.edges; edgesIter.hasNext(); edgesIter.next()) {
    643                 var edge = edgesIter.edge;
    644                 var nodeIndex = edge.nodeIndex;
    645                 var retIndex = this._getRetainerIndex(nodeIndex);
    646                 var idx = retIndex + (--this._retainers[retIndex]);
    647                 this._retainers[idx] = node.nodeIndex + this._firstEdgeOffset + edge.edgeIndex;
    648             }
    649         }
    650     },
    651 
    652     _buildAggregates: function()
    653     {
    654         this._aggregates = {};
    655         for (var iter = this._allNodes; iter.hasNext(); iter.next()) {
    656             var node = iter.node;
    657             var className = node.className;
    658             var nameMatters = node.type === "object" || node.type === "native";
    659             if (node.type !== "native" && node.selfSize === 0)
    660                 continue;
    661             if (!(className in this._aggregates))
    662                 this._aggregates[className] = { count: 0, self: 0, maxRet: 0, type: node.type, name: nameMatters ? node.name : null, idxs: [] };
    663             var clss = this._aggregates[className];
    664             ++clss.count;
    665             clss.self += node.selfSize;
    666             if (node.retainedSize > clss.maxRet)
    667                 clss.maxRet = node.retainedSize;
    668         }
    669     },
    670 
    671     _buildAggregatesIndexes: function()
    672     {
    673         for (var iter = this._allNodes; iter.hasNext(); iter.next()) {
    674             var node = iter.node;
    675             var className = node.className;
    676             var clss = this._aggregates[className];
    677             if (clss)
    678                 clss.idxs.push(node.nodeIndex);
    679         }
    680 
    681         var nodeA = new WebInspector.HeapSnapshotNode(this);
    682         var nodeB = new WebInspector.HeapSnapshotNode(this);
    683         for (var clss in this._aggregates)
    684             this._aggregates[clss].idxs.sort(
    685                 function(idxA, idxB) {
    686                     nodeA.nodeIndex = idxA;
    687                     nodeB.nodeIndex = idxB;
    688                     return nodeA.id < nodeB.id ? -1 : 1;
    689                 });
    690 
    691         this._aggregatesWithIndexes = true;
    692     },
    693 
    694     _buildIdsList: function()
    695     {
    696         var count = 0;
    697         for (var nodesIter = this._allNodes; nodesIter.hasNext(); nodesIter.next(), ++count);
    698         this._idsList = new Array(count);
    699         count = 0;
    700         for (nodesIter = this._allNodes; nodesIter.hasNext(); nodesIter.next(), ++count)
    701             this._idsList[count] = nodesIter.node.id;
    702         this._idsList.sort(this._numbersComparator);
    703     },
    704 
    705     _buildNodeIndex: function()
    706     {
    707         var count = 0;
    708         for (var nodesIter = this._allNodes; nodesIter.hasNext(); nodesIter.next(), ++count);
    709         this._nodeIndex = new Array(count + 1);
    710         count = 0;
    711         for (nodesIter = this._allNodes; nodesIter.hasNext(); nodesIter.next(), ++count)
    712             this._nodeIndex[count] = nodesIter.index;
    713         this._nodeIndex[count] = this._nodes.length;
    714     },
    715 
    716     _findNodePositionInIndex: function(index)
    717     {
    718         return binarySearch(index, this._nodeIndex, this._numbersComparator);
    719     },
    720 
    721     _findNearestNodeIndex: function(index)
    722     {
    723         var result = this._findNodePositionInIndex(index);
    724         if (result < 0) {
    725             result = -result - 1;
    726             nodeIndex = this._nodeIndex[result];
    727             // Binary search can return either maximum lower value, or minimum higher value.
    728             if (nodeIndex > index)
    729                 nodeIndex = this._nodeIndex[result - 1];
    730         } else
    731             var nodeIndex = this._nodeIndex[result];
    732         return nodeIndex;
    733     },
    734 
    735     _getRetainerIndex: function(nodeIndex)
    736     {
    737         var nodePosition = this._findNodePositionInIndex(nodeIndex);
    738         return this._retainerIndex[nodePosition];
    739     },
    740 
    741     _markInvisibleEdges: function()
    742     {
    743         // Mark hidden edges of global objects as invisible.
    744         // FIXME: This is a temporary measure. Normally, we should
    745         // really hide all hidden nodes.
    746         for (var iter = this.rootNode.edges; iter.hasNext(); iter.next()) {
    747             var edge = iter.edge;
    748             if (!edge.isShortcut)
    749                 continue;
    750             var node = edge.node;
    751             var propNames = {};
    752             for (var innerIter = node.edges; innerIter.hasNext(); innerIter.next()) {
    753                 var globalObjEdge = innerIter.edge;
    754                 if (globalObjEdge.isShortcut)
    755                     propNames[globalObjEdge._nameOrIndex] = true;
    756             }
    757             for (innerIter.first(); innerIter.hasNext(); innerIter.next()) {
    758                 var globalObjEdge = innerIter.edge;
    759                 if (!globalObjEdge.isShortcut
    760                     && globalObjEdge.node.isHidden
    761                     && globalObjEdge._hasStringName
    762                     && (globalObjEdge._nameOrIndex in propNames))
    763                     this._nodes[globalObjEdge._edges._start + globalObjEdge.edgeIndex + this._edgeTypeOffset] = this._edgeInvisibleType;
    764             }
    765         }
    766     },
    767 
    768     _numbersComparator: function(a, b)
    769     {
    770         return a < b ? -1 : (a > b ? 1 : 0);
    771     },
    772 
    773     baseSnapshotHasNode: function(baseSnapshotId, className, nodeId)
    774     {
    775         return this._baseNodeIds[baseSnapshotId][className].binaryIndexOf(nodeId, this._numbersComparator) !== -1;
    776     },
    777 
    778     updateBaseNodeIds: function(baseSnapshotId, className, nodeIds)
    779     {
    780         if (!this._baseNodeIds)
    781             this._baseNodeIds = [];
    782         if (!this._baseNodeIds[baseSnapshotId])
    783             this._baseNodeIds[baseSnapshotId] = {};
    784         this._baseNodeIds[baseSnapshotId][className] = nodeIds;
    785     }
    786 };
    787 
    788 WebInspector.HeapSnapshotFilteredOrderedIterator = function(iterator, filter)
    789 {
    790     this._filter = filter;
    791     this._iterator = iterator;
    792     this._iterationOrder = null;
    793     this._position = 0;
    794     this._lastComparator = null;
    795 }
    796 
    797 WebInspector.HeapSnapshotFilteredOrderedIterator.prototype = {
    798     _createIterationOrder: function()
    799     {
    800         this._iterationOrder = [];
    801         var iterator = this._iterator;
    802         if (!this._filter) {
    803             for (iterator.first(); iterator.hasNext(); iterator.next())
    804                 this._iterationOrder.push(iterator.index);
    805         } else {
    806             for (iterator.first(); iterator.hasNext(); iterator.next()) {
    807                 if (this._filter(iterator.item))
    808                     this._iterationOrder.push(iterator.index);
    809             }
    810         }
    811     },
    812 
    813     first: function()
    814     {
    815         this._position = 0;
    816     },
    817 
    818     hasNext: function()
    819     {
    820         return this._position < this._iterationOrder.length;
    821     },
    822 
    823     get isEmpty()
    824     {
    825         if (this._iterationOrder)
    826             return !this._iterationOrder.length;
    827         var iterator = this._iterator;
    828         if (!this._filter) {
    829             iterator.first();
    830             return !iterator.hasNext();
    831         }
    832         for (iterator.first(); iterator.hasNext(); iterator.next())
    833             if (this._filter(iterator.item)) return false;
    834         return true;
    835     },
    836 
    837     get item()
    838     {
    839         this._iterator.index = this._iterationOrder[this._position];
    840         return this._iterator.item;
    841     },
    842 
    843     get length()
    844     {
    845         if (!this._iterationOrder)
    846             this._createIterationOrder();
    847         return this._iterationOrder.length;
    848     },
    849 
    850     next: function()
    851     {
    852         ++this._position;
    853     },
    854 }
    855 
    856 WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator = function(fieldNames)
    857 {
    858     return {fieldName1:fieldNames[0], ascending1:fieldNames[1], fieldName2:fieldNames[2], ascending2:fieldNames[3]};
    859 }
    860 
    861 WebInspector.HeapSnapshotEdgesProvider = function(snapshot, nodeIndex, filter)
    862 {
    863     this.snapshot = snapshot;
    864     var node = new WebInspector.HeapSnapshotNode(snapshot, nodeIndex);
    865     WebInspector.HeapSnapshotFilteredOrderedIterator.call(this, new WebInspector.HeapSnapshotEdgeIterator(new WebInspector.HeapSnapshotEdge(snapshot, node.rawEdges)), filter);
    866 }
    867 
    868 WebInspector.HeapSnapshotEdgesProvider.prototype = {
    869     sort: function(comparator)
    870     {
    871         if (this._lastComparator === comparator)
    872             return false;
    873         this._lastComparator = comparator;
    874         var fieldName1 = comparator.fieldName1;
    875         var fieldName2 = comparator.fieldName2;
    876         var ascending1 = comparator.ascending1;
    877         var ascending2 = comparator.ascending2;
    878 
    879         var edgeA = this._iterator.item.clone();
    880         var edgeB = edgeA.clone();
    881         var nodeA = new WebInspector.HeapSnapshotNode(this.snapshot);
    882         var nodeB = new WebInspector.HeapSnapshotNode(this.snapshot);
    883 
    884         function sortByEdgeFieldName(ascending, indexA, indexB)
    885         {
    886             edgeA.edgeIndex = indexA;
    887             edgeB.edgeIndex = indexB;
    888             if (edgeB.name === "__proto__") return -1;
    889             if (edgeA.name === "__proto__") return 1;
    890             var result =
    891                 edgeA.hasStringName === edgeB.hasStringName ?
    892                 (edgeA.name < edgeB.name ? -1 : (edgeA.name > edgeB.name ? 1 : 0)) :
    893                 (edgeA.hasStringName ? -1 : 1);
    894             return ascending ? result : -result;
    895         }
    896 
    897         function sortByNodeField(fieldName, ascending, indexA, indexB)
    898         {
    899             edgeA.edgeIndex = indexA;
    900             edgeB.edgeIndex = indexB;
    901             nodeA.nodeIndex = edgeA.nodeIndex;
    902             nodeB.nodeIndex = edgeB.nodeIndex;
    903             var valueA = nodeA[fieldName];
    904             var valueB = nodeB[fieldName];
    905             var result = valueA < valueB ? -1 : (valueA > valueB ? 1 : 0);
    906             return ascending ? result : -result;
    907         }
    908 
    909         if (!this._iterationOrder)
    910             this._createIterationOrder();
    911 
    912         function sortByEdgeAndNode(indexA, indexB) {
    913             var result = sortByEdgeFieldName(ascending1, indexA, indexB);
    914             if (result === 0)
    915                 result = sortByNodeField(fieldName2, ascending2, indexA, indexB);
    916             return result;
    917         }
    918 
    919         function sortByNodeAndEdge(indexA, indexB) {
    920             var result = sortByNodeField(fieldName1, ascending1, indexA, indexB);
    921             if (result === 0)
    922                 result = sortByEdgeFieldName(ascending2, indexA, indexB);
    923             return result;
    924         }
    925 
    926         function sortByNodeAndNode(indexA, indexB) {
    927             var result = sortByNodeField(fieldName1, ascending1, indexA, indexB);
    928             if (result === 0)
    929                 result = sortByNodeField(fieldName2, ascending2, indexA, indexB);
    930             return result;
    931         }
    932 
    933         if (fieldName1 === "!edgeName")
    934             this._iterationOrder.sort(sortByEdgeAndNode);
    935         else if (fieldName2 === "!edgeName")
    936             this._iterationOrder.sort(sortByNodeAndEdge);
    937         else
    938             this._iterationOrder.sort(sortByNodeAndNode);
    939         return true;
    940     }
    941 };
    942 
    943 WebInspector.HeapSnapshotEdgesProvider.prototype.__proto__ = WebInspector.HeapSnapshotFilteredOrderedIterator.prototype;
    944 
    945 WebInspector.HeapSnapshotNodesProvider = function(snapshot, filter)
    946 {
    947     this.snapshot = snapshot;
    948     WebInspector.HeapSnapshotFilteredOrderedIterator.call(this, snapshot._allNodes, filter);
    949 }
    950 
    951 WebInspector.HeapSnapshotNodesProvider.prototype = {
    952     sort: function(comparator)
    953     {
    954         if (this._lastComparator === comparator)
    955             return false;
    956         this._lastComparator = comparator;
    957         var fieldName1 = comparator.fieldName1;
    958         var fieldName2 = comparator.fieldName2;
    959         var ascending1 = comparator.ascending1;
    960         var ascending2 = comparator.ascending2;
    961 
    962         var nodeA = new WebInspector.HeapSnapshotNode(this.snapshot);
    963         var nodeB = new WebInspector.HeapSnapshotNode(this.snapshot);
    964 
    965         function sortByNodeField(fieldName, ascending, indexA, indexB)
    966         {
    967             nodeA.nodeIndex = indexA;
    968             nodeB.nodeIndex = indexB;
    969             var valueA = nodeA[fieldName];
    970             var valueB = nodeB[fieldName];
    971             var result = valueA < valueB ? -1 : (valueA > valueB ? 1 : 0);
    972             return ascending ? result : -result;
    973         }
    974 
    975         if (!this._iterationOrder)
    976             this._createIterationOrder();
    977 
    978         function sortByComparator(indexA, indexB) {
    979             var result = sortByNodeField(fieldName1, ascending1, indexA, indexB);
    980             if (result === 0)
    981                 result = sortByNodeField(fieldName2, ascending2, indexA, indexB);
    982             return result;
    983         }
    984 
    985         this._iterationOrder.sort(sortByComparator);
    986         return true;
    987     }
    988 };
    989 
    990 WebInspector.HeapSnapshotNodesProvider.prototype.__proto__ = WebInspector.HeapSnapshotFilteredOrderedIterator.prototype;
    991 
    992 WebInspector.HeapSnapshotPathFinder = function(snapshot, targetNodeIndex)
    993 {
    994     this._snapshot = snapshot;
    995     this._maxLength = 1;
    996     this._lengthLimit = 15;
    997     this._targetNodeIndex = targetNodeIndex;
    998     this._currentPath = null;
    999     this._skipHidden = !WebInspector.DetailedHeapshotView.prototype.showHiddenData;
   1000     this._rootChildren = this._fillRootChildren();
   1001 }
   1002 
   1003 WebInspector.HeapSnapshotPathFinder.prototype = {
   1004     findNext: function()
   1005     {
   1006         for (var i = 0; i < 100000; ++i) {
   1007             if (!this._buildNextPath()) {
   1008                 if (++this._maxLength >= this._lengthLimit)
   1009                     return null;
   1010                 this._currentPath = null;
   1011                 if (!this._buildNextPath())
   1012                     return null;
   1013             }
   1014             if (this._isPathFound())
   1015                 return {path:this._pathToString(this._currentPath), len:this._currentPath.length};
   1016         }
   1017 
   1018         return false;
   1019     },
   1020 
   1021     updateRoots: function(filter)
   1022     {
   1023         this._rootChildren = this._fillRootChildren(filter);
   1024     },
   1025 
   1026     _fillRootChildren: function(filter)
   1027     {
   1028         var result = [];
   1029         for (var iter = this._snapshot.rootNode.edges; iter.hasNext(); iter.next()) {
   1030             if (!filter || filter(iter.edge.node))
   1031                 result[iter.edge.nodeIndex] = true;
   1032         }
   1033         return result;
   1034     },
   1035 
   1036     _appendToCurrentPath: function(iter)
   1037     {
   1038         this._currentPath._cache[this._lastEdge.nodeIndex] = true;
   1039         this._currentPath.push(iter);
   1040     },
   1041 
   1042     _removeLastFromCurrentPath: function()
   1043     {
   1044         this._currentPath.pop();
   1045         delete this._currentPath._cache[this._lastEdge.nodeIndex];
   1046     },
   1047 
   1048     _hasInPath: function(nodeIndex)
   1049     {
   1050         return this._targetNodeIndex === nodeIndex
   1051             || !!this._currentPath._cache[nodeIndex];
   1052     },
   1053 
   1054     _isPathFound: function()
   1055     {
   1056         return this._currentPath.length === this._maxLength
   1057             && this._lastEdge.nodeIndex in this._rootChildren;
   1058     },
   1059 
   1060     get _lastEdgeIter()
   1061     {
   1062         return this._currentPath[this._currentPath.length - 1];
   1063     },
   1064 
   1065     get _lastEdge()
   1066     {
   1067         return this._lastEdgeIter.item;
   1068     },
   1069 
   1070     _skipEdge: function(edge)
   1071     {
   1072         return edge.isInvisible
   1073             || (this._skipHidden && (edge.isHidden || edge.node.isHidden))
   1074             || this._hasInPath(edge.nodeIndex);
   1075     },
   1076 
   1077     _nextEdgeIter: function()
   1078     {
   1079         var iter = this._lastEdgeIter;
   1080         while (this._skipEdge(iter.item) && iter.hasNext())
   1081             iter.next();
   1082         return iter;
   1083     },
   1084 
   1085     _buildNextPath: function()
   1086     {
   1087         if (this._currentPath !== null) {
   1088             var iter = this._lastEdgeIter;
   1089             while (true) {
   1090                 iter.next();
   1091                 if (iter.hasNext())
   1092                     return true;
   1093                 while (true) {
   1094                     if (this._currentPath.length > 1) {
   1095                         this._removeLastFromCurrentPath();
   1096                         iter = this._lastEdgeIter;
   1097                         iter.next();
   1098                         iter = this._nextEdgeIter();
   1099                         if (iter.hasNext()) {
   1100                             while (this._currentPath.length < this._maxLength) {
   1101                                 iter = this._nextEdgeIter();
   1102                                 if (iter.hasNext())
   1103                                     this._appendToCurrentPath(iter.item.node.retainers);
   1104                                 else
   1105                                     return true;
   1106                             }
   1107                             return true;
   1108                         }
   1109                     } else
   1110                         return false;
   1111                 }
   1112             }
   1113         } else {
   1114             var node = new WebInspector.HeapSnapshotNode(this._snapshot, this._targetNodeIndex);
   1115             this._currentPath = [node.retainers];
   1116             this._currentPath._cache = {};
   1117             while (this._currentPath.length < this._maxLength) {
   1118                 var iter = this._nextEdgeIter();
   1119                 if (iter.hasNext())
   1120                     this._appendToCurrentPath(iter.item.node.retainers);
   1121                 else
   1122                     break;
   1123             }
   1124             return true;
   1125         }
   1126     },
   1127 
   1128     _nodeToString: function(node)
   1129     {
   1130         if (node.id === 1)
   1131             return node.name;
   1132         else
   1133             return node.name + "@" + node.id;
   1134     },
   1135 
   1136     _pathToString: function(path)
   1137     {
   1138         if (!path)
   1139             return "";
   1140         var sPath = [];
   1141         for (var j = 0; j < path.length; ++j)
   1142             sPath.push(path[j].item.toString());
   1143         sPath.push(this._nodeToString(path[path.length - 1].item.node));
   1144         sPath.reverse();
   1145         return sPath.join("");
   1146     }
   1147 };
   1148 
   1149 WebInspector.HeapSnapshotsDiff = function(snapshot, className)
   1150 {
   1151     this._snapshot = snapshot;
   1152     this._className = className;
   1153 };
   1154 
   1155 WebInspector.HeapSnapshotsDiff.prototype = {
   1156     set baseIds(baseIds)
   1157     {
   1158         this._baseIds = baseIds;
   1159     },
   1160 
   1161     set baseSelfSizes(baseSelfSizes)
   1162     {
   1163         this._baseSelfSizes = baseSelfSizes;
   1164     },
   1165 
   1166     calculate: function()
   1167     {
   1168         var indexes = this._snapshot.aggregates(true)[this._className].idxs;
   1169         var i = 0, l = this._baseIds.length;
   1170         var j = 0, m = indexes.length;
   1171         var diff = { addedCount: 0, removedCount: 0, addedSize: 0, removedSize: 0 };
   1172 
   1173         var nodeB = new WebInspector.HeapSnapshotNode(this._snapshot);
   1174         while (i < l && j < m) {
   1175             var nodeAId = this._baseIds[i];
   1176             if (nodeAId < nodeB.id) {
   1177                 diff.removedCount++;
   1178                 diff.removedSize += this._baseSelfSizes[i];
   1179                 ++i;
   1180             } else if (nodeAId > nodeB.id) {
   1181                 diff.addedCount++;
   1182                 diff.addedSize += nodeB.selfSize;
   1183                 nodeB.nodeIndex = indexes[++j];
   1184             } else {
   1185                 ++i;
   1186                 nodeB.nodeIndex = indexes[++j];
   1187             }
   1188         }
   1189         while (i < l) {
   1190             diff.removedCount++;
   1191             diff.removedSize += this._baseSelfSizes[i];
   1192             ++i;
   1193         }
   1194         while (j < m) {
   1195             diff.addedCount++;
   1196             diff.addedSize += nodeB.selfSize;
   1197             nodeB.nodeIndex = indexes[++j];
   1198         }
   1199         diff.countDelta = diff.addedCount - diff.removedCount;
   1200         diff.sizeDelta = diff.addedSize - diff.removedSize;
   1201         return diff;
   1202     }
   1203 };
   1204