JSHeapSnapshot.js 17 KB

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