Web Inspector: upstream build dominators tree procedure from v8.
authorloislo@chromium.org <loislo@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 21 May 2012 08:53:37 +0000 (08:53 +0000)
committerloislo@chromium.org <loislo@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 21 May 2012 08:53:37 +0000 (08:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=86640

The idea is to reduce transfer size and move all the post-processing steps to the front-end.
The JS implementation is ~1.5 times slower.

Reviewed by Yury Semikhatsky.

Covered by existing tests and performance tests.

PerformanceTests:

* inspector/heap-snapshot.html:

Source/WebCore:

* inspector/front-end/HeapSnapshot.js:
(WebInspector.HeapSnapshot.prototype._init):
(WebInspector.HeapSnapshot.prototype._buildAggregates):
(WebInspector.HeapSnapshot.prototype._buildPostOrderIndex):
(WebInspector.HeapSnapshot.prototype._buildDominatorTree):
(WebInspector.HeapSnapshot.prototype._markPageOwnedNodes):
(WebInspector.HeapSnapshot.prototype._markQueriableHeapObjects):
(WebInspector.HeapSnapshot.prototype._calculateFlags):

LayoutTests:

* inspector/profiler/heap-snapshot-expected.txt:
* inspector/profiler/heap-snapshot-test.js:
(initialize_HeapSnapshotTest.InspectorTest.createHeapSnapshotMockRaw):
* inspector/profiler/heap-snapshot.html:

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@117749 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/inspector/profiler/heap-snapshot-expected.txt
LayoutTests/inspector/profiler/heap-snapshot-test.js
LayoutTests/inspector/profiler/heap-snapshot.html
PerformanceTests/ChangeLog
PerformanceTests/inspector/heap-snapshot.html
Source/WebCore/ChangeLog
Source/WebCore/inspector/front-end/HeapSnapshot.js

index 6bd7689..62974e1 100644 (file)
@@ -1,3 +1,20 @@
+2012-05-18  Ilya Tikhonovsky  <loislo@chromium.org>
+
+        Web Inspector: upstream build dominators tree procedure from v8.
+        https://bugs.webkit.org/show_bug.cgi?id=86640
+
+        The idea is to reduce transfer size and move all the post-processing steps to the front-end.
+        The JS implementation is ~1.5 times slower.
+
+        Reviewed by Yury Semikhatsky.
+
+        Covered by existing tests and performance tests.
+
+        * inspector/profiler/heap-snapshot-expected.txt:
+        * inspector/profiler/heap-snapshot-test.js:
+        (initialize_HeapSnapshotTest.InspectorTest.createHeapSnapshotMockRaw):
+        * inspector/profiler/heap-snapshot.html:
+
 2012-05-21  Mike Lawther  <mikelawther@chromium.org>
 
         Crash in -webkit-calc
index d3de568..8923007 100644 (file)
@@ -13,6 +13,10 @@ Running: heapSnapshotNodeAndEdgeTest
 
 Running: heapSnapshotSimpleTest
 
+Running: heapSnapshotPostOrderIndexTest
+
+Running: heapSnapshotDominatorsTreeTest
+
 Running: heapSnapshotRetainersTest
 
 Running: heapSnapshotAggregatesTest
index 291c40d..b538e68 100644 (file)
@@ -69,17 +69,27 @@ InspectorTest.createHeapSnapshotMockRaw = function()
             node_count: 6,
             edge_count: 7},
         nodes: [
-            0, 0, 1, 0, 20,  0,  0,
-            1, 1, 2, 2,  2,  0,  6,
-            1, 2, 3, 3,  8,  0, 12,
-            1, 3, 4, 4, 10,  0, 18,
-            1, 4, 5, 5,  5, 14, 21,
-            1, 5, 6, 6,  6, 21, 21],
+            0, 0, 1, 0, 20,  0,  0, // root (0)
+            1, 1, 2, 2,  2,  0,  6, // A (7)
+            1, 2, 3, 3,  8,  0, 12, // B (14)
+            1, 3, 4, 4, 10,  0, 18, // C (21)
+            1, 4, 5, 5,  5, 14, 21, // D (28)
+            1, 5, 6, 6,  6, 21, 21],// E (35)
         edges: [
-            1,  6,  7, 1,  7, 14,
-            0,  1, 14, 1,  8, 21,
-            1,  9, 21, 1, 10, 28,
-            1, 11, 35],
+            // root node edges
+            1,  6,  7, // property 'a' to node 'A'
+            1,  7, 14, // property 'b' to node 'B'
+
+            // A node edges
+            0,  1, 14, // element 1 to node 'B'
+            1,  8, 21, // property 'ac' to node 'C'
+
+            // B node edges
+            1,  9, 21, // property 'bc' to node 'C'
+            1, 10, 28, // property 'bd' to node 'D'
+
+            // C node edges
+            1, 11, 35], // property 'ce' to node 'E'
         strings: ["", "A", "B", "C", "D", "E", "a", "b", "ac", "bc", "bd", "ce"]
     };
 };
