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