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