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