Home | History | Annotate | Download | only in heap_snapshot_worker
      1 /*
      2  * Copyright (C) 2012 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  * @extends {WebInspector.HeapSnapshot}
     34  * @param {!Object} profile
     35  * @param {!WebInspector.HeapSnapshotProgress} progress
     36  * @param {boolean} showHiddenData
     37  */
     38 WebInspector.JSHeapSnapshot = function(profile, progress, showHiddenData)
     39 {
     40     this._nodeFlags = { // bit flags
     41         canBeQueried: 1,
     42         detachedDOMTreeNode: 2,
     43         pageObject: 4, // The idea is to track separately the objects owned by the page and the objects owned by debugger.
     44 
     45         visitedMarkerMask: 0x0ffff,
     46         visitedMarker:     0x10000
     47     };
     48     this._lazyStringCache = { };
     49     this._showHiddenData = showHiddenData;
     50     WebInspector.HeapSnapshot.call(this, profile, progress);
     51 }
     52 
     53 WebInspector.JSHeapSnapshot.prototype = {
     54     /**
     55      * @param {number} nodeIndex
     56      * @return {!WebInspector.JSHeapSnapshotNode}
     57      */
     58     createNode: function(nodeIndex)
     59     {
     60         return new WebInspector.JSHeapSnapshotNode(this, nodeIndex);
     61     },
     62 
     63     /**
     64      * @override
     65      * @param {number} edgeIndex
     66      * @return {!WebInspector.JSHeapSnapshotEdge}
     67      */
     68     createEdge: function(edgeIndex)
     69     {
     70         return new WebInspector.JSHeapSnapshotEdge(this, edgeIndex);
     71     },
     72 
     73     /**
     74      * @override
     75      * @param {number} retainerIndex
     76      * @return {!WebInspector.JSHeapSnapshotRetainerEdge}
     77      */
     78     createRetainingEdge: function(retainerIndex)
     79     {
     80         return new WebInspector.JSHeapSnapshotRetainerEdge(this, retainerIndex);
     81     },
     82 
     83     /**
     84      * @override
     85      * @return {?function(!WebInspector.JSHeapSnapshotNode):boolean}
     86      */
     87     classNodesFilter: function()
     88     {
     89         var mapAndFlag = this.userObjectsMapAndFlag();
     90         if (!mapAndFlag)
     91             return null;
     92         var map = mapAndFlag.map;
     93         var flag = mapAndFlag.flag;
     94         /**
     95          * @param {!WebInspector.JSHeapSnapshotNode} node
     96          * @return {boolean}
     97          */
     98         function filter(node)
     99         {
    100             return !!(map[node.ordinal()] & flag);
    101         }
    102         return filter;
    103     },
    104 
    105     /**
    106      * @return {function(!WebInspector.HeapSnapshotEdge):boolean}
    107      */
    108     containmentEdgesFilter: function()
    109     {
    110         var showHiddenData = this._showHiddenData;
    111         function filter(edge)
    112         {
    113             if (edge.isInvisible())
    114                 return false;
    115             if (showHiddenData)
    116                 return true;
    117             return !edge.isHidden() && !edge.node().isHidden();
    118         }
    119         return filter;
    120     },
    121 
    122     /**
    123      * @return {function(!WebInspector.HeapSnapshotEdge):boolean}
    124      */
    125     retainingEdgesFilter: function()
    126     {
    127         var containmentEdgesFilter = this.containmentEdgesFilter();
    128         function filter(edge)
    129         {
    130             return containmentEdgesFilter(edge) && !edge.node().isRoot() && !edge.isWeak();
    131         }
    132         return filter;
    133     },
    134 
    135     _calculateFlags: function()
    136     {
    137         this._flags = new Uint32Array(this.nodeCount);
    138         this._markDetachedDOMTreeNodes();
    139         this._markQueriableHeapObjects();
    140         this._markPageOwnedNodes();
    141     },
    142 
    143     calculateDistances: function()
    144     {
    145         /**
    146          * @param {!WebInspector.HeapSnapshotNode} node
    147          * @param {!WebInspector.HeapSnapshotEdge} edge
    148          * @return {boolean}
    149          */
    150         function filter(node, edge)
    151         {
    152             if (node.isHidden())
    153                 return edge.name() !== "sloppy_function_map" || node.rawName() !== "system / NativeContext";
    154             if (node.isArray()) {
    155                 // DescriptorArrays are fixed arrays used to hold instance descriptors.
    156                 // The format of the these objects is:
    157                 //   [0]: Number of descriptors
    158                 //   [1]: Either Smi(0) if uninitialized, or a pointer to small fixed array:
    159                 //          [0]: pointer to fixed array with enum cache
    160                 //          [1]: either Smi(0) or pointer to fixed array with indices
    161                 //   [i*3+2]: i-th key
    162                 //   [i*3+3]: i-th type
    163                 //   [i*3+4]: i-th descriptor
    164                 // As long as maps may share descriptor arrays some of the descriptor
    165                 // links may not be valid for all the maps. We just skip
    166                 // all the descriptor links when calculating distances.
    167                 // For more details see http://crbug.com/413608
    168                 if (node.rawName() !== "(map descriptors)")
    169                     return true;
    170                 var index = edge.name();
    171                 return index < 2 || (index % 3) !== 1;
    172             }
    173             return true;
    174         }
    175         WebInspector.HeapSnapshot.prototype.calculateDistances.call(this, filter);
    176     },
    177 
    178     /**
    179      * @param {!WebInspector.HeapSnapshotNode} node
    180      * @return {!boolean}
    181      */
    182     _isUserRoot: function(node)
    183     {
    184         return node.isUserRoot() || node.isDocumentDOMTreesRoot();
    185     },
    186 
    187     /**
    188      * @param {function(!WebInspector.HeapSnapshotNode)} action
    189      * @param {boolean=} userRootsOnly
    190      */
    191     forEachRoot: function(action, userRootsOnly)
    192     {
    193         /**
    194          * @param {!WebInspector.HeapSnapshotNode} node
    195          * @param {string} name
    196          * @return {?WebInspector.HeapSnapshotNode}
    197          */
    198         function getChildNodeByName(node, name)
    199         {
    200             for (var iter = node.edges(); iter.hasNext(); iter.next()) {
    201                 var child = iter.edge.node();
    202                 if (child.name() === name)
    203                     return child;
    204             }
    205             return null;
    206         }
    207 
    208         var visitedNodes = {};
    209         /**
    210          * @param {!WebInspector.HeapSnapshotNode} node
    211          */
    212         function doAction(node)
    213         {
    214             var ordinal = node.ordinal();
    215             if (!visitedNodes[ordinal]) {
    216                 action(node);
    217                 visitedNodes[ordinal] = true;
    218             }
    219         }
    220 
    221         var gcRoots = getChildNodeByName(this.rootNode(), "(GC roots)");
    222         if (!gcRoots)
    223             return;
    224 
    225         if (userRootsOnly) {
    226             for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
    227                 var node = iter.edge.node();
    228                 if (this._isUserRoot(node))
    229                     doAction(node);
    230             }
    231         } else {
    232             for (var iter = gcRoots.edges(); iter.hasNext(); iter.next()) {
    233                 var subRoot = iter.edge.node();
    234                 for (var iter2 = subRoot.edges(); iter2.hasNext(); iter2.next())
    235                     doAction(iter2.edge.node());
    236                 doAction(subRoot);
    237             }
    238             for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next())
    239                 doAction(iter.edge.node())
    240         }
    241     },
    242 
    243     /**
    244      * @return {?{map: !Uint32Array, flag: number}}
    245      */
    246     userObjectsMapAndFlag: function()
    247     {
    248         return this._showHiddenData ? null : {
    249             map: this._flags,
    250             flag: this._nodeFlags.pageObject
    251         };
    252     },
    253 
    254     _flagsOfNode: function(node)
    255     {
    256         return this._flags[node.nodeIndex / this._nodeFieldCount];
    257     },
    258 
    259     _markDetachedDOMTreeNodes: function()
    260     {
    261         var flag = this._nodeFlags.detachedDOMTreeNode;
    262         var detachedDOMTreesRoot;
    263         for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
    264             var node = iter.edge.node();
    265             if (node.name() === "(Detached DOM trees)") {
    266                 detachedDOMTreesRoot = node;
    267                 break;
    268             }
    269         }
    270 
    271         if (!detachedDOMTreesRoot)
    272             return;
    273 
    274         var detachedDOMTreeRE = /^Detached DOM tree/;
    275         for (var iter = detachedDOMTreesRoot.edges(); iter.hasNext(); iter.next()) {
    276             var node = iter.edge.node();
    277             if (detachedDOMTreeRE.test(node.className())) {
    278                 for (var edgesIter = node.edges(); edgesIter.hasNext(); edgesIter.next())
    279                     this._flags[edgesIter.edge.node().nodeIndex / this._nodeFieldCount] |= flag;
    280             }
    281         }
    282     },
    283 
    284     _markQueriableHeapObjects: function()
    285     {
    286         // Allow runtime properties query for objects accessible from Window objects
    287         // via regular properties, and for DOM wrappers. Trying to access random objects
    288         // can cause a crash due to insonsistent state of internal properties of wrappers.
    289         var flag = this._nodeFlags.canBeQueried;
    290         var hiddenEdgeType = this._edgeHiddenType;
    291         var internalEdgeType = this._edgeInternalType;
    292         var invisibleEdgeType = this._edgeInvisibleType;
    293         var weakEdgeType = this._edgeWeakType;
    294         var edgeToNodeOffset = this._edgeToNodeOffset;
    295         var edgeTypeOffset = this._edgeTypeOffset;
    296         var edgeFieldsCount = this._edgeFieldsCount;
    297         var containmentEdges = this.containmentEdges;
    298         var nodes = this.nodes;
    299         var nodeCount = this.nodeCount;
    300         var nodeFieldCount = this._nodeFieldCount;
    301         var firstEdgeIndexes = this._firstEdgeIndexes;
    302 
    303         var flags = this._flags;
    304         var list = [];
    305 
    306         for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
    307             if (iter.edge.node().isUserRoot())
    308                 list.push(iter.edge.node().nodeIndex / nodeFieldCount);
    309         }
    310 
    311         while (list.length) {
    312             var nodeOrdinal = list.pop();
    313             if (flags[nodeOrdinal] & flag)
    314                 continue;
    315             flags[nodeOrdinal] |= flag;
    316             var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
    317             var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
    318             for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
    319                 var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
    320                 var childNodeOrdinal = childNodeIndex / nodeFieldCount;
    321                 if (flags[childNodeOrdinal] & flag)
    322                     continue;
    323                 var type = containmentEdges[edgeIndex + edgeTypeOffset];
    324                 if (type === hiddenEdgeType || type === invisibleEdgeType || type === internalEdgeType || type === weakEdgeType)
    325                     continue;
    326                 list.push(childNodeOrdinal);
    327             }
    328         }
    329     },
    330 
    331     _markPageOwnedNodes: function()
    332     {
    333         var edgeShortcutType = this._edgeShortcutType;
    334         var edgeElementType = this._edgeElementType;
    335         var edgeToNodeOffset = this._edgeToNodeOffset;
    336         var edgeTypeOffset = this._edgeTypeOffset;
    337         var edgeFieldsCount = this._edgeFieldsCount;
    338         var edgeWeakType = this._edgeWeakType;
    339         var firstEdgeIndexes = this._firstEdgeIndexes;
    340         var containmentEdges = this.containmentEdges;
    341         var containmentEdgesLength = containmentEdges.length;
    342         var nodes = this.nodes;
    343         var nodeFieldCount = this._nodeFieldCount;
    344         var nodesCount = this.nodeCount;
    345 
    346         var flags = this._flags;
    347         var flag = this._nodeFlags.pageObject;
    348         var visitedMarker = this._nodeFlags.visitedMarker;
    349         var visitedMarkerMask = this._nodeFlags.visitedMarkerMask;
    350         var markerAndFlag = visitedMarker | flag;
    351 
    352         var nodesToVisit = new Uint32Array(nodesCount);
    353         var nodesToVisitLength = 0;
    354 
    355         var rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount;
    356         var node = this.rootNode();
    357         for (var edgeIndex = firstEdgeIndexes[rootNodeOrdinal], endEdgeIndex = firstEdgeIndexes[rootNodeOrdinal + 1];
    358              edgeIndex < endEdgeIndex;
    359              edgeIndex += edgeFieldsCount) {
    360             var edgeType = containmentEdges[edgeIndex + edgeTypeOffset];
    361             var nodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
    362             if (edgeType === edgeElementType) {
    363                 node.nodeIndex = nodeIndex;
    364                 if (!node.isDocumentDOMTreesRoot())
    365                     continue;
    366             } else if (edgeType !== edgeShortcutType)
    367                 continue;
    368             var nodeOrdinal = nodeIndex / nodeFieldCount;
    369             nodesToVisit[nodesToVisitLength++] = nodeOrdinal;
    370             flags[nodeOrdinal] |= visitedMarker;
    371         }
    372 
    373         while (nodesToVisitLength) {
    374             var nodeOrdinal = nodesToVisit[--nodesToVisitLength];
    375             flags[nodeOrdinal] |= flag;
    376             flags[nodeOrdinal] &= visitedMarkerMask;
    377             var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
    378             var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
    379             for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
    380                 var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
    381                 var childNodeOrdinal = childNodeIndex / nodeFieldCount;
    382                 if (flags[childNodeOrdinal] & markerAndFlag)
    383                     continue;
    384                 var type = containmentEdges[edgeIndex + edgeTypeOffset];
    385                 if (type === edgeWeakType)
    386                     continue;
    387                 nodesToVisit[nodesToVisitLength++] = childNodeOrdinal;
    388                 flags[childNodeOrdinal] |= visitedMarker;
    389             }
    390         }
    391     },
    392 
    393     _calculateStatistics: function()
    394     {
    395         var nodeFieldCount = this._nodeFieldCount;
    396         var nodes = this.nodes;
    397         var nodesLength = nodes.length;
    398         var nodeTypeOffset = this._nodeTypeOffset;
    399         var nodeSizeOffset = this._nodeSelfSizeOffset;;
    400         var nodeNativeType = this._nodeNativeType;
    401         var nodeCodeType = this._nodeCodeType;
    402         var nodeConsStringType = this._nodeConsStringType;
    403         var nodeSlicedStringType = this._nodeSlicedStringType;
    404         var sizeNative = 0;
    405         var sizeCode = 0;
    406         var sizeStrings = 0;
    407         var sizeJSArrays = 0;
    408         var node = this.rootNode();
    409         for (var nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) {
    410             node.nodeIndex = nodeIndex;
    411             var nodeType = nodes[nodeIndex + nodeTypeOffset];
    412             var nodeSize = nodes[nodeIndex + nodeSizeOffset];
    413             if (nodeType === nodeNativeType)
    414                 sizeNative += nodeSize;
    415             else if (nodeType === nodeCodeType)
    416                 sizeCode += nodeSize;
    417             else if (nodeType === nodeConsStringType || nodeType === nodeSlicedStringType || node.type() === "string")
    418                 sizeStrings += nodeSize;
    419             else if (node.name() === "Array")
    420                 sizeJSArrays += this._calculateArraySize(node);
    421         }
    422         this._statistics = new WebInspector.HeapSnapshotCommon.Statistics();
    423         this._statistics.total = this.totalSize;
    424         this._statistics.v8heap = this.totalSize - sizeNative;
    425         this._statistics.native = sizeNative;
    426         this._statistics.code = sizeCode;
    427         this._statistics.jsArrays = sizeJSArrays;
    428         this._statistics.strings = sizeStrings;
    429     },
    430 
    431     /**
    432      * @param {!WebInspector.HeapSnapshotNode} node
    433      * @return {number}
    434      */
    435     _calculateArraySize: function(node)
    436     {
    437         var size = node.selfSize();
    438         var beginEdgeIndex = node.edgeIndexesStart();
    439         var endEdgeIndex = node.edgeIndexesEnd();
    440         var containmentEdges = this.containmentEdges;
    441         var strings = this.strings;
    442         var edgeToNodeOffset = this._edgeToNodeOffset;
    443         var edgeTypeOffset = this._edgeTypeOffset;
    444         var edgeNameOffset = this._edgeNameOffset;
    445         var edgeFieldsCount = this._edgeFieldsCount;
    446         var edgeInternalType = this._edgeInternalType;
    447         for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
    448             var edgeType = containmentEdges[edgeIndex + edgeTypeOffset];
    449             if (edgeType !== edgeInternalType)
    450                 continue;
    451             var edgeName = strings[containmentEdges[edgeIndex + edgeNameOffset]];
    452             if (edgeName !== "elements")
    453                 continue;
    454             var elementsNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
    455             node.nodeIndex = elementsNodeIndex;
    456             if (node.retainersCount() === 1)
    457                 size += node.selfSize();
    458             break;
    459         }
    460         return size;
    461     },
    462 
    463     /**
    464      * @return {!WebInspector.HeapSnapshotCommon.Statistics}
    465      */
    466     getStatistics: function()
    467     {
    468         return this._statistics;
    469     },
    470 
    471     __proto__: WebInspector.HeapSnapshot.prototype
    472 };
    473 
    474 /**
    475  * @constructor
    476  * @extends {WebInspector.HeapSnapshotNode}
    477  * @param {!WebInspector.JSHeapSnapshot} snapshot
    478  * @param {number=} nodeIndex
    479  */
    480 WebInspector.JSHeapSnapshotNode = function(snapshot, nodeIndex)
    481 {
    482     WebInspector.HeapSnapshotNode.call(this, snapshot, nodeIndex)
    483 }
    484 
    485 WebInspector.JSHeapSnapshotNode.prototype = {
    486     /**
    487      * @return {boolean}
    488      */
    489     canBeQueried: function()
    490     {
    491         var flags = this._snapshot._flagsOfNode(this);
    492         return !!(flags & this._snapshot._nodeFlags.canBeQueried);
    493     },
    494 
    495     /**
    496      * @return {string}
    497      */
    498     rawName: WebInspector.HeapSnapshotNode.prototype.name,
    499 
    500     /**
    501      * @return {string}
    502      */
    503     name: function()
    504     {
    505         var snapshot = this._snapshot;
    506         if (this._type() === snapshot._nodeConsStringType) {
    507             var string = snapshot._lazyStringCache[this.nodeIndex];
    508             if (typeof string === "undefined") {
    509                 string = this._consStringName();
    510                 snapshot._lazyStringCache[this.nodeIndex] = string;
    511             }
    512             return string;
    513         }
    514         return this.rawName();
    515     },
    516 
    517     /**
    518      * @return {string}
    519      */
    520     _consStringName: function()
    521     {
    522         var snapshot = this._snapshot;
    523         var consStringType = snapshot._nodeConsStringType;
    524         var edgeInternalType = snapshot._edgeInternalType;
    525         var edgeFieldsCount = snapshot._edgeFieldsCount;
    526         var edgeToNodeOffset = snapshot._edgeToNodeOffset;
    527         var edgeTypeOffset = snapshot._edgeTypeOffset;
    528         var edgeNameOffset = snapshot._edgeNameOffset;
    529         var strings = snapshot.strings;
    530         var edges = snapshot.containmentEdges;
    531         var firstEdgeIndexes = snapshot._firstEdgeIndexes;
    532         var nodeFieldCount = snapshot._nodeFieldCount;
    533         var nodeTypeOffset = snapshot._nodeTypeOffset;
    534         var nodeNameOffset = snapshot._nodeNameOffset;
    535         var nodes = snapshot.nodes;
    536         var nodesStack = [];
    537         nodesStack.push(this.nodeIndex);
    538         var name = "";
    539 
    540         while (nodesStack.length && name.length < 1024) {
    541             var nodeIndex = nodesStack.pop();
    542             if (nodes[nodeIndex + nodeTypeOffset] !== consStringType) {
    543                 name += strings[nodes[nodeIndex + nodeNameOffset]];
    544                 continue;
    545             }
    546             var nodeOrdinal = nodeIndex / nodeFieldCount;
    547             var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
    548             var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
    549             var firstNodeIndex = 0;
    550             var secondNodeIndex = 0;
    551             for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex && (!firstNodeIndex || !secondNodeIndex); edgeIndex += edgeFieldsCount) {
    552                 var edgeType = edges[edgeIndex + edgeTypeOffset];
    553                 if (edgeType === edgeInternalType) {
    554                     var edgeName = strings[edges[edgeIndex + edgeNameOffset]];
    555                     if (edgeName === "first")
    556                         firstNodeIndex = edges[edgeIndex + edgeToNodeOffset];
    557                     else if (edgeName === "second")
    558                         secondNodeIndex = edges[edgeIndex + edgeToNodeOffset];
    559                 }
    560             }
    561             nodesStack.push(secondNodeIndex);
    562             nodesStack.push(firstNodeIndex);
    563         }
    564         return name;
    565     },
    566 
    567     /**
    568      * @return {string}
    569      */
    570     className: function()
    571     {
    572         var type = this.type();
    573         switch (type) {
    574         case "hidden":
    575             return "(system)";
    576         case "object":
    577         case "native":
    578             return this.name();
    579         case "code":
    580             return "(compiled code)";
    581         default:
    582             return "(" + type + ")";
    583         }
    584     },
    585 
    586     /**
    587      * @return {number}
    588      */
    589     classIndex: function()
    590     {
    591         var snapshot = this._snapshot;
    592         var nodes = snapshot.nodes;
    593         var type = nodes[this.nodeIndex + snapshot._nodeTypeOffset];;
    594         if (type === snapshot._nodeObjectType || type === snapshot._nodeNativeType)
    595             return nodes[this.nodeIndex + snapshot._nodeNameOffset];
    596         return -1 - type;
    597     },
    598 
    599     /**
    600      * @return {number}
    601      */
    602     id: function()
    603     {
    604         var snapshot = this._snapshot;
    605         return snapshot.nodes[this.nodeIndex + snapshot._nodeIdOffset];
    606     },
    607 
    608     /**
    609      * @return {boolean}
    610      */
    611     isHidden: function()
    612     {
    613         return this._type() === this._snapshot._nodeHiddenType;
    614     },
    615 
    616     /**
    617      * @return {boolean}
    618      */
    619     isArray: function()
    620     {
    621         return this._type() === this._snapshot._nodeArrayType;
    622     },
    623 
    624     /**
    625      * @return {boolean}
    626      */
    627     isSynthetic: function()
    628     {
    629         return this._type() === this._snapshot._nodeSyntheticType;
    630     },
    631 
    632     /**
    633      * @return {!boolean}
    634      */
    635     isUserRoot: function()
    636     {
    637         return !this.isSynthetic();
    638     },
    639 
    640     /**
    641      * @return {!boolean}
    642      */
    643     isDocumentDOMTreesRoot: function()
    644     {
    645         return this.isSynthetic() && this.name() === "(Document DOM trees)";
    646     },
    647 
    648     /**
    649      * @return {!WebInspector.HeapSnapshotCommon.Node}
    650      */
    651     serialize: function()
    652     {
    653         var result = WebInspector.HeapSnapshotNode.prototype.serialize.call(this);
    654         var flags = this._snapshot._flagsOfNode(this);
    655         if (flags & this._snapshot._nodeFlags.canBeQueried)
    656             result.canBeQueried = true;
    657         if (flags & this._snapshot._nodeFlags.detachedDOMTreeNode)
    658             result.detachedDOMTreeNode = true;
    659         return result;
    660     },
    661 
    662     __proto__: WebInspector.HeapSnapshotNode.prototype
    663 };
    664 
    665 /**
    666  * @constructor
    667  * @extends {WebInspector.HeapSnapshotEdge}
    668  * @param {!WebInspector.JSHeapSnapshot} snapshot
    669  * @param {number=} edgeIndex
    670  */
    671 WebInspector.JSHeapSnapshotEdge = function(snapshot, edgeIndex)
    672 {
    673     WebInspector.HeapSnapshotEdge.call(this, snapshot, edgeIndex);
    674 }
    675 
    676 WebInspector.JSHeapSnapshotEdge.prototype = {
    677     /**
    678      * @return {!WebInspector.JSHeapSnapshotEdge}
    679      */
    680     clone: function()
    681     {
    682         var snapshot = /** @type {!WebInspector.JSHeapSnapshot} */ (this._snapshot);
    683         return new WebInspector.JSHeapSnapshotEdge(snapshot, this.edgeIndex);
    684     },
    685 
    686     /**
    687      * @return {boolean}
    688      */
    689     hasStringName: function()
    690     {
    691         if (!this.isShortcut())
    692             return this._hasStringName();
    693         return isNaN(parseInt(this._name(), 10));
    694     },
    695 
    696     /**
    697      * @return {boolean}
    698      */
    699     isElement: function()
    700     {
    701         return this._type() === this._snapshot._edgeElementType;
    702     },
    703 
    704     /**
    705      * @return {boolean}
    706      */
    707     isHidden: function()
    708     {
    709         return this._type() === this._snapshot._edgeHiddenType;
    710     },
    711 
    712     /**
    713      * @return {boolean}
    714      */
    715     isWeak: function()
    716     {
    717         return this._type() === this._snapshot._edgeWeakType;
    718     },
    719 
    720     /**
    721      * @return {boolean}
    722      */
    723     isInternal: function()
    724     {
    725         return this._type() === this._snapshot._edgeInternalType;
    726     },
    727 
    728     /**
    729      * @return {boolean}
    730      */
    731     isInvisible: function()
    732     {
    733         return this._type() === this._snapshot._edgeInvisibleType;
    734     },
    735 
    736     /**
    737      * @return {boolean}
    738      */
    739     isShortcut: function()
    740     {
    741         return this._type() === this._snapshot._edgeShortcutType;
    742     },
    743 
    744     /**
    745      * @return {string|number}
    746      */
    747     name: function()
    748     {
    749         if (!this.isShortcut())
    750             return this._name();
    751         var numName = parseInt(this._name(), 10);
    752         return isNaN(numName) ? this._name() : numName;
    753     },
    754 
    755     /**
    756      * @return {string}
    757      */
    758     toString: function()
    759     {
    760         var name = this.name();
    761         switch (this.type()) {
    762         case "context": return "->" + name;
    763         case "element": return "[" + name + "]";
    764         case "weak": return "[[" + name + "]]";
    765         case "property":
    766             return name.indexOf(" ") === -1 ? "." + name : "[\"" + name + "\"]";
    767         case "shortcut":
    768             if (typeof name === "string")
    769                 return name.indexOf(" ") === -1 ? "." + name : "[\"" + name + "\"]";
    770             else
    771                 return "[" + name + "]";
    772         case "internal":
    773         case "hidden":
    774         case "invisible":
    775             return "{" + name + "}";
    776         };
    777         return "?" + name + "?";
    778     },
    779 
    780     /**
    781      * @return {boolean}
    782      */
    783     _hasStringName: function()
    784     {
    785         var type = this._type();
    786         var snapshot = this._snapshot;
    787         return type !== snapshot._edgeElementType && type !== snapshot._edgeHiddenType;
    788     },
    789 
    790     /**
    791      * @return {string|number}
    792      */
    793     _name: function()
    794     {
    795         return this._hasStringName() ? this._snapshot.strings[this._nameOrIndex()] : this._nameOrIndex();
    796     },
    797 
    798     /**
    799      * @return {number}
    800      */
    801     _nameOrIndex: function()
    802     {
    803         return this._edges[this.edgeIndex + this._snapshot._edgeNameOffset];
    804     },
    805 
    806     /**
    807      * @return {number}
    808      */
    809     _type: function()
    810     {
    811         return this._edges[this.edgeIndex + this._snapshot._edgeTypeOffset];
    812     },
    813 
    814     __proto__: WebInspector.HeapSnapshotEdge.prototype
    815 };
    816 
    817 
    818 /**
    819  * @constructor
    820  * @extends {WebInspector.HeapSnapshotRetainerEdge}
    821  * @param {!WebInspector.JSHeapSnapshot} snapshot
    822  * @param {number} retainerIndex
    823  */
    824 WebInspector.JSHeapSnapshotRetainerEdge = function(snapshot, retainerIndex)
    825 {
    826     WebInspector.HeapSnapshotRetainerEdge.call(this, snapshot, retainerIndex);
    827 }
    828 
    829 WebInspector.JSHeapSnapshotRetainerEdge.prototype = {
    830     /**
    831      * @return {!WebInspector.JSHeapSnapshotRetainerEdge}
    832      */
    833     clone: function()
    834     {
    835         var snapshot = /** @type {!WebInspector.JSHeapSnapshot} */ (this._snapshot);
    836         return new WebInspector.JSHeapSnapshotRetainerEdge(snapshot, this.retainerIndex());
    837     },
    838 
    839     /**
    840      * @return {boolean}
    841      */
    842     isHidden: function()
    843     {
    844         return this._edge().isHidden();
    845     },
    846 
    847     /**
    848      * @return {boolean}
    849      */
    850     isInternal: function()
    851     {
    852         return this._edge().isInternal();
    853     },
    854 
    855     /**
    856      * @return {boolean}
    857      */
    858     isInvisible: function()
    859     {
    860         return this._edge().isInvisible();
    861     },
    862 
    863     /**
    864      * @return {boolean}
    865      */
    866     isShortcut: function()
    867     {
    868         return this._edge().isShortcut();
    869     },
    870 
    871     /**
    872      * @return {boolean}
    873      */
    874     isWeak: function()
    875     {
    876         return this._edge().isWeak();
    877     },
    878 
    879     __proto__: WebInspector.HeapSnapshotRetainerEdge.prototype
    880 }
    881 
    882