index 44b293d..4e3583f 100644 (file)
@@ -95,6 +95,27 @@ function test()
             next();
         },
 
+        function heapSnapshotPostOrderIndexTest(next)
+        {
+            var snapshot = new WebInspector.HeapSnapshot(InspectorTest.createHeapSnapshotMock());
+            var postOrderIndex = snapshot._buildPostOrderIndex().postOrderIndex2NodeIndex;
+            var expected = [28,35,21,14,7,0];
+            for (var i = 0; i < expected.length; ++i)
+                InspectorTest.assertEquals(expected[i], postOrderIndex[i], "Post ordered indexes");
+            next();
+        },
+
+        function heapSnapshotDominatorsTreeTest(next)
+        {
+            var snapshot = new WebInspector.HeapSnapshot(InspectorTest.createHeapSnapshotMock());
+            var result = snapshot._buildPostOrderIndex();
+            var dominatorsTree = snapshot._buildDominatorTree(result.postOrderIndex2NodeIndex, result.nodeOrdinal2PostOrderIndex);
+            var expected = [0, 0, 0, 0, 14, 21];
+            for (var i = 0; i < expected.length; ++i)
+                InspectorTest.assertEquals(expected[i], dominatorsTree[i], "Dominators Tree");
+            next();
+        },
+
         function heapSnapshotRetainersTest(next)
         {
             var snapshot = new WebInspector.HeapSnapshot(InspectorTest.createHeapSnapshotMock());
index bab6284..34f0919 100644 (file)
@@ -1,3 +1,17 @@
+2012-05-18  Ilya Tikhonovsky  <loislo@chromium.org>
+
+        Web Inspector: upstream build dominators tree procedure from v8.
+        https://bugs.webkit.org/show_bug.cgi?id=86640
+
+        The idea is to reduce transfer size and move all the post-processing steps to the front-end.
+        The JS implementation is ~1.5 times slower.
+
+        Reviewed by Yury Semikhatsky.
+
+        Covered by existing tests and performance tests.
+
+        * inspector/heap-snapshot.html:
+
 2012-05-18  Kentaro Hara  <haraken@chromium.org>
 
         [perf-test] Remove Bindings/dom-attributes.html
index 9ce31eb..b7012ec 100644 (file)
@@ -28,7 +28,10 @@ function test()
             InspectorTest.measureFunction(WebInspector.HeapSnapshot.prototype, "_calculateObjectToWindowDistance");
             InspectorTest.measureFunction(WebInspector.HeapSnapshot.prototype, "_markDetachedDOMTreeNodes");
             InspectorTest.measureFunction(WebInspector.HeapSnapshot.prototype, "_markQueriableHeapObjects");
+            InspectorTest.measureFunction(WebInspector.HeapSnapshot.prototype, "_markPageOwnedNodes");
             InspectorTest.measureFunction(WebInspector.HeapSnapshot.prototype, "_splitNodesAndContainmentEdges");
+            InspectorTest.measureFunction(WebInspector.HeapSnapshot.prototype, "_buildPostOrderIndex");
+            InspectorTest.measureFunction(WebInspector.HeapSnapshot.prototype, "_buildDominatorTree");
 
             timer.finish(backendTimerCookie);
             transferTimerCookie = timer.start("transfer-snapshot");
index d652b87..a160098 100644 (file)
@@ -1,3 +1,24 @@
+2012-05-18  Ilya Tikhonovsky  <loislo@chromium.org>
+
+        Web Inspector: upstream build dominators tree procedure from v8.
+        https://bugs.webkit.org/show_bug.cgi?id=86640
+
+        The idea is to reduce transfer size and move all the post-processing steps to the front-end.
+        The JS implementation is ~1.5 times slower.
+
+        Reviewed by Yury Semikhatsky.
+
+        Covered by existing tests and performance tests.
+
+        * inspector/front-end/HeapSnapshot.js:
+        (WebInspector.HeapSnapshot.prototype._init):
+        (WebInspector.HeapSnapshot.prototype._buildAggregates):
+        (WebInspector.HeapSnapshot.prototype._buildPostOrderIndex):
+        (WebInspector.HeapSnapshot.prototype._buildDominatorTree):
+        (WebInspector.HeapSnapshot.prototype._markPageOwnedNodes):
+        (WebInspector.HeapSnapshot.prototype._markQueriableHeapObjects):
+        (WebInspector.HeapSnapshot.prototype._calculateFlags):
+
 2012-05-21  Mike Lawther  <mikelawther@chromium.org>
 
         Crash in -webkit-calc
index 301c782..af35e53 100644 (file)
@@ -708,6 +708,10 @@ WebInspector.HeapSnapshot.prototype = {
         this._nodeFlags = { // bit flags
             canBeQueried: 1,
             detachedDOMTreeNode: 2,
+            pageObject: 4, // The idea is to track separately the objects owned by the page and the objects owned by debugger.
+
+            visitedMarkerMask: 0x0ffff, // bits: 0,1111,1111,1111,1111
+            visitedMarker:     0x10000  // bits: 1,0000,0000,0000,0000
         };
 
         this.nodeCount = this._nodes.length / this._nodeFieldCount;
@@ -719,6 +723,8 @@ WebInspector.HeapSnapshot.prototype = {
             this._buildDominatedNodes()
         this._calculateFlags();
         this._calculateObjectToWindowDistance();
+        var result = this._buildPostOrderIndex();
+        this._dominatorsTree = this._buildDominatorTree(result.postOrderIndex2NodeIndex, result.nodeOrdinal2PostOrderIndex);
     },
 
     _buildRetainers: function()
@@ -787,6 +793,7 @@ WebInspector.HeapSnapshot.prototype = {
         delete this._firstDominatedNodeIndex;
         delete this._flags;
         delete this._distancesToWindow;
+        delete this._dominatorsTree;
     },
 
     get _allNodes()
@@ -959,14 +966,14 @@ WebInspector.HeapSnapshot.prototype = {
         var nodes = this._nodes;
         var nodesLength = nodes.length;
         var nodeNativeType = this._nodeNativeType;
-        var nodeFieldsCount = this._nodeFieldCount;
+        var nodeFieldCount = this._nodeFieldCount;
         var selfSizeOffset = this._nodeSelfSizeOffset;
         var nodeTypeOffset = this._nodeTypeOffset;
         var node = new WebInspector.HeapSnapshotNode(this, this._rootNodeIndex);
         var distancesToWindow = this._distancesToWindow;
 
-        for (var nodeIndex = this._rootNodeIndex; nodeIndex < nodesLength; nodeIndex += nodeFieldsCount) {
-            var nodeOrdinal = nodeIndex / nodeFieldsCount;
+        for (var nodeIndex = this._rootNodeIndex; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) {
+            var nodeOrdinal = nodeIndex / nodeFieldCount;
             node.nodeIndex = nodeIndex;
             var selfSize = nodes[nodeIndex + selfSizeOffset];
             if (filter && !filter(node))
@@ -1063,6 +1070,193 @@ WebInspector.HeapSnapshot.prototype = {
                 });
     },
 
+    _buildPostOrderIndex: function()
+    {
+        var nodeFieldCount = this._nodeFieldCount;
+        var nodes = this._nodes;
+        var nodeCount = this.nodeCount;
+        var rootNodeIndex = this._rootNodeIndex;
+
+        var edgeFieldsCount = this._edgeFieldsCount;
+        var edgeToNodeOffset = this._edgeToNodeOffset;
+        var edgeTypeOffset = this._edgeTypeOffset;
+        var edgeShortcutType = this._edgeShortcutType;
+        var firstEdgeIndexOffset = this._firstEdgeIndexOffset;
+        var containmentEdges = this._containmentEdges;
+        var containmentEdgesLength = this._containmentEdges.length;
+
+        var flags = this._flags;
+        var flag = this._nodeFlags.pageObject;
+
+        var nodesToVisit = new Uint32Array(nodeCount);
+        var postOrderIndex2NodeIndex = new Uint32Array(nodeCount);
+        var nodeOrdinal2PostOrderIndex = new Uint32Array(nodeCount);
+        var painted = new Uint8Array(nodeCount);
+        var nodesToVisitLength = 0;
+        var postOrderIndex = 0;
+        var grey = 1;
+        var black = 2;
+
+        nodesToVisit[nodesToVisitLength++] = this._rootNodeIndex;
+        painted[this._rootNodeIndex / nodeFieldCount] = grey;
+
+        while (nodesToVisitLength) {
+            var nodeIndex = nodesToVisit[nodesToVisitLength - 1];
+            var nodeOrdinal = nodeIndex / nodeFieldCount;
+            if (painted[nodeOrdinal] === grey) {
+                painted[nodeOrdinal] = black;
+                var nodeFlag = flags[nodeOrdinal] & flag;
+                var beginEdgeIndex = nodes[nodeIndex + firstEdgeIndexOffset];
+                var endEdgeIndex =  nodeOrdinal < nodeCount - 1
+                                    ? nodes[nodeIndex + firstEdgeIndexOffset + nodeFieldCount]
+                                    : containmentEdgesLength;
+                for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
+                    if (nodeIndex !== rootNodeIndex && containmentEdges[edgeIndex + edgeTypeOffset] === edgeShortcutType)
+                        continue;
+                    var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
+                    var childNodeOrdinal = childNodeIndex / nodeFieldCount;
+                    var childNodeFlag = flags[childNodeOrdinal] & flag;
+                    // We are skipping the edges from non-page-owned nodes to page-owned nodes.
+                    // Otherwise the dominators for the objects that also were retained by debugger would be affected.
+                    if (nodeIndex !== rootNodeIndex && childNodeFlag && !nodeFlag)
+                        continue;
+                    if (!painted[childNodeOrdinal]) {
+                        painted[childNodeOrdinal] = grey;
+                        nodesToVisit[nodesToVisitLength++] = childNodeIndex;
+                    }
+                }
+            } else {
+                nodeOrdinal2PostOrderIndex[nodeOrdinal] = postOrderIndex;
+                postOrderIndex2NodeIndex[postOrderIndex++] = nodeIndex;
+                --nodesToVisitLength;
+            }
+        }
+        return {postOrderIndex2NodeIndex: postOrderIndex2NodeIndex, nodeOrdinal2PostOrderIndex: nodeOrdinal2PostOrderIndex};
+    },
+
+    // The algorithm is based on the article:
+    // K. Cooper, T. Harvey and K. Kennedy "A Simple, Fast Dominance Algorithm"
+    // Softw. Pract. Exper. 4 (2001), pp. 1-10.
+    /**
+     * @param {Array.<number>} postOrderIndex2NodeIndex
+     * @param {Array.<number>} nodeOrdinal2PostOrderIndex
+     */
+    _buildDominatorTree: function(postOrderIndex2NodeIndex, nodeOrdinal2PostOrderIndex)
+    {
+        var nodeFieldCount = this._nodeFieldCount;
+        var nodes = this._nodes;
+        var firstRetainerIndex = this._firstRetainerIndex;
+        var retainingNodes = this._retainingNodes;
+        var retainingEdges = this._retainingEdges;
+        var edgeFieldsCount = this._edgeFieldsCount;
+        var edgeToNodeOffset = this._edgeToNodeOffset;
+        var edgeTypeOffset = this._edgeTypeOffset;
+        var edgeShortcutType = this._edgeShortcutType;
+        var firstEdgeIndexOffset = this._firstEdgeIndexOffset;
+        var containmentEdges = this._containmentEdges;
+        var containmentEdgesLength = this._containmentEdges.length;
+        var rootNodeIndex = this._rootNodeIndex;
+
+        var flags = this._flags;
+        var flag = this._nodeFlags.pageObject;
+
+        var nodesCount = postOrderIndex2NodeIndex.length;
+        var rootPostOrderedIndex = nodesCount - 1;
+        var noEntry = nodesCount;
+        var dominators = new Uint32Array(nodesCount);
+        for (var i = 0; i < rootPostOrderedIndex; ++i)
+            dominators[i] = noEntry;
+        dominators[rootPostOrderedIndex] = rootPostOrderedIndex;
+
+        // The affected array is used to mark entries which dominators
+        // have to be racalculated because of changes in their retainers.
+        var affected = new Uint8Array(nodesCount);
+
+        { // Mark the root direct children as affected.
+            var nodeIndex = this._rootNodeIndex;
+            var beginEdgeToNodeFieldIndex = nodes[nodeIndex + firstEdgeIndexOffset] + edgeToNodeOffset;
+            var endEdgeToNodeFieldIndex = nodes[nodeIndex + nodeFieldCount + firstEdgeIndexOffset];
+            for (var toNodeFieldIndex = beginEdgeToNodeFieldIndex;
+                 toNodeFieldIndex < endEdgeToNodeFieldIndex;
+                 toNodeFieldIndex += edgeFieldsCount) {
+                var childNodeOrdinal = containmentEdges[toNodeFieldIndex] / nodeFieldCount;
+                affected[nodeOrdinal2PostOrderIndex[childNodeOrdinal]] = 1;
+            }
+        }
+
+        var changed = true;
+        while (changed) {
+            changed = false;
+            for (var postOrderIndex = rootPostOrderedIndex - 1; postOrderIndex >= 0; --postOrderIndex) {
+                if (affected[postOrderIndex] === 0)
+                    continue;
+                affected[postOrderIndex] = 0;
+                // If dominator of the entry has already been set to root,
+                // then it can't propagate any further.
+                if (dominators[postOrderIndex] === rootPostOrderedIndex)
+                    continue;
+                var nodeOrdinal = postOrderIndex2NodeIndex[postOrderIndex] / nodeFieldCount;
+                var nodeFlag = !!(flags[nodeOrdinal] & flag);
+                var newDominatorIndex = noEntry;
+                var beginRetainerIndex = firstRetainerIndex[nodeOrdinal];
+                var endRetainerIndex = firstRetainerIndex[nodeOrdinal + 1];
+                for (var retainerIndex = beginRetainerIndex; retainerIndex < endRetainerIndex; ++retainerIndex) {
+                    var retainerEdgeIndex = retainingEdges[retainerIndex];
+                    var retainerEdgeType = containmentEdges[retainerEdgeIndex + edgeTypeOffset];
+                    var retainerNodeIndex = retainingNodes[retainerIndex];
+                    if (retainerNodeIndex !== rootNodeIndex && retainerEdgeType === edgeShortcutType)
+                        continue;
+                    var retainerNodeOrdinal = retainerNodeIndex / nodeFieldCount;
+                    var retainerNodeFlag = !!(flags[retainerNodeOrdinal] & flag);
+                    // We are skipping the edges from non-page-owned nodes to page-owned nodes.
+                    // Otherwise the dominators for the objects that also were retained by debugger would be affected.
+                    if (retainerNodeIndex !== rootNodeIndex && nodeFlag && !retainerNodeFlag)
+                        continue;
+                    var retanerPostOrderIndex = nodeOrdinal2PostOrderIndex[retainerNodeOrdinal];
+                    if (dominators[retanerPostOrderIndex] !== noEntry) {
+                        if (newDominatorIndex === noEntry)
+                            newDominatorIndex = retanerPostOrderIndex;
+                        else {
+                            while (retanerPostOrderIndex !== newDominatorIndex) {
+                                while (retanerPostOrderIndex < newDominatorIndex)
+                                    retanerPostOrderIndex = dominators[retanerPostOrderIndex];
+                                while (newDominatorIndex < retanerPostOrderIndex)
+                                    newDominatorIndex = dominators[newDominatorIndex];
+                            }
+                        }
+                        // If idom has already reached the root, it doesn't make sense
+                        // to check other retainers.
+                        if (newDominatorIndex === rootPostOrderedIndex)
+                            break;
+                    }
+                }
+                if (newDominatorIndex !== noEntry && dominators[postOrderIndex] !== newDominatorIndex) {
+                    dominators[postOrderIndex] = newDominatorIndex;
+                    changed = true;
+                    nodeIndex = postOrderIndex2NodeIndex[postOrderIndex];
+                    nodeOrdinal = nodeIndex / nodeFieldCount;
+                    beginEdgeToNodeFieldIndex = nodes[nodeIndex + firstEdgeIndexOffset] + edgeToNodeOffset;
+                    endEdgeToNodeFieldIndex = nodeOrdinal < nodesCount - 1
+                                                  ? nodes[nodeIndex + firstEdgeIndexOffset + nodeFieldCount]
+                                                  : containmentEdgesLength;
+                    for (var toNodeFieldIndex = beginEdgeToNodeFieldIndex;
+                         toNodeFieldIndex < endEdgeToNodeFieldIndex;
+                         toNodeFieldIndex += edgeFieldsCount) {
+                        var childNodeOrdinal = containmentEdges[toNodeFieldIndex] / nodeFieldCount;
+                        affected[nodeOrdinal2PostOrderIndex[childNodeOrdinal]] = 1;
+                    }
+                }
+            }
+        }
+
+        var dominatorsTree = new Uint32Array(nodesCount);
+        for (var postOrderIndex = 0, l = dominators.length; postOrderIndex < l; ++postOrderIndex) {
+            var nodeOrdinal = postOrderIndex2NodeIndex[postOrderIndex] / nodeFieldCount;
+            dominatorsTree[nodeOrdinal] = postOrderIndex2NodeIndex[dominators[postOrderIndex]];
+        }
+        return dominatorsTree;
+    },
+
     _buildDominatedNodes: function()
     {
         // Builds up two arrays:
@@ -1160,6 +1354,62 @@ WebInspector.HeapSnapshot.prototype = {
         }
     },
 
+    _markPageOwnedNodes: function()
+    {
+        var edgeShortcutType = this._edgeShortcutType;
+        var edgeToNodeOffset = this._edgeToNodeOffset;
+        var edgeTypeOffset = this._edgeTypeOffset;
+        var edgeFieldsCount = this._edgeFieldsCount;
+        var firstEdgeIndexOffset = this._firstEdgeIndexOffset;
+        var containmentEdges = this._containmentEdges;
+        var containmentEdgesLength = containmentEdges.length;
+        var nodes = this._nodes;
+        var nodeFieldCount = this._nodeFieldCount;
+        var nodesCount = this.nodeCount;
+
+        var flags = this._flags;
+        var flag = this._nodeFlags.pageObject;
+        var visitedMarker = this._nodeFlags.visitedMarker;
+        var visitedMarkerMask = this._nodeFlags.visitedMarkerMask;
+        var markerAndFlag = visitedMarker | flag;
+
+        var nodesToVisit = new Uint32Array(nodesCount);
+        var nodesToVisitLength = 0;
+
+        for (var edgeIndex = nodes[this._rootNodeIndex + firstEdgeIndexOffset], endEdgeIndex = nodes[this._rootNodeIndex + nodeFieldCount + firstEdgeIndexOffset];
+             edgeIndex < endEdgeIndex;
+             edgeIndex += edgeFieldsCount) {
+            if (containmentEdges[edgeIndex + edgeTypeOffset] === edgeShortcutType) {
+                var nodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
+                nodesToVisit[nodesToVisitLength++] = nodeIndex;
+                flags[nodeIndex / nodeFieldCount] |= visitedMarker;
+            }
+        }
+
+        while (nodesToVisitLength) {
+            var nodeIndex = nodesToVisit[--nodesToVisitLength];
+            var nodeOrdinal = nodeIndex / nodeFieldCount;
+            flags[nodeOrdinal] |= flag;
+            flags[nodeOrdinal] &= visitedMarkerMask;
+            var beginEdgeToNodeFieldIndex = nodes[nodeIndex + firstEdgeIndexOffset] + edgeToNodeOffset;
+            var endEdgeToNodeFieldIndex = 0;
+            if (nodeOrdinal < nodesCount - 1)
+                endEdgeToNodeFieldIndex = nodes[nodeIndex + firstEdgeIndexOffset + nodeFieldCount] + edgeToNodeOffset;
+            else
+                endEdgeToNodeFieldIndex = containmentEdgesLength;
+            for (var edgeToNodeIndex = beginEdgeToNodeFieldIndex;
+                 edgeToNodeIndex < endEdgeToNodeFieldIndex;
+                 edgeToNodeIndex += edgeFieldsCount) {
+                var childNodeIndex = containmentEdges[edgeToNodeIndex];
+                var childNodeOrdinal = childNodeIndex / nodeFieldCount;
+                if (flags[childNodeOrdinal] & markerAndFlag)
+                    continue;
+                nodesToVisit[nodesToVisitLength++] = childNodeIndex;
+                flags[childNodeOrdinal] |= visitedMarker;
+            }
+        }
+    },
+
     _markQueriableHeapObjects: function()
     {
         // Allow runtime properties query for objects accessible from Window objects
@@ -1180,6 +1430,7 @@ WebInspector.HeapSnapshot.prototype = {
 
         var flags = this._flags;
         var list = [];
+
         for (var iter = this.rootNode.edges; iter.hasNext(); iter.next()) {
             if (iter.edge.node.isWindow)
                 list.push(iter.edge.node.nodeIndex);
@@ -1212,6 +1463,7 @@ WebInspector.HeapSnapshot.prototype = {
         this._flags = new Uint32Array(this.nodeCount);
         this._markDetachedDOMTreeNodes();
         this._markQueriableHeapObjects();
+        this._markPageOwnedNodes();
     },
 
     calculateSnapshotDiff: function(baseSnapshotId, baseSnapshotAggregates)