HeapSnapshotView.js 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168
  1. /*
  2. * Copyright (C) 2011 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.View}
  33. * @param {!WebInspector.ProfilesPanel} parent
  34. * @param {!WebInspector.HeapProfileHeader} profile
  35. */
  36. WebInspector.HeapSnapshotView = function(parent, profile)
  37. {
  38. WebInspector.View.call(this);
  39. this.element.addStyleClass("heap-snapshot-view");
  40. this.parent = parent;
  41. this.parent.addEventListener("profile added", this._onProfileHeaderAdded, this);
  42. this.viewsContainer = document.createElement("div");
  43. this.viewsContainer.addStyleClass("views-container");
  44. this.element.appendChild(this.viewsContainer);
  45. this.containmentView = new WebInspector.View();
  46. this.containmentView.element.addStyleClass("view");
  47. this.containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid();
  48. this.containmentDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
  49. this.containmentDataGrid.show(this.containmentView.element);
  50. this.containmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
  51. this.constructorsView = new WebInspector.View();
  52. this.constructorsView.element.addStyleClass("view");
  53. this.constructorsView.element.appendChild(this._createToolbarWithClassNameFilter());
  54. this.constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid();
  55. this.constructorsDataGrid.element.addStyleClass("class-view-grid");
  56. this.constructorsDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
  57. this.constructorsDataGrid.show(this.constructorsView.element);
  58. this.constructorsDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
  59. this.diffView = new WebInspector.View();
  60. this.diffView.element.addStyleClass("view");
  61. this.diffView.element.appendChild(this._createToolbarWithClassNameFilter());
  62. this.diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid();
  63. this.diffDataGrid.element.addStyleClass("class-view-grid");
  64. this.diffDataGrid.show(this.diffView.element);
  65. this.diffDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
  66. this.dominatorView = new WebInspector.View();
  67. this.dominatorView.element.addStyleClass("view");
  68. this.dominatorDataGrid = new WebInspector.HeapSnapshotDominatorsDataGrid();
  69. this.dominatorDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
  70. this.dominatorDataGrid.show(this.dominatorView.element);
  71. this.dominatorDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
  72. this.retainmentViewHeader = document.createElement("div");
  73. this.retainmentViewHeader.addStyleClass("retainers-view-header");
  74. WebInspector.installDragHandle(this.retainmentViewHeader, this._startRetainersHeaderDragging.bind(this), this._retainersHeaderDragging.bind(this), this._endRetainersHeaderDragging.bind(this), "row-resize");
  75. var retainingPathsTitleDiv = document.createElement("div");
  76. retainingPathsTitleDiv.className = "title";
  77. var retainingPathsTitle = document.createElement("span");
  78. retainingPathsTitle.textContent = WebInspector.UIString("Object's retaining tree");
  79. retainingPathsTitleDiv.appendChild(retainingPathsTitle);
  80. this.retainmentViewHeader.appendChild(retainingPathsTitleDiv);
  81. this.element.appendChild(this.retainmentViewHeader);
  82. this.retainmentView = new WebInspector.View();
  83. this.retainmentView.element.addStyleClass("view");
  84. this.retainmentView.element.addStyleClass("retaining-paths-view");
  85. this.retainmentDataGrid = new WebInspector.HeapSnapshotRetainmentDataGrid();
  86. this.retainmentDataGrid.show(this.retainmentView.element);
  87. this.retainmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._inspectedObjectChanged, this);
  88. this.retainmentView.show(this.element);
  89. this.retainmentDataGrid.reset();
  90. this.dataGrid = /** @type {WebInspector.HeapSnapshotSortableDataGrid} */ (this.constructorsDataGrid);
  91. this.currentView = this.constructorsView;
  92. this.viewSelectElement = document.createElement("select");
  93. this.viewSelectElement.className = "status-bar-item";
  94. this.viewSelectElement.addEventListener("change", this._onSelectedViewChanged.bind(this), false);
  95. this.views = [{title: "Summary", view: this.constructorsView, grid: this.constructorsDataGrid},
  96. {title: "Comparison", view: this.diffView, grid: this.diffDataGrid},
  97. {title: "Containment", view: this.containmentView, grid: this.containmentDataGrid},
  98. {title: "Dominators", view: this.dominatorView, grid: this.dominatorDataGrid}];
  99. this.views.current = 0;
  100. for (var i = 0; i < this.views.length; ++i) {
  101. var view = this.views[i];
  102. var option = document.createElement("option");
  103. option.label = WebInspector.UIString(view.title);
  104. this.viewSelectElement.appendChild(option);
  105. }
  106. this._profileUid = profile.uid;
  107. this._profileTypeId = profile.profileType().id;
  108. this.baseSelectElement = document.createElement("select");
  109. this.baseSelectElement.className = "status-bar-item";
  110. this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false);
  111. this._updateBaseOptions();
  112. this.filterSelectElement = document.createElement("select");
  113. this.filterSelectElement.className = "status-bar-item";
  114. this.filterSelectElement.addEventListener("change", this._changeFilter.bind(this), false);
  115. this._updateFilterOptions();
  116. this.helpButton = new WebInspector.StatusBarButton("", "heap-snapshot-help-status-bar-item status-bar-item");
  117. this.helpButton.addEventListener("click", this._helpClicked, this);
  118. this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true);
  119. this.profile.load(profileCallback.bind(this));
  120. function profileCallback(heapSnapshotProxy)
  121. {
  122. var list = this._profiles();
  123. var profileIndex;
  124. for (var i = 0; i < list.length; ++i) {
  125. if (list[i].uid === this._profileUid) {
  126. profileIndex = i;
  127. break;
  128. }
  129. }
  130. if (profileIndex > 0)
  131. this.baseSelectElement.selectedIndex = profileIndex - 1;
  132. else
  133. this.baseSelectElement.selectedIndex = profileIndex;
  134. this.dataGrid.setDataSource(heapSnapshotProxy);
  135. }
  136. }
  137. WebInspector.HeapSnapshotView.prototype = {
  138. dispose: function()
  139. {
  140. this.profile.dispose();
  141. if (this.baseProfile)
  142. this.baseProfile.dispose();
  143. this.containmentDataGrid.dispose();
  144. this.constructorsDataGrid.dispose();
  145. this.diffDataGrid.dispose();
  146. this.dominatorDataGrid.dispose();
  147. this.retainmentDataGrid.dispose();
  148. },
  149. statusBarItems: function()
  150. {
  151. /**
  152. * @param {boolean=} hidden
  153. */
  154. function appendArrowImage(element, hidden)
  155. {
  156. var span = document.createElement("span");
  157. span.className = "status-bar-select-container" + (hidden ? " hidden" : "");
  158. span.appendChild(element);
  159. return span;
  160. }
  161. return [appendArrowImage(this.viewSelectElement), appendArrowImage(this.baseSelectElement, true), appendArrowImage(this.filterSelectElement), this.helpButton.element];
  162. },
  163. get profile()
  164. {
  165. return this.parent.getProfile(this._profileTypeId, this._profileUid);
  166. },
  167. get baseProfile()
  168. {
  169. return this.parent.getProfile(this._profileTypeId, this._baseProfileUid);
  170. },
  171. wasShown: function()
  172. {
  173. // FIXME: load base and current snapshots in parallel
  174. this.profile.load(profileCallback1.bind(this));
  175. function profileCallback1() {
  176. if (this.baseProfile)
  177. this.baseProfile.load(profileCallback2.bind(this));
  178. else
  179. profileCallback2.call(this);
  180. }
  181. function profileCallback2() {
  182. this.currentView.show(this.viewsContainer);
  183. }
  184. },
  185. willHide: function()
  186. {
  187. this._currentSearchResultIndex = -1;
  188. this._popoverHelper.hidePopover();
  189. if (this.helpPopover && this.helpPopover.isShowing())
  190. this.helpPopover.hide();
  191. },
  192. onResize: function()
  193. {
  194. var height = this.retainmentView.element.clientHeight;
  195. this._updateRetainmentViewHeight(height);
  196. },
  197. searchCanceled: function()
  198. {
  199. if (this._searchResults) {
  200. for (var i = 0; i < this._searchResults.length; ++i) {
  201. var node = this._searchResults[i].node;
  202. delete node._searchMatched;
  203. node.refresh();
  204. }
  205. }
  206. delete this._searchFinishedCallback;
  207. this._currentSearchResultIndex = -1;
  208. this._searchResults = [];
  209. },
  210. performSearch: function(query, finishedCallback)
  211. {
  212. // Call searchCanceled since it will reset everything we need before doing a new search.
  213. this.searchCanceled();
  214. query = query.trim();
  215. if (!query.length)
  216. return;
  217. if (this.currentView !== this.constructorsView && this.currentView !== this.diffView)
  218. return;
  219. this._searchFinishedCallback = finishedCallback;
  220. function matchesByName(gridNode) {
  221. return ("_name" in gridNode) && gridNode._name.hasSubstring(query, true);
  222. }
  223. function matchesById(gridNode) {
  224. return ("snapshotNodeId" in gridNode) && gridNode.snapshotNodeId === query;
  225. }
  226. var matchPredicate;
  227. if (query.charAt(0) !== "@")
  228. matchPredicate = matchesByName;
  229. else {
  230. query = parseInt(query.substring(1), 10);
  231. matchPredicate = matchesById;
  232. }
  233. function matchesQuery(gridNode)
  234. {
  235. delete gridNode._searchMatched;
  236. if (matchPredicate(gridNode)) {
  237. gridNode._searchMatched = true;
  238. gridNode.refresh();
  239. return true;
  240. }
  241. return false;
  242. }
  243. var current = this.dataGrid.rootNode().children[0];
  244. var depth = 0;
  245. var info = {};
  246. // Restrict to type nodes and instances.
  247. const maxDepth = 1;
  248. while (current) {
  249. if (matchesQuery(current))
  250. this._searchResults.push({ node: current });
  251. current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
  252. depth += info.depthChange;
  253. }
  254. finishedCallback(this, this._searchResults.length);
  255. },
  256. jumpToFirstSearchResult: function()
  257. {
  258. if (!this._searchResults || !this._searchResults.length)
  259. return;
  260. this._currentSearchResultIndex = 0;
  261. this._jumpToSearchResult(this._currentSearchResultIndex);
  262. },
  263. jumpToLastSearchResult: function()
  264. {
  265. if (!this._searchResults || !this._searchResults.length)
  266. return;
  267. this._currentSearchResultIndex = (this._searchResults.length - 1);
  268. this._jumpToSearchResult(this._currentSearchResultIndex);
  269. },
  270. jumpToNextSearchResult: function()
  271. {
  272. if (!this._searchResults || !this._searchResults.length)
  273. return;
  274. if (++this._currentSearchResultIndex >= this._searchResults.length)
  275. this._currentSearchResultIndex = 0;
  276. this._jumpToSearchResult(this._currentSearchResultIndex);
  277. },
  278. jumpToPreviousSearchResult: function()
  279. {
  280. if (!this._searchResults || !this._searchResults.length)
  281. return;
  282. if (--this._currentSearchResultIndex < 0)
  283. this._currentSearchResultIndex = (this._searchResults.length - 1);
  284. this._jumpToSearchResult(this._currentSearchResultIndex);
  285. },
  286. showingFirstSearchResult: function()
  287. {
  288. return (this._currentSearchResultIndex === 0);
  289. },
  290. showingLastSearchResult: function()
  291. {
  292. return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
  293. },
  294. _jumpToSearchResult: function(index)
  295. {
  296. var searchResult = this._searchResults[index];
  297. if (!searchResult)
  298. return;
  299. var node = searchResult.node;
  300. node.revealAndSelect();
  301. },
  302. refreshVisibleData: function()
  303. {
  304. var child = this.dataGrid.rootNode().children[0];
  305. while (child) {
  306. child.refresh();
  307. child = child.traverseNextNode(false, null, true);
  308. }
  309. },
  310. _changeBase: function()
  311. {
  312. if (this._baseProfileUid === this._profiles()[this.baseSelectElement.selectedIndex].uid)
  313. return;
  314. this._baseProfileUid = this._profiles()[this.baseSelectElement.selectedIndex].uid;
  315. var dataGrid = /** @type {WebInspector.HeapSnapshotDiffDataGrid} */ (this.dataGrid);
  316. // Change set base data source only if main data source is already set.
  317. if (dataGrid.snapshot)
  318. this.baseProfile.load(dataGrid.setBaseDataSource.bind(dataGrid));
  319. if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
  320. return;
  321. // The current search needs to be performed again. First negate out previous match
  322. // count by calling the search finished callback with a negative number of matches.
  323. // Then perform the search again with the same query and callback.
  324. this._searchFinishedCallback(this, -this._searchResults.length);
  325. this.performSearch(this.currentQuery, this._searchFinishedCallback);
  326. },
  327. _changeFilter: function()
  328. {
  329. var profileIndex = this.filterSelectElement.selectedIndex - 1;
  330. this.dataGrid.filterSelectIndexChanged(this._profiles(), profileIndex);
  331. WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
  332. action: WebInspector.UserMetrics.UserActionNames.HeapSnapshotFilterChanged,
  333. label: this.filterSelectElement[this.filterSelectElement.selectedIndex].label
  334. });
  335. if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
  336. return;
  337. // The current search needs to be performed again. First negate out previous match
  338. // count by calling the search finished callback with a negative number of matches.
  339. // Then perform the search again with the same query and callback.
  340. this._searchFinishedCallback(this, -this._searchResults.length);
  341. this.performSearch(this.currentQuery, this._searchFinishedCallback);
  342. },
  343. _createToolbarWithClassNameFilter: function()
  344. {
  345. var toolbar = document.createElement("div");
  346. toolbar.addStyleClass("class-view-toolbar");
  347. var classNameFilter = document.createElement("input");
  348. classNameFilter.addStyleClass("class-name-filter");
  349. classNameFilter.setAttribute("placeholder", WebInspector.UIString("Class filter"));
  350. classNameFilter.addEventListener("keyup", this._changeNameFilter.bind(this, classNameFilter), false);
  351. toolbar.appendChild(classNameFilter);
  352. return toolbar;
  353. },
  354. _changeNameFilter: function(classNameInputElement)
  355. {
  356. var filter = classNameInputElement.value;
  357. this.dataGrid.changeNameFilter(filter);
  358. },
  359. /**
  360. * @return {!Array.<!WebInspector.ProfileHeader>}
  361. */
  362. _profiles: function()
  363. {
  364. return this.parent.getProfileType(this._profileTypeId).getProfiles();
  365. },
  366. /**
  367. * @param {WebInspector.ContextMenu} contextMenu
  368. * @param {Event} event
  369. */
  370. populateContextMenu: function(contextMenu, event)
  371. {
  372. this.dataGrid.populateContextMenu(this.parent, contextMenu, event);
  373. },
  374. _selectionChanged: function(event)
  375. {
  376. var selectedNode = event.target.selectedNode;
  377. this._setRetainmentDataGridSource(selectedNode);
  378. this._inspectedObjectChanged(event);
  379. },
  380. _inspectedObjectChanged: function(event)
  381. {
  382. var selectedNode = event.target.selectedNode;
  383. if (!this.profile.fromFile() && selectedNode instanceof WebInspector.HeapSnapshotGenericObjectNode)
  384. ConsoleAgent.addInspectedHeapObject(selectedNode.snapshotNodeId);
  385. },
  386. _setRetainmentDataGridSource: function(nodeItem)
  387. {
  388. if (nodeItem && nodeItem.snapshotNodeIndex)
  389. this.retainmentDataGrid.setDataSource(nodeItem.isDeletedNode ? nodeItem.dataGrid.baseSnapshot : nodeItem.dataGrid.snapshot, nodeItem.snapshotNodeIndex);
  390. else
  391. this.retainmentDataGrid.reset();
  392. },
  393. _mouseDownInContentsGrid: function(event)
  394. {
  395. if (event.detail < 2)
  396. return;
  397. var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
  398. if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("shallowSize-column") && !cell.hasStyleClass("retainedSize-column")))
  399. return;
  400. event.consume(true);
  401. },
  402. changeView: function(viewTitle, callback)
  403. {
  404. var viewIndex = null;
  405. for (var i = 0; i < this.views.length; ++i)
  406. if (this.views[i].title === viewTitle) {
  407. viewIndex = i;
  408. break;
  409. }
  410. if (this.views.current === viewIndex) {
  411. setTimeout(callback, 0);
  412. return;
  413. }
  414. function dataGridContentShown(event)
  415. {
  416. var dataGrid = event.data;
  417. dataGrid.removeEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this);
  418. if (dataGrid === this.dataGrid)
  419. callback();
  420. }
  421. this.views[viewIndex].grid.addEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this);
  422. this.viewSelectElement.selectedIndex = viewIndex;
  423. this._changeView(viewIndex);
  424. },
  425. _updateDataSourceAndView: function()
  426. {
  427. var dataGrid = this.dataGrid;
  428. if (dataGrid.snapshot)
  429. return;
  430. this.profile.load(didLoadSnapshot.bind(this));
  431. function didLoadSnapshot(snapshotProxy)
  432. {
  433. if (this.dataGrid !== dataGrid)
  434. return;
  435. if (dataGrid.snapshot !== snapshotProxy)
  436. dataGrid.setDataSource(snapshotProxy);
  437. if (dataGrid === this.diffDataGrid) {
  438. if (!this._baseProfileUid)
  439. this._baseProfileUid = this._profiles()[this.baseSelectElement.selectedIndex].uid;
  440. this.baseProfile.load(didLoadBaseSnaphot.bind(this));
  441. }
  442. }
  443. function didLoadBaseSnaphot(baseSnapshotProxy)
  444. {
  445. if (this.diffDataGrid.baseSnapshot !== baseSnapshotProxy)
  446. this.diffDataGrid.setBaseDataSource(baseSnapshotProxy);
  447. }
  448. },
  449. _onSelectedViewChanged: function(event)
  450. {
  451. this._changeView(event.target.selectedIndex);
  452. },
  453. _updateSelectorsVisibility: function()
  454. {
  455. if (this.currentView === this.diffView)
  456. this.baseSelectElement.parentElement.removeStyleClass("hidden");
  457. else
  458. this.baseSelectElement.parentElement.addStyleClass("hidden");
  459. if (this.currentView === this.constructorsView)
  460. this.filterSelectElement.parentElement.removeStyleClass("hidden");
  461. else
  462. this.filterSelectElement.parentElement.addStyleClass("hidden");
  463. },
  464. _changeView: function(selectedIndex)
  465. {
  466. if (selectedIndex === this.views.current)
  467. return;
  468. this.views.current = selectedIndex;
  469. this.currentView.detach();
  470. var view = this.views[this.views.current];
  471. this.currentView = view.view;
  472. this.dataGrid = view.grid;
  473. this.currentView.show(this.viewsContainer);
  474. this.refreshVisibleData();
  475. this.dataGrid.updateWidths();
  476. this._updateSelectorsVisibility();
  477. this._updateDataSourceAndView();
  478. if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
  479. return;
  480. // The current search needs to be performed again. First negate out previous match
  481. // count by calling the search finished callback with a negative number of matches.
  482. // Then perform the search again the with same query and callback.
  483. this._searchFinishedCallback(this, -this._searchResults.length);
  484. this.performSearch(this.currentQuery, this._searchFinishedCallback);
  485. },
  486. _getHoverAnchor: function(target)
  487. {
  488. var span = target.enclosingNodeOrSelfWithNodeName("span");
  489. if (!span)
  490. return;
  491. var row = target.enclosingNodeOrSelfWithNodeName("tr");
  492. if (!row)
  493. return;
  494. span.node = row._dataGridNode;
  495. return span;
  496. },
  497. _resolveObjectForPopover: function(element, showCallback, objectGroupName)
  498. {
  499. if (this.profile.fromFile())
  500. return;
  501. element.node.queryObjectContent(showCallback, objectGroupName);
  502. },
  503. _helpClicked: function(event)
  504. {
  505. if (!this._helpPopoverContentElement) {
  506. var refTypes = ["a:", "console-formatted-name", WebInspector.UIString("property"),
  507. "0:", "console-formatted-name", WebInspector.UIString("element"),
  508. "a:", "console-formatted-number", WebInspector.UIString("context var"),
  509. "a:", "console-formatted-null", WebInspector.UIString("system prop")];
  510. var objTypes = [" a ", "console-formatted-object", "Object",
  511. "\"a\"", "console-formatted-string", "String",
  512. "/a/", "console-formatted-string", "RegExp",
  513. "a()", "console-formatted-function", "Function",
  514. "a[]", "console-formatted-object", "Array",
  515. "num", "console-formatted-number", "Number",
  516. " a ", "console-formatted-null", "System"];
  517. var contentElement = document.createElement("table");
  518. contentElement.className = "heap-snapshot-help";
  519. var headerRow = document.createElement("tr");
  520. var propsHeader = document.createElement("th");
  521. propsHeader.textContent = WebInspector.UIString("Property types:");
  522. headerRow.appendChild(propsHeader);
  523. var objsHeader = document.createElement("th");
  524. objsHeader.textContent = WebInspector.UIString("Object types:");
  525. headerRow.appendChild(objsHeader);
  526. contentElement.appendChild(headerRow);
  527. function appendHelp(help, index, cell)
  528. {
  529. var div = document.createElement("div");
  530. div.className = "source-code event-properties";
  531. var name = document.createElement("span");
  532. name.textContent = help[index];
  533. name.className = help[index + 1];
  534. div.appendChild(name);
  535. var desc = document.createElement("span");
  536. desc.textContent = " " + help[index + 2];
  537. div.appendChild(desc);
  538. cell.appendChild(div);
  539. }
  540. var len = Math.max(refTypes.length, objTypes.length);
  541. for (var i = 0; i < len; i += 3) {
  542. var row = document.createElement("tr");
  543. var refCell = document.createElement("td");
  544. if (refTypes[i])
  545. appendHelp(refTypes, i, refCell);
  546. row.appendChild(refCell);
  547. var objCell = document.createElement("td");
  548. if (objTypes[i])
  549. appendHelp(objTypes, i, objCell);
  550. row.appendChild(objCell);
  551. contentElement.appendChild(row);
  552. }
  553. this._helpPopoverContentElement = contentElement;
  554. this.helpPopover = new WebInspector.Popover();
  555. }
  556. if (this.helpPopover.isShowing())
  557. this.helpPopover.hide();
  558. else
  559. this.helpPopover.show(this._helpPopoverContentElement, this.helpButton.element);
  560. },
  561. /**
  562. * @return {boolean}
  563. */
  564. _startRetainersHeaderDragging: function(event)
  565. {
  566. if (!this.isShowing())
  567. return false;
  568. this._previousDragPosition = event.pageY;
  569. return true;
  570. },
  571. _retainersHeaderDragging: function(event)
  572. {
  573. var height = this.retainmentView.element.clientHeight;
  574. height += this._previousDragPosition - event.pageY;
  575. this._previousDragPosition = event.pageY;
  576. this._updateRetainmentViewHeight(height);
  577. event.consume(true);
  578. },
  579. _endRetainersHeaderDragging: function(event)
  580. {
  581. delete this._previousDragPosition;
  582. event.consume();
  583. },
  584. _updateRetainmentViewHeight: function(height)
  585. {
  586. height = Number.constrain(height, Preferences.minConsoleHeight, this.element.clientHeight - Preferences.minConsoleHeight);
  587. this.viewsContainer.style.bottom = (height + this.retainmentViewHeader.clientHeight) + "px";
  588. this.retainmentView.element.style.height = height + "px";
  589. this.retainmentViewHeader.style.bottom = height + "px";
  590. this.currentView.doResize();
  591. },
  592. _updateBaseOptions: function()
  593. {
  594. var list = this._profiles();
  595. // We're assuming that snapshots can only be added.
  596. if (this.baseSelectElement.length === list.length)
  597. return;
  598. for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) {
  599. var baseOption = document.createElement("option");
  600. var title = list[i].title;
  601. if (WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(title))
  602. title = WebInspector.UIString("Snapshot %d", WebInspector.ProfilesPanelDescriptor.userInitiatedProfileIndex(title));
  603. baseOption.label = title;
  604. this.baseSelectElement.appendChild(baseOption);
  605. }
  606. },
  607. _updateFilterOptions: function()
  608. {
  609. var list = this._profiles();
  610. // We're assuming that snapshots can only be added.
  611. if (this.filterSelectElement.length - 1 === list.length)
  612. return;
  613. if (!this.filterSelectElement.length) {
  614. var filterOption = document.createElement("option");
  615. filterOption.label = WebInspector.UIString("All objects");
  616. this.filterSelectElement.appendChild(filterOption);
  617. }
  618. if (this.profile.fromFile())
  619. return;
  620. for (var i = this.filterSelectElement.length - 1, n = list.length; i < n; ++i) {
  621. var profile = list[i];
  622. var filterOption = document.createElement("option");
  623. var title = list[i].title;
  624. if (WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(title)) {
  625. var profileIndex = WebInspector.ProfilesPanelDescriptor.userInitiatedProfileIndex(title);
  626. if (!i)
  627. title = WebInspector.UIString("Objects allocated before Snapshot %d", profileIndex);
  628. else
  629. title = WebInspector.UIString("Objects allocated between Snapshots %d and %d", profileIndex - 1, profileIndex);
  630. }
  631. filterOption.label = title;
  632. this.filterSelectElement.appendChild(filterOption);
  633. }
  634. },
  635. /**
  636. * @param {WebInspector.Event} event
  637. */
  638. _onProfileHeaderAdded: function(event)
  639. {
  640. if (!event.data || event.data.type !== this._profileTypeId)
  641. return;
  642. this._updateBaseOptions();
  643. this._updateFilterOptions();
  644. },
  645. __proto__: WebInspector.View.prototype
  646. }
  647. /**
  648. * @constructor
  649. * @extends {WebInspector.ProfileType}
  650. * @implements {HeapProfilerAgent.Dispatcher}
  651. */
  652. WebInspector.HeapSnapshotProfileType = function()
  653. {
  654. WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("Take Heap Snapshot"));
  655. InspectorBackend.registerHeapProfilerDispatcher(this);
  656. }
  657. WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";
  658. WebInspector.HeapSnapshotProfileType.prototype = {
  659. get buttonTooltip()
  660. {
  661. return WebInspector.UIString("Take heap snapshot.");
  662. },
  663. /**
  664. * @override
  665. * @return {boolean}
  666. */
  667. isInstantProfile: function()
  668. {
  669. return true;
  670. },
  671. /**
  672. * @override
  673. * @return {boolean}
  674. */
  675. buttonClicked: function()
  676. {
  677. this._takeHeapSnapshot();
  678. return false;
  679. },
  680. get treeItemTitle()
  681. {
  682. return WebInspector.UIString("HEAP SNAPSHOTS");
  683. },
  684. get description()
  685. {
  686. return WebInspector.UIString("Heap snapshot profiles show memory distribution among your page's JavaScript objects and related DOM nodes.");
  687. },
  688. /**
  689. * @override
  690. * @param {string=} title
  691. * @return {!WebInspector.ProfileHeader}
  692. */
  693. createTemporaryProfile: function(title)
  694. {
  695. title = title || WebInspector.UIString("Snapshotting\u2026");
  696. return new WebInspector.HeapProfileHeader(this, title);
  697. },
  698. /**
  699. * @override
  700. * @param {HeapProfilerAgent.ProfileHeader} profile
  701. * @return {!WebInspector.ProfileHeader}
  702. */
  703. createProfile: function(profile)
  704. {
  705. return new WebInspector.HeapProfileHeader(this, profile.title, profile.uid, profile.maxJSObjectId || 0);
  706. },
  707. _takeHeapSnapshot: function()
  708. {
  709. var temporaryProfile = this.findTemporaryProfile();
  710. if (!temporaryProfile)
  711. this.addProfile(this.createTemporaryProfile());
  712. HeapProfilerAgent.takeHeapSnapshot(true, function() {});
  713. WebInspector.userMetrics.ProfilesHeapProfileTaken.record();
  714. },
  715. /**
  716. * @param {HeapProfilerAgent.ProfileHeader} profileHeader
  717. */
  718. addProfileHeader: function(profileHeader)
  719. {
  720. this.addProfile(this.createProfile(profileHeader));
  721. },
  722. /**
  723. * @override
  724. * @param {number} uid
  725. * @param {string} chunk
  726. */
  727. addHeapSnapshotChunk: function(uid, chunk)
  728. {
  729. var profile = this._profilesIdMap[this._makeKey(uid)];
  730. if (profile)
  731. profile.transferChunk(chunk);
  732. },
  733. /**
  734. * @override
  735. * @param {number} uid
  736. */
  737. finishHeapSnapshot: function(uid)
  738. {
  739. var profile = this._profilesIdMap[this._makeKey(uid)];
  740. if (profile)
  741. profile.finishHeapSnapshot();
  742. },
  743. /**
  744. * @override
  745. * @param {number} done
  746. * @param {number} total
  747. */
  748. reportHeapSnapshotProgress: function(done, total)
  749. {
  750. var profile = this.findTemporaryProfile();
  751. if (profile)
  752. this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProgressUpdated, {"profile": profile, "done": done, "total": total});
  753. },
  754. /**
  755. * @override
  756. */
  757. resetProfiles: function()
  758. {
  759. this._reset();
  760. },
  761. /**
  762. * @override
  763. * @param {!WebInspector.ProfileHeader} profile
  764. */
  765. removeProfile: function(profile)
  766. {
  767. WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
  768. if (!profile.isTemporary)
  769. HeapProfilerAgent.removeProfile(profile.uid);
  770. },
  771. /**
  772. * @override
  773. * @param {function(this:WebInspector.ProfileType, ?string, Array.<ProfilerAgent.ProfileHeader>)} populateCallback
  774. */
  775. _requestProfilesFromBackend: function(populateCallback)
  776. {
  777. HeapProfilerAgent.getProfileHeaders(populateCallback);
  778. },
  779. __proto__: WebInspector.ProfileType.prototype
  780. }
  781. /**
  782. * @constructor
  783. * @extends {WebInspector.ProfileHeader}
  784. * @param {!WebInspector.ProfileType} type
  785. * @param {string} title
  786. * @param {number=} uid
  787. * @param {number=} maxJSObjectId
  788. */
  789. WebInspector.HeapProfileHeader = function(type, title, uid, maxJSObjectId)
  790. {
  791. WebInspector.ProfileHeader.call(this, type, title, uid);
  792. this.maxJSObjectId = maxJSObjectId;
  793. /**
  794. * @type {WebInspector.OutputStream}
  795. */
  796. this._receiver = null;
  797. /**
  798. * @type {WebInspector.HeapSnapshotProxy}
  799. */
  800. this._snapshotProxy = null;
  801. this._totalNumberOfChunks = 0;
  802. }
  803. WebInspector.HeapProfileHeader.prototype = {
  804. /**
  805. * @override
  806. */
  807. createSidebarTreeElement: function()
  808. {
  809. return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item");
  810. },
  811. /**
  812. * @override
  813. * @param {!WebInspector.ProfilesPanel} profilesPanel
  814. */
  815. createView: function(profilesPanel)
  816. {
  817. return new WebInspector.HeapSnapshotView(profilesPanel, this);
  818. },
  819. /**
  820. * @override
  821. * @param {function(WebInspector.HeapSnapshotProxy):void} callback
  822. */
  823. load: function(callback)
  824. {
  825. if (this._snapshotProxy) {
  826. callback(this._snapshotProxy);
  827. return;
  828. }
  829. this._numberOfChunks = 0;
  830. this._savedChunks = 0;
  831. this._savingToFile = false;
  832. if (!this._receiver) {
  833. this._setupWorker();
  834. this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
  835. this.sidebarElement.wait = true;
  836. this.startSnapshotTransfer();
  837. }
  838. var loaderProxy = /** @type {WebInspector.HeapSnapshotLoaderProxy} */ (this._receiver);
  839. loaderProxy.addConsumer(callback);
  840. },
  841. startSnapshotTransfer: function()
  842. {
  843. HeapProfilerAgent.getHeapSnapshot(this.uid);
  844. },
  845. snapshotConstructorName: function()
  846. {
  847. return "JSHeapSnapshot";
  848. },
  849. snapshotProxyConstructor: function()
  850. {
  851. return WebInspector.HeapSnapshotProxy;
  852. },
  853. _setupWorker: function()
  854. {
  855. function setProfileWait(event)
  856. {
  857. this.sidebarElement.wait = event.data;
  858. }
  859. var worker = new WebInspector.HeapSnapshotWorker();
  860. worker.addEventListener("wait", setProfileWait, this);
  861. var loaderProxy = worker.createLoader(this.snapshotConstructorName(), this.snapshotProxyConstructor());
  862. loaderProxy.addConsumer(this._snapshotReceived.bind(this));
  863. this._receiver = loaderProxy;
  864. },
  865. /**
  866. * @override
  867. */
  868. dispose: function()
  869. {
  870. if (this._receiver)
  871. this._receiver.close();
  872. else if (this._snapshotProxy)
  873. this._snapshotProxy.dispose();
  874. },
  875. /**
  876. * @param {number} value
  877. * @param {number} maxValue
  878. */
  879. _updateTransferProgress: function(value, maxValue)
  880. {
  881. var percentValue = ((maxValue ? (value / maxValue) : 0) * 100).toFixed(0);
  882. if (this._savingToFile)
  883. this.sidebarElement.subtitle = WebInspector.UIString("Saving\u2026 %d\%", percentValue);
  884. else
  885. this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026 %d\%", percentValue);
  886. },
  887. _updateSnapshotStatus: function()
  888. {
  889. this.sidebarElement.subtitle = Number.bytesToString(this._snapshotProxy.totalSize);
  890. this.sidebarElement.wait = false;
  891. },
  892. /**
  893. * @param {string} chunk
  894. */
  895. transferChunk: function(chunk)
  896. {
  897. ++this._numberOfChunks;
  898. this._receiver.write(chunk, callback.bind(this));
  899. function callback()
  900. {
  901. this._updateTransferProgress(++this._savedChunks, this._totalNumberOfChunks);
  902. if (this._totalNumberOfChunks === this._savedChunks) {
  903. if (this._savingToFile)
  904. this._updateSnapshotStatus();
  905. else
  906. this.sidebarElement.subtitle = WebInspector.UIString("Parsing\u2026");
  907. this._receiver.close();
  908. }
  909. }
  910. },
  911. _snapshotReceived: function(snapshotProxy)
  912. {
  913. this._receiver = null;
  914. if (snapshotProxy)
  915. this._snapshotProxy = snapshotProxy;
  916. this._updateSnapshotStatus();
  917. var worker = /** @type {WebInspector.HeapSnapshotWorker} */ (this._snapshotProxy.worker);
  918. this.isTemporary = false;
  919. worker.startCheckingForLongRunningCalls();
  920. },
  921. finishHeapSnapshot: function()
  922. {
  923. this._totalNumberOfChunks = this._numberOfChunks;
  924. },
  925. /**
  926. * @override
  927. * @return {boolean}
  928. */
  929. canSaveToFile: function()
  930. {
  931. return !this.fromFile() && !!this._snapshotProxy && !this._receiver;
  932. },
  933. /**
  934. * @override
  935. */
  936. saveToFile: function()
  937. {
  938. this._numberOfChunks = 0;
  939. var fileOutputStream = new WebInspector.FileOutputStream();
  940. function onOpen()
  941. {
  942. this._receiver = fileOutputStream;
  943. this._savedChunks = 0;
  944. this._updateTransferProgress(0, this._totalNumberOfChunks);
  945. HeapProfilerAgent.getHeapSnapshot(this.uid);
  946. }
  947. this._savingToFile = true;
  948. this._fileName = this._fileName || "Heap-" + new Date().toISO8601Compact() + ".heapsnapshot";
  949. fileOutputStream.open(this._fileName, onOpen.bind(this));
  950. },
  951. /**
  952. * @override
  953. * @param {File} file
  954. */
  955. loadFromFile: function(file)
  956. {
  957. this.title = file.name;
  958. this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
  959. this.sidebarElement.wait = true;
  960. this._setupWorker();
  961. this._numberOfChunks = 0;
  962. this._savingToFile = false;
  963. var delegate = new WebInspector.HeapSnapshotLoadFromFileDelegate(this);
  964. var fileReader = this._createFileReader(file, delegate);
  965. fileReader.start(this._receiver);
  966. },
  967. _createFileReader: function(file, delegate)
  968. {
  969. return new WebInspector.ChunkedFileReader(file, 10000000, delegate);
  970. },
  971. __proto__: WebInspector.ProfileHeader.prototype
  972. }
  973. /**
  974. * @constructor
  975. * @implements {WebInspector.OutputStreamDelegate}
  976. */
  977. WebInspector.HeapSnapshotLoadFromFileDelegate = function(snapshotHeader)
  978. {
  979. this._snapshotHeader = snapshotHeader;
  980. }
  981. WebInspector.HeapSnapshotLoadFromFileDelegate.prototype = {
  982. onTransferStarted: function()
  983. {
  984. },
  985. /**
  986. * @param {WebInspector.ChunkedReader} reader
  987. */
  988. onChunkTransferred: function(reader)
  989. {
  990. this._snapshotHeader._updateTransferProgress(reader.loadedSize(), reader.fileSize());
  991. },
  992. onTransferFinished: function()
  993. {
  994. this._snapshotHeader.finishHeapSnapshot();
  995. },
  996. /**
  997. * @param {WebInspector.ChunkedReader} reader
  998. */
  999. onError: function (reader, e)
  1000. {
  1001. switch(e.target.error.code) {
  1002. case e.target.error.NOT_FOUND_ERR:
  1003. this._snapshotHeader.sidebarElement.subtitle = WebInspector.UIString("'%s' not found.", reader.fileName());
  1004. break;
  1005. case e.target.error.NOT_READABLE_ERR:
  1006. this._snapshotHeader.sidebarElement.subtitle = WebInspector.UIString("'%s' is not readable", reader.fileName());
  1007. break;
  1008. case e.target.error.ABORT_ERR:
  1009. break;
  1010. default:
  1011. this._snapshotHeader.sidebarElement.subtitle = WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code);
  1012. }
  1013. }
  1014. }