HeapSnapshotView.js 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844
  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. if (profile._profileType.id === WebInspector.TrackingHeapSnapshotProfileType.TypeId) {
  43. this._trackingOverviewGrid = new WebInspector.HeapTrackingOverviewGrid(profile);
  44. this._trackingOverviewGrid.addEventListener(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, this._onIdsRangeChanged.bind(this));
  45. this._trackingOverviewGrid.show(this.element);
  46. }
  47. this.viewsContainer = document.createElement("div");
  48. this.viewsContainer.addStyleClass("views-container");
  49. this.element.appendChild(this.viewsContainer);
  50. this.containmentView = new WebInspector.View();
  51. this.containmentView.element.addStyleClass("view");
  52. this.containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid();
  53. this.containmentDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
  54. this.containmentDataGrid.show(this.containmentView.element);
  55. this.containmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
  56. this.constructorsView = new WebInspector.View();
  57. this.constructorsView.element.addStyleClass("view");
  58. this.constructorsView.element.appendChild(this._createToolbarWithClassNameFilter());
  59. this.constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid();
  60. this.constructorsDataGrid.element.addStyleClass("class-view-grid");
  61. this.constructorsDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
  62. this.constructorsDataGrid.show(this.constructorsView.element);
  63. this.constructorsDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
  64. this.dataGrid = /** @type {WebInspector.HeapSnapshotSortableDataGrid} */ (this.constructorsDataGrid);
  65. this.currentView = this.constructorsView;
  66. this.currentView.show(this.viewsContainer);
  67. this.diffView = new WebInspector.View();
  68. this.diffView.element.addStyleClass("view");
  69. this.diffView.element.appendChild(this._createToolbarWithClassNameFilter());
  70. this.diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid();
  71. this.diffDataGrid.element.addStyleClass("class-view-grid");
  72. this.diffDataGrid.show(this.diffView.element);
  73. this.diffDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
  74. this.dominatorView = new WebInspector.View();
  75. this.dominatorView.element.addStyleClass("view");
  76. this.dominatorDataGrid = new WebInspector.HeapSnapshotDominatorsDataGrid();
  77. this.dominatorDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
  78. this.dominatorDataGrid.show(this.dominatorView.element);
  79. this.dominatorDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
  80. this.retainmentViewHeader = document.createElement("div");
  81. this.retainmentViewHeader.addStyleClass("retainers-view-header");
  82. WebInspector.installDragHandle(this.retainmentViewHeader, this._startRetainersHeaderDragging.bind(this), this._retainersHeaderDragging.bind(this), this._endRetainersHeaderDragging.bind(this), "row-resize");
  83. var retainingPathsTitleDiv = document.createElement("div");
  84. retainingPathsTitleDiv.className = "title";
  85. var retainingPathsTitle = document.createElement("span");
  86. retainingPathsTitle.textContent = WebInspector.UIString("Object's retaining tree");
  87. retainingPathsTitleDiv.appendChild(retainingPathsTitle);
  88. this.retainmentViewHeader.appendChild(retainingPathsTitleDiv);
  89. this.element.appendChild(this.retainmentViewHeader);
  90. this.retainmentView = new WebInspector.View();
  91. this.retainmentView.element.addStyleClass("view");
  92. this.retainmentView.element.addStyleClass("retaining-paths-view");
  93. this.retainmentDataGrid = new WebInspector.HeapSnapshotRetainmentDataGrid();
  94. this.retainmentDataGrid.show(this.retainmentView.element);
  95. this.retainmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._inspectedObjectChanged, this);
  96. this.retainmentView.show(this.element);
  97. this.retainmentDataGrid.reset();
  98. this.viewSelect = new WebInspector.StatusBarComboBox(this._onSelectedViewChanged.bind(this));
  99. this.views = [{title: "Summary", view: this.constructorsView, grid: this.constructorsDataGrid},
  100. {title: "Comparison", view: this.diffView, grid: this.diffDataGrid},
  101. {title: "Containment", view: this.containmentView, grid: this.containmentDataGrid}];
  102. if (WebInspector.settings.showAdvancedHeapSnapshotProperties.get())
  103. this.views.push({title: "Dominators", view: this.dominatorView, grid: this.dominatorDataGrid});
  104. this.views.current = 0;
  105. for (var i = 0; i < this.views.length; ++i)
  106. this.viewSelect.createOption(WebInspector.UIString(this.views[i].title));
  107. this._profileUid = profile.uid;
  108. this._profileTypeId = profile.profileType().id;
  109. this.baseSelect = new WebInspector.StatusBarComboBox(this._changeBase.bind(this));
  110. this.baseSelect.element.addStyleClass("hidden");
  111. this._updateBaseOptions();
  112. this.filterSelect = new WebInspector.StatusBarComboBox(this._changeFilter.bind(this));
  113. this._updateFilterOptions();
  114. this.selectedSizeText = new WebInspector.StatusBarText("");
  115. this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true);
  116. this.profile.load(profileCallback.bind(this));
  117. function profileCallback(heapSnapshotProxy)
  118. {
  119. var list = this._profiles();
  120. var profileIndex;
  121. for (var i = 0; i < list.length; ++i) {
  122. if (list[i].uid === this._profileUid) {
  123. profileIndex = i;
  124. break;
  125. }
  126. }
  127. if (profileIndex > 0)
  128. this.baseSelect.setSelectedIndex(profileIndex - 1);
  129. else
  130. this.baseSelect.setSelectedIndex(profileIndex);
  131. this.dataGrid.setDataSource(heapSnapshotProxy);
  132. }
  133. }
  134. WebInspector.HeapSnapshotView.prototype = {
  135. _onIdsRangeChanged: function(event)
  136. {
  137. var minId = event.data.minId;
  138. var maxId = event.data.maxId;
  139. this.selectedSizeText.setText(WebInspector.UIString("Selected size: %s", Number.bytesToString(event.data.size)));
  140. if (this.constructorsDataGrid.snapshot)
  141. this.constructorsDataGrid.setSelectionRange(minId, maxId);
  142. },
  143. dispose: function()
  144. {
  145. this.parent.removeEventListener("profile added", this._onProfileHeaderAdded, this);
  146. this.profile.dispose();
  147. if (this.baseProfile)
  148. this.baseProfile.dispose();
  149. this.containmentDataGrid.dispose();
  150. this.constructorsDataGrid.dispose();
  151. this.diffDataGrid.dispose();
  152. this.dominatorDataGrid.dispose();
  153. this.retainmentDataGrid.dispose();
  154. },
  155. get statusBarItems()
  156. {
  157. return [this.viewSelect.element, this.baseSelect.element, this.filterSelect.element, this.selectedSizeText.element];
  158. },
  159. get profile()
  160. {
  161. return this.parent.getProfile(this._profileTypeId, this._profileUid);
  162. },
  163. get baseProfile()
  164. {
  165. return this.parent.getProfile(this._profileTypeId, this._baseProfileUid);
  166. },
  167. wasShown: function()
  168. {
  169. // FIXME: load base and current snapshots in parallel
  170. this.profile.load(profileCallback.bind(this));
  171. function profileCallback() {
  172. this.profile._wasShown();
  173. if (this.baseProfile)
  174. this.baseProfile.load(function() { });
  175. }
  176. },
  177. willHide: function()
  178. {
  179. this._currentSearchResultIndex = -1;
  180. this._popoverHelper.hidePopover();
  181. if (this.helpPopover && this.helpPopover.isShowing())
  182. this.helpPopover.hide();
  183. },
  184. onResize: function()
  185. {
  186. var height = this.retainmentView.element.clientHeight;
  187. this._updateRetainmentViewHeight(height);
  188. },
  189. searchCanceled: function()
  190. {
  191. if (this._searchResults) {
  192. for (var i = 0; i < this._searchResults.length; ++i) {
  193. var node = this._searchResults[i].node;
  194. delete node._searchMatched;
  195. node.refresh();
  196. }
  197. }
  198. delete this._searchFinishedCallback;
  199. this._currentSearchResultIndex = -1;
  200. this._searchResults = [];
  201. },
  202. /**
  203. * @param {string} query
  204. * @param {function(!WebInspector.View, number)} finishedCallback
  205. */
  206. performSearch: function(query, finishedCallback)
  207. {
  208. // Call searchCanceled since it will reset everything we need before doing a new search.
  209. this.searchCanceled();
  210. query = query.trim();
  211. if (!query)
  212. return;
  213. if (this.currentView !== this.constructorsView && this.currentView !== this.diffView)
  214. return;
  215. this._searchFinishedCallback = finishedCallback;
  216. var nameRegExp = createPlainTextSearchRegex(query, "i");
  217. var snapshotNodeId = null;
  218. function matchesByName(gridNode) {
  219. return ("_name" in gridNode) && nameRegExp.test(gridNode._name);
  220. }
  221. function matchesById(gridNode) {
  222. return ("snapshotNodeId" in gridNode) && gridNode.snapshotNodeId === snapshotNodeId;
  223. }
  224. var matchPredicate;
  225. if (query.charAt(0) !== "@")
  226. matchPredicate = matchesByName;
  227. else {
  228. snapshotNodeId = parseInt(query.substring(1), 10);
  229. matchPredicate = matchesById;
  230. }
  231. function matchesQuery(gridNode)
  232. {
  233. delete gridNode._searchMatched;
  234. if (matchPredicate(gridNode)) {
  235. gridNode._searchMatched = true;
  236. gridNode.refresh();
  237. return true;
  238. }
  239. return false;
  240. }
  241. var current = this.dataGrid.rootNode().children[0];
  242. var depth = 0;
  243. var info = {};
  244. // Restrict to type nodes and instances.
  245. const maxDepth = 1;
  246. while (current) {
  247. if (matchesQuery(current))
  248. this._searchResults.push({ node: current });
  249. current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
  250. depth += info.depthChange;
  251. }
  252. finishedCallback(this, this._searchResults.length);
  253. },
  254. jumpToFirstSearchResult: function()
  255. {
  256. if (!this._searchResults || !this._searchResults.length)
  257. return;
  258. this._currentSearchResultIndex = 0;
  259. this._jumpToSearchResult(this._currentSearchResultIndex);
  260. },
  261. jumpToLastSearchResult: function()
  262. {
  263. if (!this._searchResults || !this._searchResults.length)
  264. return;
  265. this._currentSearchResultIndex = (this._searchResults.length - 1);
  266. this._jumpToSearchResult(this._currentSearchResultIndex);
  267. },
  268. jumpToNextSearchResult: function()
  269. {
  270. if (!this._searchResults || !this._searchResults.length)
  271. return;
  272. if (++this._currentSearchResultIndex >= this._searchResults.length)
  273. this._currentSearchResultIndex = 0;
  274. this._jumpToSearchResult(this._currentSearchResultIndex);
  275. },
  276. jumpToPreviousSearchResult: function()
  277. {
  278. if (!this._searchResults || !this._searchResults.length)
  279. return;
  280. if (--this._currentSearchResultIndex < 0)
  281. this._currentSearchResultIndex = (this._searchResults.length - 1);
  282. this._jumpToSearchResult(this._currentSearchResultIndex);
  283. },
  284. showingFirstSearchResult: function()
  285. {
  286. return (this._currentSearchResultIndex === 0);
  287. },
  288. showingLastSearchResult: function()
  289. {
  290. return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
  291. },
  292. _jumpToSearchResult: function(index)
  293. {
  294. var searchResult = this._searchResults[index];
  295. if (!searchResult)
  296. return;
  297. var node = searchResult.node;
  298. node.revealAndSelect();
  299. },
  300. refreshVisibleData: function()
  301. {
  302. var child = this.dataGrid.rootNode().children[0];
  303. while (child) {
  304. child.refresh();
  305. child = child.traverseNextNode(false, null, true);
  306. }
  307. },
  308. _changeBase: function()
  309. {
  310. if (this._baseProfileUid === this._profiles()[this.baseSelect.selectedIndex()].uid)
  311. return;
  312. this._baseProfileUid = this._profiles()[this.baseSelect.selectedIndex()].uid;
  313. var dataGrid = /** @type {WebInspector.HeapSnapshotDiffDataGrid} */ (this.dataGrid);
  314. // Change set base data source only if main data source is already set.
  315. if (dataGrid.snapshot)
  316. this.baseProfile.load(dataGrid.setBaseDataSource.bind(dataGrid));
  317. if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
  318. return;
  319. // The current search needs to be performed again. First negate out previous match
  320. // count by calling the search finished callback with a negative number of matches.
  321. // Then perform the search again with the same query and callback.
  322. this._searchFinishedCallback(this, -this._searchResults.length);
  323. this.performSearch(this.currentQuery, this._searchFinishedCallback);
  324. },
  325. _changeFilter: function()
  326. {
  327. var profileIndex = this.filterSelect.selectedIndex() - 1;
  328. this.dataGrid.filterSelectIndexChanged(this._profiles(), profileIndex);
  329. WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
  330. action: WebInspector.UserMetrics.UserActionNames.HeapSnapshotFilterChanged,
  331. label: this.filterSelect.selectedOption().label
  332. });
  333. if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
  334. return;
  335. // The current search needs to be performed again. First negate out previous match
  336. // count by calling the search finished callback with a negative number of matches.
  337. // Then perform the search again with the same query and callback.
  338. this._searchFinishedCallback(this, -this._searchResults.length);
  339. this.performSearch(this.currentQuery, this._searchFinishedCallback);
  340. },
  341. _createToolbarWithClassNameFilter: function()
  342. {
  343. var toolbar = document.createElement("div");
  344. toolbar.addStyleClass("class-view-toolbar");
  345. var classNameFilter = document.createElement("input");
  346. classNameFilter.addStyleClass("class-name-filter");
  347. classNameFilter.setAttribute("placeholder", WebInspector.UIString("Class filter"));
  348. classNameFilter.addEventListener("keyup", this._changeNameFilter.bind(this, classNameFilter), false);
  349. toolbar.appendChild(classNameFilter);
  350. return toolbar;
  351. },
  352. _changeNameFilter: function(classNameInputElement)
  353. {
  354. var filter = classNameInputElement.value;
  355. this.dataGrid.changeNameFilter(filter);
  356. },
  357. /**
  358. * @return {!Array.<!WebInspector.ProfileHeader>}
  359. */
  360. _profiles: function()
  361. {
  362. return this.parent.getProfileType(this._profileTypeId).getProfiles();
  363. },
  364. /**
  365. * @param {WebInspector.ContextMenu} contextMenu
  366. * @param {Event} event
  367. */
  368. populateContextMenu: function(contextMenu, event)
  369. {
  370. this.dataGrid.populateContextMenu(this.parent, contextMenu, event);
  371. },
  372. _selectionChanged: function(event)
  373. {
  374. var selectedNode = event.target.selectedNode;
  375. this._setRetainmentDataGridSource(selectedNode);
  376. this._inspectedObjectChanged(event);
  377. },
  378. _inspectedObjectChanged: function(event)
  379. {
  380. var selectedNode = event.target.selectedNode;
  381. if (!this.profile.fromFile() && selectedNode instanceof WebInspector.HeapSnapshotGenericObjectNode)
  382. ConsoleAgent.addInspectedHeapObject(selectedNode.snapshotNodeId);
  383. },
  384. _setRetainmentDataGridSource: function(nodeItem)
  385. {
  386. if (nodeItem && nodeItem.snapshotNodeIndex)
  387. this.retainmentDataGrid.setDataSource(nodeItem.isDeletedNode ? nodeItem.dataGrid.baseSnapshot : nodeItem.dataGrid.snapshot, nodeItem.snapshotNodeIndex);
  388. else
  389. this.retainmentDataGrid.reset();
  390. },
  391. _mouseDownInContentsGrid: function(event)
  392. {
  393. if (event.detail < 2)
  394. return;
  395. var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
  396. if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("shallowSize-column") && !cell.hasStyleClass("retainedSize-column")))
  397. return;
  398. event.consume(true);
  399. },
  400. changeView: function(viewTitle, callback)
  401. {
  402. var viewIndex = null;
  403. for (var i = 0; i < this.views.length; ++i) {
  404. if (this.views[i].title === viewTitle) {
  405. viewIndex = i;
  406. break;
  407. }
  408. }
  409. if (this.views.current === viewIndex || viewIndex == null) {
  410. setTimeout(callback, 0);
  411. return;
  412. }
  413. function dataGridContentShown(event)
  414. {
  415. var dataGrid = event.data;
  416. dataGrid.removeEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this);
  417. if (dataGrid === this.dataGrid)
  418. callback();
  419. }
  420. this.views[viewIndex].grid.addEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this);
  421. this.viewSelect.setSelectedIndex(viewIndex);
  422. this._changeView(viewIndex);
  423. },
  424. _updateDataSourceAndView: function()
  425. {
  426. var dataGrid = this.dataGrid;
  427. if (dataGrid.snapshot)
  428. return;
  429. this.profile.load(didLoadSnapshot.bind(this));
  430. function didLoadSnapshot(snapshotProxy)
  431. {
  432. if (this.dataGrid !== dataGrid)
  433. return;
  434. if (dataGrid.snapshot !== snapshotProxy)
  435. dataGrid.setDataSource(snapshotProxy);
  436. if (dataGrid === this.diffDataGrid) {
  437. if (!this._baseProfileUid)
  438. this._baseProfileUid = this._profiles()[this.baseSelect.selectedIndex()].uid;
  439. this.baseProfile.load(didLoadBaseSnaphot.bind(this));
  440. }
  441. }
  442. function didLoadBaseSnaphot(baseSnapshotProxy)
  443. {
  444. if (this.diffDataGrid.baseSnapshot !== baseSnapshotProxy)
  445. this.diffDataGrid.setBaseDataSource(baseSnapshotProxy);
  446. }
  447. },
  448. _onSelectedViewChanged: function(event)
  449. {
  450. this._changeView(event.target.selectedIndex);
  451. },
  452. _updateSelectorsVisibility: function()
  453. {
  454. if (this.currentView === this.diffView)
  455. this.baseSelect.element.removeStyleClass("hidden");
  456. else
  457. this.baseSelect.element.addStyleClass("hidden");
  458. if (this.currentView === this.constructorsView) {
  459. if (this._trackingOverviewGrid) {
  460. this._trackingOverviewGrid.element.removeStyleClass("hidden");
  461. this._trackingOverviewGrid.update();
  462. this.viewsContainer.addStyleClass("reserve-80px-at-top");
  463. }
  464. this.filterSelect.element.removeStyleClass("hidden");
  465. } else {
  466. this.filterSelect.element.addStyleClass("hidden");
  467. if (this._trackingOverviewGrid) {
  468. this._trackingOverviewGrid.element.addStyleClass("hidden");
  469. this.viewsContainer.removeStyleClass("reserve-80px-at-top");
  470. }
  471. }
  472. },
  473. _changeView: function(selectedIndex)
  474. {
  475. if (selectedIndex === this.views.current)
  476. return;
  477. this.views.current = selectedIndex;
  478. this.currentView.detach();
  479. var view = this.views[this.views.current];
  480. this.currentView = view.view;
  481. this.dataGrid = view.grid;
  482. this.currentView.show(this.viewsContainer);
  483. this.refreshVisibleData();
  484. this.dataGrid.updateWidths();
  485. this._updateSelectorsVisibility();
  486. this._updateDataSourceAndView();
  487. if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
  488. return;
  489. // The current search needs to be performed again. First negate out previous match
  490. // count by calling the search finished callback with a negative number of matches.
  491. // Then perform the search again the with same query and callback.
  492. this._searchFinishedCallback(this, -this._searchResults.length);
  493. this.performSearch(this.currentQuery, this._searchFinishedCallback);
  494. },
  495. _getHoverAnchor: function(target)
  496. {
  497. var span = target.enclosingNodeOrSelfWithNodeName("span");
  498. if (!span)
  499. return;
  500. var row = target.enclosingNodeOrSelfWithNodeName("tr");
  501. if (!row)
  502. return;
  503. span.node = row._dataGridNode;
  504. return span;
  505. },
  506. _resolveObjectForPopover: function(element, showCallback, objectGroupName)
  507. {
  508. if (this.profile.fromFile())
  509. return;
  510. element.node.queryObjectContent(showCallback, objectGroupName);
  511. },
  512. /**
  513. * @return {boolean}
  514. */
  515. _startRetainersHeaderDragging: function(event)
  516. {
  517. if (!this.isShowing())
  518. return false;
  519. this._previousDragPosition = event.pageY;
  520. return true;
  521. },
  522. _retainersHeaderDragging: function(event)
  523. {
  524. var height = this.retainmentView.element.clientHeight;
  525. height += this._previousDragPosition - event.pageY;
  526. this._previousDragPosition = event.pageY;
  527. this._updateRetainmentViewHeight(height);
  528. event.consume(true);
  529. },
  530. _endRetainersHeaderDragging: function(event)
  531. {
  532. delete this._previousDragPosition;
  533. event.consume();
  534. },
  535. _updateRetainmentViewHeight: function(height)
  536. {
  537. height = Number.constrain(height, Preferences.minConsoleHeight, this.element.clientHeight - Preferences.minConsoleHeight);
  538. this.viewsContainer.style.bottom = (height + this.retainmentViewHeader.clientHeight) + "px";
  539. if (this._trackingOverviewGrid && this.currentView === this.constructorsView)
  540. this.viewsContainer.addStyleClass("reserve-80px-at-top");
  541. this.retainmentView.element.style.height = height + "px";
  542. this.retainmentViewHeader.style.bottom = height + "px";
  543. this.currentView.doResize();
  544. },
  545. _updateBaseOptions: function()
  546. {
  547. var list = this._profiles();
  548. // We're assuming that snapshots can only be added.
  549. if (this.baseSelect.size() === list.length)
  550. return;
  551. for (var i = this.baseSelect.size(), n = list.length; i < n; ++i) {
  552. var title = list[i].title;
  553. if (WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(title))
  554. title = WebInspector.UIString("Snapshot %d", WebInspector.ProfilesPanelDescriptor.userInitiatedProfileIndex(title));
  555. this.baseSelect.createOption(title);
  556. }
  557. },
  558. _updateFilterOptions: function()
  559. {
  560. var list = this._profiles();
  561. // We're assuming that snapshots can only be added.
  562. if (this.filterSelect.size() - 1 === list.length)
  563. return;
  564. if (!this.filterSelect.size())
  565. this.filterSelect.createOption(WebInspector.UIString("All objects"));
  566. if (this.profile.fromFile())
  567. return;
  568. for (var i = this.filterSelect.size() - 1, n = list.length; i < n; ++i) {
  569. var profile = list[i];
  570. var title = list[i].title;
  571. if (WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(title)) {
  572. var profileIndex = WebInspector.ProfilesPanelDescriptor.userInitiatedProfileIndex(title);
  573. if (!i)
  574. title = WebInspector.UIString("Objects allocated before Snapshot %d", profileIndex);
  575. else
  576. title = WebInspector.UIString("Objects allocated between Snapshots %d and %d", profileIndex - 1, profileIndex);
  577. }
  578. this.filterSelect.createOption(title);
  579. }
  580. },
  581. /**
  582. * @param {WebInspector.Event} event
  583. */
  584. _onProfileHeaderAdded: function(event)
  585. {
  586. if (!event.data || event.data.type !== this._profileTypeId)
  587. return;
  588. this._updateBaseOptions();
  589. this._updateFilterOptions();
  590. },
  591. __proto__: WebInspector.View.prototype
  592. }
  593. /**
  594. * @constructor
  595. * @implements {HeapProfilerAgent.Dispatcher}
  596. */
  597. WebInspector.HeapProfilerDispatcher = function()
  598. {
  599. this._dispatchers = [];
  600. InspectorBackend.registerHeapProfilerDispatcher(this);
  601. }
  602. WebInspector.HeapProfilerDispatcher.prototype = {
  603. /**
  604. * @param {HeapProfilerAgent.Dispatcher} dispatcher
  605. */
  606. register: function(dispatcher)
  607. {
  608. this._dispatchers.push(dispatcher);
  609. },
  610. _genericCaller: function(eventName)
  611. {
  612. var args = Array.prototype.slice.call(arguments.callee.caller.arguments);
  613. for (var i = 0; i < this._dispatchers.length; ++i)
  614. this._dispatchers[i][eventName].apply(this._dispatchers[i], args);
  615. },
  616. /**
  617. * @override
  618. * @param {Array.<number>} samples
  619. */
  620. heapStatsUpdate: function(samples)
  621. {
  622. this._genericCaller("heapStatsUpdate");
  623. },
  624. /**
  625. * @override
  626. * @param {number} lastSeenObjectId
  627. * @param {number} timestamp
  628. */
  629. lastSeenObjectId: function(lastSeenObjectId, timestamp)
  630. {
  631. this._genericCaller("lastSeenObjectId");
  632. },
  633. /**
  634. * @param {HeapProfilerAgent.ProfileHeader} profileHeader
  635. */
  636. addProfileHeader: function(profileHeader)
  637. {
  638. this._genericCaller("addProfileHeader");
  639. },
  640. /**
  641. * @override
  642. * @param {number} uid
  643. * @param {string} chunk
  644. */
  645. addHeapSnapshotChunk: function(uid, chunk)
  646. {
  647. this._genericCaller("addHeapSnapshotChunk");
  648. },
  649. /**
  650. * @override
  651. * @param {number} uid
  652. */
  653. finishHeapSnapshot: function(uid)
  654. {
  655. this._genericCaller("finishHeapSnapshot");
  656. },
  657. /**
  658. * @override
  659. * @param {number} done
  660. * @param {number} total
  661. */
  662. reportHeapSnapshotProgress: function(done, total)
  663. {
  664. this._genericCaller("reportHeapSnapshotProgress");
  665. },
  666. /**
  667. * @override
  668. */
  669. resetProfiles: function()
  670. {
  671. this._genericCaller("resetProfiles");
  672. }
  673. }
  674. WebInspector.HeapProfilerDispatcher._dispatcher = new WebInspector.HeapProfilerDispatcher();
  675. /**
  676. * @constructor
  677. * @extends {WebInspector.ProfileType}
  678. * @implements {HeapProfilerAgent.Dispatcher}
  679. */
  680. WebInspector.HeapSnapshotProfileType = function()
  681. {
  682. WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("Take Heap Snapshot"));
  683. WebInspector.HeapProfilerDispatcher._dispatcher.register(this);
  684. }
  685. WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";
  686. WebInspector.HeapSnapshotProfileType.SnapshotReceived = "SnapshotReceived";
  687. WebInspector.HeapSnapshotProfileType.prototype = {
  688. /**
  689. * @override
  690. * @return {string}
  691. */
  692. fileExtension: function()
  693. {
  694. return ".heapsnapshot";
  695. },
  696. get buttonTooltip()
  697. {
  698. return WebInspector.UIString("Take heap snapshot.");
  699. },
  700. /**
  701. * @override
  702. * @return {boolean}
  703. */
  704. isInstantProfile: function()
  705. {
  706. return true;
  707. },
  708. /**
  709. * @override
  710. * @return {boolean}
  711. */
  712. buttonClicked: function()
  713. {
  714. this._takeHeapSnapshot(function() {});
  715. WebInspector.userMetrics.ProfilesHeapProfileTaken.record();
  716. return false;
  717. },
  718. /**
  719. * @override
  720. * @param {Array.<number>} samples
  721. */
  722. heapStatsUpdate: function(samples)
  723. {
  724. },
  725. /**
  726. * @override
  727. * @param {number} lastSeenObjectId
  728. * @param {number} timestamp
  729. */
  730. lastSeenObjectId: function(lastSeenObjectId, timestamp)
  731. {
  732. },
  733. get treeItemTitle()
  734. {
  735. return WebInspector.UIString("HEAP SNAPSHOTS");
  736. },
  737. get description()
  738. {
  739. return WebInspector.UIString("Heap snapshot profiles show memory distribution among your page's JavaScript objects and related DOM nodes.");
  740. },
  741. /**
  742. * @override
  743. * @param {string=} title
  744. * @return {!WebInspector.ProfileHeader}
  745. */
  746. createTemporaryProfile: function(title)
  747. {
  748. title = title || WebInspector.UIString("Snapshotting\u2026");
  749. return new WebInspector.HeapProfileHeader(this, title);
  750. },
  751. /**
  752. * @override
  753. * @param {HeapProfilerAgent.ProfileHeader} profile
  754. * @return {!WebInspector.ProfileHeader}
  755. */
  756. createProfile: function(profile)
  757. {
  758. return new WebInspector.HeapProfileHeader(this, profile.title, profile.uid, profile.maxJSObjectId || 0);
  759. },
  760. _takeHeapSnapshot: function(callback)
  761. {
  762. var temporaryProfile = this.findTemporaryProfile();
  763. if (!temporaryProfile)
  764. this.addProfile(this.createTemporaryProfile());
  765. HeapProfilerAgent.takeHeapSnapshot(true, callback);
  766. },
  767. /**
  768. * @param {HeapProfilerAgent.ProfileHeader} profileHeader
  769. */
  770. addProfileHeader: function(profileHeader)
  771. {
  772. if (!this.findTemporaryProfile())
  773. return;
  774. var profile = this.createProfile(profileHeader);
  775. profile._profileSamples = this._profileSamples;
  776. this._profileSamples = null;
  777. this.addProfile(profile);
  778. },
  779. /**
  780. * @override
  781. * @param {number} uid
  782. * @param {string} chunk
  783. */
  784. addHeapSnapshotChunk: function(uid, chunk)
  785. {
  786. var profile = this._profilesIdMap[this._makeKey(uid)];
  787. if (profile)
  788. profile.transferChunk(chunk);
  789. },
  790. /**
  791. * @override
  792. * @param {number} uid
  793. */
  794. finishHeapSnapshot: function(uid)
  795. {
  796. var profile = this._profilesIdMap[this._makeKey(uid)];
  797. if (profile)
  798. profile.finishHeapSnapshot();
  799. },
  800. /**
  801. * @override
  802. * @param {number} done
  803. * @param {number} total
  804. */
  805. reportHeapSnapshotProgress: function(done, total)
  806. {
  807. var profile = this.findTemporaryProfile();
  808. if (profile)
  809. this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProgressUpdated, {"profile": profile, "done": done, "total": total});
  810. },
  811. /**
  812. * @override
  813. */
  814. resetProfiles: function()
  815. {
  816. this._reset();
  817. },
  818. /**
  819. * @override
  820. * @param {!WebInspector.ProfileHeader} profile
  821. */
  822. removeProfile: function(profile)
  823. {
  824. WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
  825. if (!profile.isTemporary)
  826. HeapProfilerAgent.removeProfile(profile.uid);
  827. },
  828. /**
  829. * @override
  830. * @param {function(this:WebInspector.ProfileType, ?string, Array.<HeapProfilerAgent.ProfileHeader>)} populateCallback
  831. */
  832. _requestProfilesFromBackend: function(populateCallback)
  833. {
  834. HeapProfilerAgent.getProfileHeaders(populateCallback);
  835. },
  836. _snapshotReceived: function(profile)
  837. {
  838. this.dispatchEventToListeners(WebInspector.HeapSnapshotProfileType.SnapshotReceived, profile);
  839. },
  840. __proto__: WebInspector.ProfileType.prototype
  841. }
  842. /**
  843. * @constructor
  844. * @extends {WebInspector.HeapSnapshotProfileType}
  845. * @param {WebInspector.ProfilesPanel} profilesPanel
  846. */
  847. WebInspector.TrackingHeapSnapshotProfileType = function(profilesPanel)
  848. {
  849. WebInspector.ProfileType.call(this, WebInspector.TrackingHeapSnapshotProfileType.TypeId, WebInspector.UIString("Record Heap Allocations"));
  850. this._profilesPanel = profilesPanel;
  851. WebInspector.HeapProfilerDispatcher._dispatcher.register(this);
  852. }
  853. WebInspector.TrackingHeapSnapshotProfileType.TypeId = "HEAP-RECORD";
  854. WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate = "HeapStatsUpdate";
  855. WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted = "TrackingStarted";
  856. WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped = "TrackingStopped";
  857. WebInspector.TrackingHeapSnapshotProfileType.prototype = {
  858. /**
  859. * @override
  860. * @param {Array.<number>} samples
  861. */
  862. heapStatsUpdate: function(samples)
  863. {
  864. if (!this._profileSamples)
  865. return;
  866. var index;
  867. for (var i = 0; i < samples.length; i += 3) {
  868. index = samples[i];
  869. var count = samples[i+1];
  870. var size = samples[i+2];
  871. this._profileSamples.sizes[index] = size;
  872. if (!this._profileSamples.max[index] || size > this._profileSamples.max[index])
  873. this._profileSamples.max[index] = size;
  874. }
  875. this._lastUpdatedIndex = index;
  876. },
  877. /**
  878. * @override
  879. * @param {number} lastSeenObjectId
  880. * @param {number} timestamp
  881. */
  882. lastSeenObjectId: function(lastSeenObjectId, timestamp)
  883. {
  884. var profileSamples = this._profileSamples;
  885. if (!profileSamples)
  886. return;
  887. var currentIndex = Math.max(profileSamples.ids.length, profileSamples.max.length - 1);
  888. profileSamples.ids[currentIndex] = lastSeenObjectId;
  889. if (!profileSamples.max[currentIndex]) {
  890. profileSamples.max[currentIndex] = 0;
  891. profileSamples.sizes[currentIndex] = 0;
  892. }
  893. profileSamples.timestamps[currentIndex] = timestamp;
  894. if (profileSamples.totalTime < timestamp - profileSamples.timestamps[0])
  895. profileSamples.totalTime *= 2;
  896. this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._profileSamples);
  897. var profile = this.findTemporaryProfile();
  898. profile.sidebarElement.wait = true;
  899. if (profile.sidebarElement && !profile.sidebarElement.wait)
  900. profile.sidebarElement.wait = true;
  901. },
  902. /**
  903. * @override
  904. * @return {boolean}
  905. */
  906. hasTemporaryView: function()
  907. {
  908. return true;
  909. },
  910. get buttonTooltip()
  911. {
  912. return this._recording ? WebInspector.UIString("Stop recording heap profile.") : WebInspector.UIString("Start recording heap profile.");
  913. },
  914. /**
  915. * @override
  916. * @return {boolean}
  917. */
  918. isInstantProfile: function()
  919. {
  920. return false;
  921. },
  922. /**
  923. * @override
  924. * @return {boolean}
  925. */
  926. buttonClicked: function()
  927. {
  928. return this._toggleRecording();
  929. },
  930. _startRecordingProfile: function()
  931. {
  932. this._lastSeenIndex = -1;
  933. this._profileSamples = {
  934. 'sizes': [],
  935. 'ids': [],
  936. 'timestamps': [],
  937. 'max': [],
  938. 'totalTime': 30000
  939. };
  940. this._recording = true;
  941. HeapProfilerAgent.startTrackingHeapObjects();
  942. this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted);
  943. },
  944. _stopRecordingProfile: function()
  945. {
  946. HeapProfilerAgent.stopTrackingHeapObjects();
  947. HeapProfilerAgent.takeHeapSnapshot(true);
  948. this._recording = false;
  949. this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped);
  950. },
  951. _toggleRecording: function()
  952. {
  953. if (this._recording)
  954. this._stopRecordingProfile();
  955. else
  956. this._startRecordingProfile();
  957. return this._recording;
  958. },
  959. get treeItemTitle()
  960. {
  961. return WebInspector.UIString("HEAP TIMELINES");
  962. },
  963. get description()
  964. {
  965. return WebInspector.UIString("Record JavaScript object allocations over time. Use this profile type to isolate memory leaks.");
  966. },
  967. _reset: function()
  968. {
  969. WebInspector.HeapSnapshotProfileType.prototype._reset.call(this);
  970. if (this._recording)
  971. this._stopRecordingProfile();
  972. this._profileSamples = null;
  973. this._lastSeenIndex = -1;
  974. },
  975. /**
  976. * @override
  977. * @param {string=} title
  978. * @return {!WebInspector.ProfileHeader}
  979. */
  980. createTemporaryProfile: function(title)
  981. {
  982. title = title || WebInspector.UIString("Recording\u2026");
  983. return new WebInspector.HeapProfileHeader(this, title);
  984. },
  985. /**
  986. * @override
  987. * @param {function(this:WebInspector.ProfileType, ?string, Array.<HeapProfilerAgent.ProfileHeader>)} populateCallback
  988. */
  989. _requestProfilesFromBackend: function(populateCallback)
  990. {
  991. },
  992. __proto__: WebInspector.HeapSnapshotProfileType.prototype
  993. }
  994. /**
  995. * @constructor
  996. * @extends {WebInspector.ProfileHeader}
  997. * @param {!WebInspector.ProfileType} type
  998. * @param {string} title
  999. * @param {number=} uid
  1000. * @param {number=} maxJSObjectId
  1001. */
  1002. WebInspector.HeapProfileHeader = function(type, title, uid, maxJSObjectId)
  1003. {
  1004. WebInspector.ProfileHeader.call(this, type, title, uid);
  1005. this.maxJSObjectId = maxJSObjectId;
  1006. /**
  1007. * @type {WebInspector.OutputStream}
  1008. */
  1009. this._receiver = null;
  1010. /**
  1011. * @type {WebInspector.HeapSnapshotProxy}
  1012. */
  1013. this._snapshotProxy = null;
  1014. this._totalNumberOfChunks = 0;
  1015. this._transferHandler = null;
  1016. }
  1017. WebInspector.HeapProfileHeader.prototype = {
  1018. /**
  1019. * @override
  1020. */
  1021. createSidebarTreeElement: function()
  1022. {
  1023. return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item");
  1024. },
  1025. /**
  1026. * @override
  1027. * @param {!WebInspector.ProfilesPanel} profilesPanel
  1028. */
  1029. createView: function(profilesPanel)
  1030. {
  1031. return new WebInspector.HeapSnapshotView(profilesPanel, this);
  1032. },
  1033. /**
  1034. * @override
  1035. * @param {function(WebInspector.HeapSnapshotProxy):void} callback
  1036. */
  1037. load: function(callback)
  1038. {
  1039. if (this.uid === -1)
  1040. return;
  1041. if (this._snapshotProxy) {
  1042. callback(this._snapshotProxy);
  1043. return;
  1044. }
  1045. this._numberOfChunks = 0;
  1046. if (!this._receiver) {
  1047. this._setupWorker();
  1048. this._transferHandler = new WebInspector.BackendSnapshotLoader(this);
  1049. this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
  1050. this.sidebarElement.wait = true;
  1051. this.startSnapshotTransfer();
  1052. }
  1053. var loaderProxy = /** @type {WebInspector.HeapSnapshotLoaderProxy} */ (this._receiver);
  1054. loaderProxy.addConsumer(callback);
  1055. },
  1056. startSnapshotTransfer: function()
  1057. {
  1058. HeapProfilerAgent.getHeapSnapshot(this.uid);
  1059. },
  1060. snapshotConstructorName: function()
  1061. {
  1062. return "JSHeapSnapshot";
  1063. },
  1064. snapshotProxyConstructor: function()
  1065. {
  1066. return WebInspector.HeapSnapshotProxy;
  1067. },
  1068. _setupWorker: function()
  1069. {
  1070. function setProfileWait(event)
  1071. {
  1072. this.sidebarElement.wait = event.data;
  1073. }
  1074. var worker = new WebInspector.HeapSnapshotWorkerProxy(this._handleWorkerEvent.bind(this));
  1075. worker.addEventListener("wait", setProfileWait, this);
  1076. var loaderProxy = worker.createLoader(this.snapshotConstructorName(), this.snapshotProxyConstructor());
  1077. loaderProxy.addConsumer(this._snapshotReceived.bind(this));
  1078. this._receiver = loaderProxy;
  1079. },
  1080. /**
  1081. * @param{string} eventName
  1082. * @param{*} data
  1083. */
  1084. _handleWorkerEvent: function(eventName, data)
  1085. {
  1086. if (WebInspector.HeapSnapshotProgress.Event.Update !== eventName)
  1087. return;
  1088. this._updateSubtitle(data);
  1089. },
  1090. /**
  1091. * @override
  1092. */
  1093. dispose: function()
  1094. {
  1095. if (this._receiver)
  1096. this._receiver.close();
  1097. else if (this._snapshotProxy)
  1098. this._snapshotProxy.dispose();
  1099. if (this._view) {
  1100. var view = this._view;
  1101. this._view = null;
  1102. view.dispose();
  1103. }
  1104. },
  1105. _updateSubtitle: function(value)
  1106. {
  1107. this.sidebarElement.subtitle = value;
  1108. },
  1109. _didCompleteSnapshotTransfer: function()
  1110. {
  1111. this.sidebarElement.subtitle = Number.bytesToString(this._snapshotProxy.totalSize);
  1112. this.sidebarElement.wait = false;
  1113. },
  1114. /**
  1115. * @param {string} chunk
  1116. */
  1117. transferChunk: function(chunk)
  1118. {
  1119. this._transferHandler.transferChunk(chunk);
  1120. },
  1121. _snapshotReceived: function(snapshotProxy)
  1122. {
  1123. this._receiver = null;
  1124. if (snapshotProxy)
  1125. this._snapshotProxy = snapshotProxy;
  1126. this._didCompleteSnapshotTransfer();
  1127. var worker = /** @type {WebInspector.HeapSnapshotWorkerProxy} */ (this._snapshotProxy.worker);
  1128. this.isTemporary = false;
  1129. worker.startCheckingForLongRunningCalls();
  1130. this.notifySnapshotReceived();
  1131. },
  1132. notifySnapshotReceived: function()
  1133. {
  1134. this._profileType._snapshotReceived(this);
  1135. },
  1136. finishHeapSnapshot: function()
  1137. {
  1138. if (this._transferHandler) {
  1139. this._transferHandler.finishTransfer();
  1140. this._totalNumberOfChunks = this._transferHandler._totalNumberOfChunks;
  1141. }
  1142. },
  1143. // Hook point for tests.
  1144. _wasShown: function()
  1145. {
  1146. },
  1147. /**
  1148. * @override
  1149. * @return {boolean}
  1150. */
  1151. canSaveToFile: function()
  1152. {
  1153. return !this.fromFile() && !!this._snapshotProxy && !this._receiver;
  1154. },
  1155. /**
  1156. * @override
  1157. */
  1158. saveToFile: function()
  1159. {
  1160. var fileOutputStream = new WebInspector.FileOutputStream();
  1161. function onOpen()
  1162. {
  1163. this._receiver = fileOutputStream;
  1164. this._transferHandler = new WebInspector.SaveSnapshotHandler(this);
  1165. HeapProfilerAgent.getHeapSnapshot(this.uid);
  1166. }
  1167. this._fileName = this._fileName || "Heap-" + new Date().toISO8601Compact() + this._profileType.fileExtension();
  1168. fileOutputStream.open(this._fileName, onOpen.bind(this));
  1169. },
  1170. /**
  1171. * @override
  1172. * @param {File} file
  1173. */
  1174. loadFromFile: function(file)
  1175. {
  1176. this.title = file.name;
  1177. this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
  1178. this.sidebarElement.wait = true;
  1179. this._setupWorker();
  1180. var delegate = new WebInspector.HeapSnapshotLoadFromFileDelegate(this);
  1181. var fileReader = this._createFileReader(file, delegate);
  1182. fileReader.start(this._receiver);
  1183. },
  1184. _createFileReader: function(file, delegate)
  1185. {
  1186. return new WebInspector.ChunkedFileReader(file, 10000000, delegate);
  1187. },
  1188. __proto__: WebInspector.ProfileHeader.prototype
  1189. }
  1190. /**
  1191. * @constructor
  1192. * @param {WebInspector.HeapProfileHeader} header
  1193. * @param {string} title
  1194. */
  1195. WebInspector.SnapshotTransferHandler = function(header, title)
  1196. {
  1197. this._numberOfChunks = 0;
  1198. this._savedChunks = 0;
  1199. this._header = header;
  1200. this._totalNumberOfChunks = 0;
  1201. this._title = title;
  1202. }
  1203. WebInspector.SnapshotTransferHandler.prototype = {
  1204. /**
  1205. * @param {string} chunk
  1206. */
  1207. transferChunk: function(chunk)
  1208. {
  1209. ++this._numberOfChunks;
  1210. this._header._receiver.write(chunk, this._didTransferChunk.bind(this));
  1211. },
  1212. finishTransfer: function()
  1213. {
  1214. },
  1215. _didTransferChunk: function()
  1216. {
  1217. this._updateProgress(++this._savedChunks, this._totalNumberOfChunks);
  1218. },
  1219. _updateProgress: function(value, total)
  1220. {
  1221. }
  1222. }
  1223. /**
  1224. * @constructor
  1225. * @param {WebInspector.HeapProfileHeader} header
  1226. * @extends {WebInspector.SnapshotTransferHandler}
  1227. */
  1228. WebInspector.SaveSnapshotHandler = function(header)
  1229. {
  1230. WebInspector.SnapshotTransferHandler.call(this, header, "Saving\u2026 %d\%");
  1231. this._totalNumberOfChunks = header._totalNumberOfChunks;
  1232. this._updateProgress(0, this._totalNumberOfChunks);
  1233. }
  1234. WebInspector.SaveSnapshotHandler.prototype = {
  1235. _updateProgress: function(value, total)
  1236. {
  1237. var percentValue = ((total ? (value / total) : 0) * 100).toFixed(0);
  1238. this._header._updateSubtitle(WebInspector.UIString(this._title, percentValue));
  1239. if (value === total) {
  1240. this._header._receiver.close();
  1241. this._header._didCompleteSnapshotTransfer();
  1242. }
  1243. },
  1244. __proto__: WebInspector.SnapshotTransferHandler.prototype
  1245. }
  1246. /**
  1247. * @constructor
  1248. * @param {WebInspector.HeapProfileHeader} header
  1249. * @extends {WebInspector.SnapshotTransferHandler}
  1250. */
  1251. WebInspector.BackendSnapshotLoader = function(header)
  1252. {
  1253. WebInspector.SnapshotTransferHandler.call(this, header, "Loading\u2026 %d\%");
  1254. }
  1255. WebInspector.BackendSnapshotLoader.prototype = {
  1256. finishTransfer: function()
  1257. {
  1258. this._header._receiver.close(this._didFinishTransfer.bind(this));
  1259. this._totalNumberOfChunks = this._numberOfChunks;
  1260. },
  1261. _didFinishTransfer: function()
  1262. {
  1263. console.assert(this._totalNumberOfChunks === this._savedChunks, "Not all chunks were transfered.");
  1264. },
  1265. __proto__: WebInspector.SnapshotTransferHandler.prototype
  1266. }
  1267. /**
  1268. * @constructor
  1269. * @implements {WebInspector.OutputStreamDelegate}
  1270. */
  1271. WebInspector.HeapSnapshotLoadFromFileDelegate = function(snapshotHeader)
  1272. {
  1273. this._snapshotHeader = snapshotHeader;
  1274. }
  1275. WebInspector.HeapSnapshotLoadFromFileDelegate.prototype = {
  1276. onTransferStarted: function()
  1277. {
  1278. },
  1279. /**
  1280. * @param {WebInspector.ChunkedReader} reader
  1281. */
  1282. onChunkTransferred: function(reader)
  1283. {
  1284. },
  1285. onTransferFinished: function()
  1286. {
  1287. },
  1288. /**
  1289. * @param {WebInspector.ChunkedReader} reader
  1290. */
  1291. onError: function (reader, e)
  1292. {
  1293. switch(e.target.error.code) {
  1294. case e.target.error.NOT_FOUND_ERR:
  1295. this._snapshotHeader._updateSubtitle(WebInspector.UIString("'%s' not found.", reader.fileName()));
  1296. break;
  1297. case e.target.error.NOT_READABLE_ERR:
  1298. this._snapshotHeader._updateSubtitle(WebInspector.UIString("'%s' is not readable", reader.fileName()));
  1299. break;
  1300. case e.target.error.ABORT_ERR:
  1301. break;
  1302. default:
  1303. this._snapshotHeader._updateSubtitle(WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code));
  1304. }
  1305. }
  1306. }
  1307. /**
  1308. * @constructor
  1309. * @extends {WebInspector.View}
  1310. * @param {!WebInspector.HeapProfileHeader} heapProfileHeader
  1311. */
  1312. WebInspector.HeapTrackingOverviewGrid = function(heapProfileHeader)
  1313. {
  1314. WebInspector.View.call(this);
  1315. this.registerRequiredCSS("flameChart.css");
  1316. this.element.id = "heap-recording-view";
  1317. this._overviewContainer = this.element.createChild("div", "overview-container");
  1318. this._overviewGrid = new WebInspector.OverviewGrid("heap-recording");
  1319. this._overviewCanvas = this._overviewContainer.createChild("canvas", "heap-recording-overview-canvas");
  1320. this._overviewContainer.appendChild(this._overviewGrid.element);
  1321. this._overviewCalculator = new WebInspector.HeapTrackingOverviewGrid.OverviewCalculator();
  1322. this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
  1323. this._profileSamples = heapProfileHeader._profileSamples || heapProfileHeader._profileType._profileSamples;
  1324. if (heapProfileHeader.isTemporary) {
  1325. this._profileType = heapProfileHeader._profileType;
  1326. this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this);
  1327. this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this);
  1328. }
  1329. var timestamps = this._profileSamples.timestamps;
  1330. var totalTime = this._profileSamples.totalTime;
  1331. this._windowLeft = 0.0;
  1332. this._windowRight = totalTime && timestamps.length ? (timestamps[timestamps.length - 1] - timestamps[0]) / totalTime : 1.0;
  1333. this._overviewGrid.setWindow(this._windowLeft, this._windowRight);
  1334. this._yScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale();
  1335. this._xScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale();
  1336. }
  1337. WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged = "IdsRangeChanged";
  1338. WebInspector.HeapTrackingOverviewGrid.prototype = {
  1339. _onStopTracking: function(event)
  1340. {
  1341. this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this);
  1342. this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this);
  1343. },
  1344. _onHeapStatsUpdate: function(event)
  1345. {
  1346. this._profileSamples = event.data;
  1347. this._scheduleUpdate();
  1348. },
  1349. /**
  1350. * @param {number} width
  1351. * @param {number} height
  1352. */
  1353. _drawOverviewCanvas: function(width, height)
  1354. {
  1355. if (!this._profileSamples)
  1356. return;
  1357. var profileSamples = this._profileSamples;
  1358. var sizes = profileSamples.sizes;
  1359. var topSizes = profileSamples.max;
  1360. var timestamps = profileSamples.timestamps;
  1361. var startTime = timestamps[0];
  1362. var endTime = timestamps[timestamps.length - 1];
  1363. var scaleFactor = this._xScale.nextScale(width / profileSamples.totalTime);
  1364. var maxSize = 0;
  1365. /**
  1366. * @param {Array.<number>} sizes
  1367. * @param {function(number, number):void} callback
  1368. */
  1369. function aggregateAndCall(sizes, callback)
  1370. {
  1371. var size = 0;
  1372. var currentX = 0;
  1373. for (var i = 1; i < timestamps.length; ++i) {
  1374. var x = Math.floor((timestamps[i] - startTime) * scaleFactor);
  1375. if (x !== currentX) {
  1376. if (size)
  1377. callback(currentX, size);
  1378. size = 0;
  1379. currentX = x;
  1380. }
  1381. size += sizes[i];
  1382. }
  1383. callback(currentX, size);
  1384. }
  1385. /**
  1386. * @param {number} x
  1387. * @param {number} size
  1388. */
  1389. function maxSizeCallback(x, size)
  1390. {
  1391. maxSize = Math.max(maxSize, size);
  1392. }
  1393. aggregateAndCall(sizes, maxSizeCallback);
  1394. var yScaleFactor = this._yScale.nextScale(maxSize ? height / (maxSize * 1.1) : 0.0);
  1395. this._overviewCanvas.width = width * window.devicePixelRatio;
  1396. this._overviewCanvas.height = height * window.devicePixelRatio;
  1397. this._overviewCanvas.style.width = width + "px";
  1398. this._overviewCanvas.style.height = height + "px";
  1399. var context = this._overviewCanvas.getContext("2d");
  1400. context.scale(window.devicePixelRatio, window.devicePixelRatio);
  1401. context.beginPath();
  1402. context.lineWidth = 2;
  1403. context.strokeStyle = "rgba(192, 192, 192, 0.6)";
  1404. var currentX = (endTime - startTime) * scaleFactor;
  1405. context.moveTo(currentX, height - 1);
  1406. context.lineTo(currentX, 0);
  1407. context.stroke();
  1408. context.closePath();
  1409. var gridY;
  1410. var gridValue;
  1411. var gridLabelHeight = 14;
  1412. if (yScaleFactor) {
  1413. const maxGridValue = (height - gridLabelHeight) / yScaleFactor;
  1414. // The round value calculation is a bit tricky, because
  1415. // it has a form k*10^n*1024^m, where k=[1,5], n=[0..3], m is an integer,
  1416. // e.g. a round value 10KB is 10240 bytes.
  1417. gridValue = Math.pow(1024, Math.floor(Math.log(maxGridValue) / Math.log(1024)));
  1418. gridValue *= Math.pow(10, Math.floor(Math.log(maxGridValue / gridValue) / Math.log(10)));
  1419. if (gridValue * 5 <= maxGridValue)
  1420. gridValue *= 5;
  1421. gridY = Math.round(height - gridValue * yScaleFactor - 0.5) + 0.5;
  1422. context.beginPath();
  1423. context.lineWidth = 1;
  1424. context.strokeStyle = "rgba(0, 0, 0, 0.2)";
  1425. context.moveTo(0, gridY);
  1426. context.lineTo(width, gridY);
  1427. context.stroke();
  1428. context.closePath();
  1429. }
  1430. /**
  1431. * @param {number} x
  1432. * @param {number} size
  1433. */
  1434. function drawBarCallback(x, size)
  1435. {
  1436. context.moveTo(x, height - 1);
  1437. context.lineTo(x, Math.round(height - size * yScaleFactor - 1));
  1438. }
  1439. context.beginPath();
  1440. context.lineWidth = 2;
  1441. context.strokeStyle = "rgba(192, 192, 192, 0.6)";
  1442. aggregateAndCall(topSizes, drawBarCallback);
  1443. context.stroke();
  1444. context.closePath();
  1445. context.beginPath();
  1446. context.lineWidth = 2;
  1447. context.strokeStyle = "rgba(0, 0, 192, 0.8)";
  1448. aggregateAndCall(sizes, drawBarCallback);
  1449. context.stroke();
  1450. context.closePath();
  1451. if (gridValue) {
  1452. var label = Number.bytesToString(gridValue);
  1453. var labelPadding = 4;
  1454. var labelX = 0;
  1455. var labelY = gridY - 0.5;
  1456. var labelWidth = 2 * labelPadding + context.measureText(label).width;
  1457. context.beginPath();
  1458. context.textBaseline = "bottom";
  1459. context.font = "10px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family");
  1460. context.fillStyle = "rgba(255, 255, 255, 0.75)";
  1461. context.fillRect(labelX, labelY - gridLabelHeight, labelWidth, gridLabelHeight);
  1462. context.fillStyle = "rgb(64, 64, 64)";
  1463. context.fillText(label, labelX + labelPadding, labelY);
  1464. context.fill();
  1465. context.closePath();
  1466. }
  1467. },
  1468. onResize: function()
  1469. {
  1470. this._updateOverviewCanvas = true;
  1471. this._scheduleUpdate();
  1472. },
  1473. _onWindowChanged: function()
  1474. {
  1475. if (!this._updateGridTimerId)
  1476. this._updateGridTimerId = setTimeout(this._updateGrid.bind(this), 10);
  1477. },
  1478. _scheduleUpdate: function()
  1479. {
  1480. if (this._updateTimerId)
  1481. return;
  1482. this._updateTimerId = setTimeout(this.update.bind(this), 10);
  1483. },
  1484. _updateBoundaries: function()
  1485. {
  1486. this._windowLeft = this._overviewGrid.windowLeft();
  1487. this._windowRight = this._overviewGrid.windowRight();
  1488. this._windowWidth = this._windowRight - this._windowLeft;
  1489. },
  1490. update: function()
  1491. {
  1492. this._updateTimerId = null;
  1493. if (!this.isShowing())
  1494. return;
  1495. this._updateBoundaries();
  1496. this._overviewCalculator._updateBoundaries(this);
  1497. this._overviewGrid.updateDividers(this._overviewCalculator);
  1498. this._drawOverviewCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - 20);
  1499. },
  1500. _updateGrid: function()
  1501. {
  1502. this._updateGridTimerId = 0;
  1503. this._updateBoundaries();
  1504. var ids = this._profileSamples.ids;
  1505. var timestamps = this._profileSamples.timestamps;
  1506. var sizes = this._profileSamples.sizes;
  1507. var startTime = timestamps[0];
  1508. var totalTime = this._profileSamples.totalTime;
  1509. var timeLeft = startTime + totalTime * this._windowLeft;
  1510. var timeRight = startTime + totalTime * this._windowRight;
  1511. var minId = 0;
  1512. var maxId = ids[ids.length - 1] + 1;
  1513. var size = 0;
  1514. for (var i = 0; i < timestamps.length; ++i) {
  1515. if (!timestamps[i])
  1516. continue;
  1517. if (timestamps[i] > timeRight)
  1518. break;
  1519. maxId = ids[i];
  1520. if (timestamps[i] < timeLeft) {
  1521. minId = ids[i];
  1522. continue;
  1523. }
  1524. size += sizes[i];
  1525. }
  1526. this.dispatchEventToListeners(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, {minId: minId, maxId: maxId, size: size});
  1527. },
  1528. __proto__: WebInspector.View.prototype
  1529. }
  1530. /**
  1531. * @constructor
  1532. */
  1533. WebInspector.HeapTrackingOverviewGrid.SmoothScale = function()
  1534. {
  1535. this._lastUpdate = 0;
  1536. this._currentScale = 0.0;
  1537. }
  1538. WebInspector.HeapTrackingOverviewGrid.SmoothScale.prototype = {
  1539. /**
  1540. * @param {number} target
  1541. * @return {number}
  1542. */
  1543. nextScale: function(target) {
  1544. target = target || this._currentScale;
  1545. if (this._currentScale) {
  1546. var now = Date.now();
  1547. var timeDeltaMs = now - this._lastUpdate;
  1548. this._lastUpdate = now;
  1549. var maxChangePerSec = 20;
  1550. var maxChangePerDelta = Math.pow(maxChangePerSec, timeDeltaMs / 1000);
  1551. var scaleChange = target / this._currentScale;
  1552. this._currentScale *= Number.constrain(scaleChange, 1 / maxChangePerDelta, maxChangePerDelta);
  1553. } else
  1554. this._currentScale = target;
  1555. return this._currentScale;
  1556. }
  1557. }
  1558. /**
  1559. * @constructor
  1560. * @implements {WebInspector.TimelineGrid.Calculator}
  1561. */
  1562. WebInspector.HeapTrackingOverviewGrid.OverviewCalculator = function()
  1563. {
  1564. }
  1565. WebInspector.HeapTrackingOverviewGrid.OverviewCalculator.prototype = {
  1566. /**
  1567. * @param {WebInspector.HeapTrackingOverviewGrid} chart
  1568. */
  1569. _updateBoundaries: function(chart)
  1570. {
  1571. this._minimumBoundaries = 0;
  1572. this._maximumBoundaries = chart._profileSamples.totalTime;
  1573. this._xScaleFactor = chart._overviewContainer.clientWidth / this._maximumBoundaries;
  1574. },
  1575. /**
  1576. * @param {number} time
  1577. */
  1578. computePosition: function(time)
  1579. {
  1580. return (time - this._minimumBoundaries) * this._xScaleFactor;
  1581. },
  1582. formatTime: function(value)
  1583. {
  1584. return Number.secondsToString((value + this._minimumBoundaries) / 1000);
  1585. },
  1586. maximumBoundary: function()
  1587. {
  1588. return this._maximumBoundaries;
  1589. },
  1590. minimumBoundary: function()
  1591. {
  1592. return this._minimumBoundaries;
  1593. },
  1594. zeroTime: function()
  1595. {
  1596. return this._minimumBoundaries;
  1597. },
  1598. boundarySpan: function()
  1599. {
  1600. return this._maximumBoundaries - this._minimumBoundaries;
  1601. }
  1602. }