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