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