TimelinePanel.js 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613
  1. /*
  2. * Copyright (C) 2012 Google Inc. All rights reserved.
  3. * Copyright (C) 2012 Intel Inc. All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are
  7. * met:
  8. *
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * * Redistributions in binary form must reproduce the above
  12. * copyright notice, this list of conditions and the following disclaimer
  13. * in the documentation and/or other materials provided with the
  14. * distribution.
  15. * * Neither the name of Google Inc. nor the names of its
  16. * contributors may be used to endorse or promote products derived from
  17. * this software without specific prior written permission.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  23. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  24. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  25. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  29. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. */
  31. importScript("MemoryStatistics.js");
  32. importScript("DOMCountersGraph.js");
  33. importScript("TimelineModel.js");
  34. importScript("TimelineOverviewPane.js");
  35. importScript("TimelinePresentationModel.js");
  36. importScript("TimelineFrameController.js");
  37. /**
  38. * @constructor
  39. * @extends {WebInspector.Panel}
  40. */
  41. WebInspector.TimelinePanel = function()
  42. {
  43. WebInspector.Panel.call(this, "timeline");
  44. this.registerRequiredCSS("timelinePanel.css");
  45. this._model = new WebInspector.TimelineModel();
  46. this._presentationModel = new WebInspector.TimelinePresentationModel();
  47. this._overviewModeSetting = WebInspector.settings.createSetting("timelineOverviewMode", WebInspector.TimelineOverviewPane.Mode.Events);
  48. this._glueRecordsSetting = WebInspector.settings.createSetting("timelineGlueRecords", false);
  49. this._overviewPane = new WebInspector.TimelineOverviewPane(this._model);
  50. this._overviewPane.addEventListener(WebInspector.TimelineOverviewPane.Events.WindowChanged, this._invalidateAndScheduleRefresh.bind(this, false, true));
  51. this._overviewPane.addEventListener(WebInspector.TimelineOverviewPane.Events.ModeChanged, this._overviewModeChanged, this);
  52. this._overviewPane.show(this.element);
  53. this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false);
  54. this.createSidebarViewWithTree();
  55. this._containerElement = this.splitView.element;
  56. this._containerElement.tabIndex = 0;
  57. this._containerElement.id = "timeline-container";
  58. this._containerElement.addEventListener("scroll", this._onScroll.bind(this), false);
  59. this._timelineMemorySplitter = this.element.createChild("div");
  60. this._timelineMemorySplitter.id = "timeline-memory-splitter";
  61. WebInspector.installDragHandle(this._timelineMemorySplitter, this._startSplitterDragging.bind(this), this._splitterDragging.bind(this), this._endSplitterDragging.bind(this), "ns-resize");
  62. this._timelineMemorySplitter.addStyleClass("hidden");
  63. this._includeDomCounters = false;
  64. this._memoryStatistics = new WebInspector.DOMCountersGraph(this, this._model, this.splitView.sidebarWidth());
  65. this._includeDomCounters = true;
  66. WebInspector.settings.memoryCounterGraphsHeight = WebInspector.settings.createSetting("memoryCounterGraphsHeight", 150);
  67. var itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RECORDS"), {}, true);
  68. this.sidebarTree.appendChild(itemsTreeElement);
  69. this.sidebarTree.setFocusable(false);
  70. this._sidebarListElement = document.createElement("div");
  71. this.sidebarElement.appendChild(this._sidebarListElement);
  72. this._containerContentElement = this.splitView.mainElement;
  73. this._containerContentElement.id = "resources-container-content";
  74. this._timelineGrid = new WebInspector.TimelineGrid();
  75. this._itemsGraphsElement = this._timelineGrid.itemsGraphsElement;
  76. this._itemsGraphsElement.id = "timeline-graphs";
  77. this._containerContentElement.appendChild(this._timelineGrid.element);
  78. this._timelineGrid.gridHeaderElement.id = "timeline-grid-header";
  79. this._memoryStatistics.setMainTimelineGrid(this._timelineGrid);
  80. this.element.appendChild(this._timelineGrid.gridHeaderElement);
  81. this._topGapElement = document.createElement("div");
  82. this._topGapElement.className = "timeline-gap";
  83. this._itemsGraphsElement.appendChild(this._topGapElement);
  84. this._graphRowsElement = document.createElement("div");
  85. this._itemsGraphsElement.appendChild(this._graphRowsElement);
  86. this._bottomGapElement = document.createElement("div");
  87. this._bottomGapElement.className = "timeline-gap";
  88. this._itemsGraphsElement.appendChild(this._bottomGapElement);
  89. this._expandElements = document.createElement("div");
  90. this._expandElements.id = "orphan-expand-elements";
  91. this._itemsGraphsElement.appendChild(this._expandElements);
  92. this._calculator = new WebInspector.TimelineCalculator(this._model);
  93. this._createStatusBarItems();
  94. this._frameMode = false;
  95. this._boundariesAreValid = true;
  96. this._scrollTop = 0;
  97. this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
  98. this.element.addEventListener("mousemove", this._mouseMove.bind(this), false);
  99. this.element.addEventListener("mouseout", this._mouseOut.bind(this), false);
  100. // Short events filter is disabled by default.
  101. this._durationFilter = new WebInspector.TimelineIsLongFilter();
  102. this._expandOffset = 15;
  103. this._headerLineCount = 1;
  104. this._adjustHeaderHeight();
  105. this._mainThreadTasks = /** @type {!Array.<{startTime: number, endTime: number}>} */ ([]);
  106. this._cpuBarsElement = this._timelineGrid.gridHeaderElement.createChild("div", "timeline-cpu-bars");
  107. this._mainThreadMonitoringEnabled = WebInspector.settings.showCpuOnTimelineRuler.get();
  108. WebInspector.settings.showCpuOnTimelineRuler.addChangeListener(this._showCpuOnTimelineRulerChanged, this);
  109. this._createFileSelector();
  110. this._model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onTimelineEventRecorded, this);
  111. this._model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this);
  112. this._registerShortcuts();
  113. this._allRecordsCount = 0;
  114. this._presentationModel.addFilter(new WebInspector.TimelineWindowFilter(this._overviewPane));
  115. this._presentationModel.addFilter(new WebInspector.TimelineCategoryFilter());
  116. this._presentationModel.addFilter(this._durationFilter);
  117. }
  118. // Define row height, should be in sync with styles for timeline graphs.
  119. WebInspector.TimelinePanel.rowHeight = 18;
  120. WebInspector.TimelinePanel.durationFilterPresetsMs = [0, 1, 15];
  121. WebInspector.TimelinePanel.prototype = {
  122. _showCpuOnTimelineRulerChanged: function()
  123. {
  124. var mainThreadMonitoringEnabled = WebInspector.settings.showCpuOnTimelineRuler.get();
  125. if (this._mainThreadMonitoringEnabled !== mainThreadMonitoringEnabled) {
  126. this._mainThreadMonitoringEnabled = mainThreadMonitoringEnabled;
  127. this._refreshMainThreadBars();
  128. }
  129. },
  130. /**
  131. * @param {Event} event
  132. * @return {boolean}
  133. */
  134. _startSplitterDragging: function(event)
  135. {
  136. this._dragOffset = this._timelineMemorySplitter.offsetTop + 2 - event.pageY;
  137. return true;
  138. },
  139. /**
  140. * @param {Event} event
  141. */
  142. _splitterDragging: function(event)
  143. {
  144. var top = event.pageY + this._dragOffset
  145. this._setSplitterPosition(top);
  146. event.preventDefault();
  147. },
  148. /**
  149. * @param {Event} event
  150. */
  151. _endSplitterDragging: function(event)
  152. {
  153. delete this._dragOffset;
  154. this._memoryStatistics.show();
  155. WebInspector.settings.memoryCounterGraphsHeight.set(this.splitView.element.offsetHeight);
  156. },
  157. _setSplitterPosition: function(top)
  158. {
  159. const overviewHeight = 90;
  160. const sectionMinHeight = 100;
  161. top = Number.constrain(top, overviewHeight + sectionMinHeight, this.element.offsetHeight - sectionMinHeight);
  162. this.splitView.element.style.height = (top - overviewHeight) + "px";
  163. this._timelineMemorySplitter.style.top = (top - 2) + "px";
  164. this._memoryStatistics.setTopPosition(top);
  165. this._containerElementHeight = this._containerElement.clientHeight;
  166. this.onResize();
  167. },
  168. get calculator()
  169. {
  170. return this._calculator;
  171. },
  172. get statusBarItems()
  173. {
  174. return this._statusBarItems.select("element").concat([
  175. this._miscStatusBarItems
  176. ]);
  177. },
  178. defaultFocusedElement: function()
  179. {
  180. return this.element;
  181. },
  182. _createStatusBarItems: function()
  183. {
  184. this._statusBarItems = /** @type {!Array.<!WebInspector.StatusBarItem>} */ ([]);
  185. this.toggleTimelineButton = new WebInspector.StatusBarButton(WebInspector.UIString("Record"), "record-profile-status-bar-item");
  186. this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked, this);
  187. this._statusBarItems.push(this.toggleTimelineButton);
  188. this.clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item");
  189. this.clearButton.addEventListener("click", this._clearPanel, this);
  190. this._statusBarItems.push(this.clearButton);
  191. this.garbageCollectButton = new WebInspector.StatusBarButton(WebInspector.UIString("Collect Garbage"), "garbage-collect-status-bar-item");
  192. this.garbageCollectButton.addEventListener("click", this._garbageCollectButtonClicked, this);
  193. this._statusBarItems.push(this.garbageCollectButton);
  194. this._glueParentButton = new WebInspector.StatusBarButton(WebInspector.UIString("Glue asynchronous events to causes"), "glue-async-status-bar-item");
  195. this._glueParentButton.toggled = this._glueRecordsSetting.get();
  196. this._presentationModel.setGlueRecords(this._glueParentButton.toggled);
  197. this._glueParentButton.addEventListener("click", this._glueParentButtonClicked, this);
  198. this._statusBarItems.push(this._glueParentButton);
  199. this._durationFilterSelector = new WebInspector.StatusBarComboBox(this._durationFilterChanged.bind(this));
  200. for (var presetIndex = 0; presetIndex < WebInspector.TimelinePanel.durationFilterPresetsMs.length; ++presetIndex) {
  201. var durationMs = WebInspector.TimelinePanel.durationFilterPresetsMs[presetIndex];
  202. var option = document.createElement("option");
  203. if (!durationMs) {
  204. option.text = WebInspector.UIString("All");
  205. option.title = WebInspector.UIString("Show all records");
  206. } else {
  207. option.text = WebInspector.UIString("\u2265 %dms", durationMs);
  208. option.title = WebInspector.UIString("Hide records shorter than %dms", durationMs);
  209. }
  210. option._durationMs = durationMs;
  211. this._durationFilterSelector.addOption(option);
  212. this._durationFilterSelector.element.title = this._durationFilterSelector.selectedOption().title;
  213. }
  214. this._statusBarItems.push(this._durationFilterSelector);
  215. this._miscStatusBarItems = document.createElement("div");
  216. this._miscStatusBarItems.className = "status-bar-items timeline-misc-status-bar-items";
  217. this._statusBarFilters = this._miscStatusBarItems.createChild("div", "timeline-misc-status-bar-filters");
  218. var categories = WebInspector.TimelinePresentationModel.categories();
  219. for (var categoryName in categories) {
  220. var category = categories[categoryName];
  221. if (category.overviewStripGroupIndex < 0)
  222. continue;
  223. this._statusBarFilters.appendChild(this._createTimelineCategoryStatusBarCheckbox(category));
  224. }
  225. var statsContainer = this._statusBarFilters.createChild("div", "timeline-records-stats-container");
  226. this.recordsCounter = statsContainer.createChild("div", "timeline-records-stats");
  227. this.frameStatistics = statsContainer.createChild("div", "timeline-records-stats hidden");
  228. function getAnchor()
  229. {
  230. return this.frameStatistics;
  231. }
  232. this._frameStatisticsPopoverHelper = new WebInspector.PopoverHelper(this.frameStatistics, getAnchor.bind(this), this._showFrameStatistics.bind(this));
  233. },
  234. /**
  235. * @param {WebInspector.TimelineCategory} category
  236. */
  237. _createTimelineCategoryStatusBarCheckbox: function(category)
  238. {
  239. var labelContainer = document.createElement("div");
  240. labelContainer.addStyleClass("timeline-category-statusbar-item");
  241. labelContainer.addStyleClass("timeline-category-" + category.name);
  242. labelContainer.addStyleClass("status-bar-item");
  243. var label = labelContainer.createChild("label");
  244. var checkBorder = label.createChild("div", "timeline-category-checkbox");
  245. var checkElement = checkBorder.createChild("div", "timeline-category-checkbox-check timeline-category-checkbox-checked");
  246. checkElement.type = "checkbox";
  247. checkElement.checked = true;
  248. labelContainer.addEventListener("click", listener.bind(this), false);
  249. function listener(event)
  250. {
  251. var checked = !checkElement.checked;
  252. checkElement.checked = checked;
  253. category.hidden = !checked;
  254. checkElement.enableStyleClass("timeline-category-checkbox-checked", checkElement.checked);
  255. this._invalidateAndScheduleRefresh(true, true);
  256. }
  257. var typeElement = label.createChild("span", "type");
  258. typeElement.textContent = category.title;
  259. return labelContainer;
  260. },
  261. /**
  262. * @param {?WebInspector.ProgressIndicator} indicator
  263. */
  264. _setOperationInProgress: function(indicator)
  265. {
  266. this._operationInProgress = !!indicator;
  267. for (var i = 0; i < this._statusBarItems.length; ++i)
  268. this._statusBarItems[i].setEnabled(!this._operationInProgress);
  269. this._glueParentButton.setEnabled(!this._operationInProgress && !this._frameController);
  270. this._miscStatusBarItems.removeChildren();
  271. this._miscStatusBarItems.appendChild(indicator ? indicator.element : this._statusBarFilters);
  272. },
  273. _registerShortcuts: function()
  274. {
  275. this.registerShortcuts(WebInspector.TimelinePanelDescriptor.ShortcutKeys.StartStopRecording, this._toggleTimelineButtonClicked.bind(this));
  276. this.registerShortcuts(WebInspector.TimelinePanelDescriptor.ShortcutKeys.SaveToFile, this._saveToFile.bind(this));
  277. this.registerShortcuts(WebInspector.TimelinePanelDescriptor.ShortcutKeys.LoadFromFile, this._selectFileToLoad.bind(this));
  278. },
  279. _createFileSelector: function()
  280. {
  281. if (this._fileSelectorElement)
  282. this.element.removeChild(this._fileSelectorElement);
  283. this._fileSelectorElement = WebInspector.createFileSelectorElement(this._loadFromFile.bind(this));
  284. this.element.appendChild(this._fileSelectorElement);
  285. },
  286. _contextMenu: function(event)
  287. {
  288. var contextMenu = new WebInspector.ContextMenu(event);
  289. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save Timeline data\u2026" : "Save Timeline Data\u2026"), this._saveToFile.bind(this), this._operationInProgress);
  290. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Load Timeline data\u2026" : "Load Timeline Data\u2026"), this._selectFileToLoad.bind(this), this._operationInProgress);
  291. contextMenu.show();
  292. },
  293. /**
  294. * @param {Event=} event
  295. * @return {boolean}
  296. */
  297. _saveToFile: function(event)
  298. {
  299. if (this._operationInProgress)
  300. return true;
  301. this._model.saveToFile();
  302. return true;
  303. },
  304. /**
  305. * @param {Event=} event
  306. * @return {boolean}
  307. */
  308. _selectFileToLoad: function(event) {
  309. this._fileSelectorElement.click();
  310. return true;
  311. },
  312. /**
  313. * @param {!File} file
  314. */
  315. _loadFromFile: function(file)
  316. {
  317. var progressIndicator = this._prepareToLoadTimeline();
  318. if (!progressIndicator)
  319. return;
  320. this._model.loadFromFile(file, progressIndicator);
  321. this._createFileSelector();
  322. },
  323. /**
  324. * @param {string} url
  325. */
  326. loadFromURL: function(url)
  327. {
  328. var progressIndicator = this._prepareToLoadTimeline();
  329. if (!progressIndicator)
  330. return;
  331. this._model.loadFromURL(url, progressIndicator);
  332. },
  333. /**
  334. * @return {?WebInspector.ProgressIndicator}
  335. */
  336. _prepareToLoadTimeline: function()
  337. {
  338. if (this._operationInProgress)
  339. return null;
  340. if (this.toggleTimelineButton.toggled) {
  341. this.toggleTimelineButton.toggled = false;
  342. this._model.stopRecord();
  343. }
  344. var progressIndicator = new WebInspector.ProgressIndicator();
  345. progressIndicator.addEventListener(WebInspector.ProgressIndicator.Events.Done, this._setOperationInProgress.bind(this, null));
  346. this._setOperationInProgress(progressIndicator);
  347. return progressIndicator;
  348. },
  349. _rootRecord: function()
  350. {
  351. return this._presentationModel.rootRecord();
  352. },
  353. _updateRecordsCounter: function(recordsInWindowCount)
  354. {
  355. this.recordsCounter.textContent = WebInspector.UIString("%d of %d records shown", recordsInWindowCount, this._allRecordsCount);
  356. },
  357. _updateFrameStatistics: function(frames)
  358. {
  359. if (frames.length) {
  360. this._lastFrameStatistics = new WebInspector.FrameStatistics(frames);
  361. var details = WebInspector.UIString("avg: %s, \u03c3: %s",
  362. Number.secondsToString(this._lastFrameStatistics.average, true), Number.secondsToString(this._lastFrameStatistics.stddev, true));
  363. } else
  364. this._lastFrameStatistics = null;
  365. this.frameStatistics.textContent = WebInspector.UIString("%d of %d frames shown", frames.length, this._presentationModel.frames().length);
  366. if (details) {
  367. this.frameStatistics.appendChild(document.createTextNode(" ("));
  368. this.frameStatistics.createChild("span", "timeline-frames-stats").textContent = details;
  369. this.frameStatistics.appendChild(document.createTextNode(")"));
  370. }
  371. },
  372. /**
  373. * @param {Element} anchor
  374. * @param {WebInspector.Popover} popover
  375. */
  376. _showFrameStatistics: function(anchor, popover)
  377. {
  378. popover.show(WebInspector.TimelinePresentationModel.generatePopupContentForFrameStatistics(this._lastFrameStatistics), anchor);
  379. },
  380. _updateEventDividers: function()
  381. {
  382. this._timelineGrid.removeEventDividers();
  383. var clientWidth = this._graphRowsElementWidth;
  384. var dividers = [];
  385. var eventDividerRecords = this._presentationModel.eventDividerRecords();
  386. for (var i = 0; i < eventDividerRecords.length; ++i) {
  387. var record = eventDividerRecords[i];
  388. var positions = this._calculator.computeBarGraphWindowPosition(record);
  389. var dividerPosition = Math.round(positions.left);
  390. if (dividerPosition < 0 || dividerPosition >= clientWidth || dividers[dividerPosition])
  391. continue;
  392. var divider = WebInspector.TimelinePresentationModel.createEventDivider(record.type, record.title);
  393. divider.style.left = dividerPosition + "px";
  394. dividers[dividerPosition] = divider;
  395. }
  396. this._timelineGrid.addEventDividers(dividers);
  397. },
  398. _updateFrameBars: function(frames)
  399. {
  400. var clientWidth = this._graphRowsElementWidth;
  401. if (this._frameContainer)
  402. this._frameContainer.removeChildren();
  403. else {
  404. const frameContainerBorderWidth = 1;
  405. this._frameContainer = document.createElement("div");
  406. this._frameContainer.addStyleClass("fill");
  407. this._frameContainer.addStyleClass("timeline-frame-container");
  408. this._frameContainer.style.height = this._headerLineCount * WebInspector.TimelinePanel.rowHeight + frameContainerBorderWidth + "px";
  409. this._frameContainer.addEventListener("dblclick", this._onFrameDoubleClicked.bind(this), false);
  410. }
  411. var dividers = [ this._frameContainer ];
  412. for (var i = 0; i < frames.length; ++i) {
  413. var frame = frames[i];
  414. var frameStart = this._calculator.computePosition(frame.startTime);
  415. var frameEnd = this._calculator.computePosition(frame.endTime);
  416. var frameStrip = document.createElement("div");
  417. frameStrip.className = "timeline-frame-strip";
  418. var actualStart = Math.max(frameStart, 0);
  419. var width = frameEnd - actualStart;
  420. frameStrip.style.left = actualStart + "px";
  421. frameStrip.style.width = width + "px";
  422. frameStrip._frame = frame;
  423. const minWidthForFrameInfo = 60;
  424. if (width > minWidthForFrameInfo)
  425. frameStrip.textContent = Number.secondsToString(frame.endTime - frame.startTime, true);
  426. this._frameContainer.appendChild(frameStrip);
  427. if (actualStart > 0) {
  428. var frameMarker = WebInspector.TimelinePresentationModel.createEventDivider(WebInspector.TimelineModel.RecordType.BeginFrame);
  429. frameMarker.style.left = frameStart + "px";
  430. dividers.push(frameMarker);
  431. }
  432. }
  433. this._timelineGrid.addEventDividers(dividers);
  434. },
  435. _onFrameDoubleClicked: function(event)
  436. {
  437. var frameBar = event.target.enclosingNodeOrSelfWithClass("timeline-frame-strip");
  438. if (!frameBar)
  439. return;
  440. this._overviewPane.zoomToFrame(frameBar._frame);
  441. },
  442. _overviewModeChanged: function(event)
  443. {
  444. var mode = event.data;
  445. var shouldShowMemory = mode === WebInspector.TimelineOverviewPane.Mode.Memory;
  446. var frameMode = mode === WebInspector.TimelineOverviewPane.Mode.Frames;
  447. this._overviewModeSetting.set(mode);
  448. if (frameMode !== this._frameMode) {
  449. this._frameMode = frameMode;
  450. this._glueParentButton.setEnabled(!frameMode);
  451. this._presentationModel.setGlueRecords(this._glueParentButton.toggled && !frameMode);
  452. this._repopulateRecords();
  453. if (frameMode) {
  454. this.element.addStyleClass("timeline-frame-overview");
  455. this.recordsCounter.addStyleClass("hidden");
  456. this.frameStatistics.removeStyleClass("hidden");
  457. this._frameController = new WebInspector.TimelineFrameController(this._model, this._overviewPane, this._presentationModel);
  458. } else {
  459. this._frameController.dispose();
  460. this._frameController = null;
  461. this.element.removeStyleClass("timeline-frame-overview");
  462. this.recordsCounter.removeStyleClass("hidden");
  463. this.frameStatistics.addStyleClass("hidden");
  464. }
  465. }
  466. if (shouldShowMemory === this._memoryStatistics.visible())
  467. return;
  468. if (!shouldShowMemory) {
  469. this._timelineMemorySplitter.addStyleClass("hidden");
  470. this._memoryStatistics.hide();
  471. this.splitView.element.style.height = "auto";
  472. this.splitView.element.style.bottom = "0";
  473. this.onResize();
  474. } else {
  475. this._timelineMemorySplitter.removeStyleClass("hidden");
  476. this._memoryStatistics.show();
  477. this.splitView.element.style.bottom = "auto";
  478. this._setSplitterPosition(WebInspector.settings.memoryCounterGraphsHeight.get());
  479. }
  480. },
  481. /**
  482. * @return {boolean}
  483. */
  484. _toggleTimelineButtonClicked: function()
  485. {
  486. if (this._operationInProgress)
  487. return true;
  488. if (this.toggleTimelineButton.toggled) {
  489. this._model.stopRecord();
  490. this.toggleTimelineButton.title = WebInspector.UIString("Record");
  491. } else {
  492. this._model.startRecord(this._includeDomCounters);
  493. this.toggleTimelineButton.title = WebInspector.UIString("Stop");
  494. WebInspector.userMetrics.TimelineStarted.record();
  495. }
  496. this.toggleTimelineButton.toggled = !this.toggleTimelineButton.toggled;
  497. return true;
  498. },
  499. _durationFilterChanged: function()
  500. {
  501. var option = this._durationFilterSelector.selectedOption();
  502. var minimumRecordDuration = +option._durationMs / 1000.0;
  503. this._durationFilter.setMinimumRecordDuration(minimumRecordDuration);
  504. this._durationFilterSelector.element.title = option.title;
  505. this._invalidateAndScheduleRefresh(true, true);
  506. },
  507. _garbageCollectButtonClicked: function()
  508. {
  509. HeapProfilerAgent.collectGarbage();
  510. },
  511. _glueParentButtonClicked: function()
  512. {
  513. var newValue = !this._glueParentButton.toggled;
  514. this._glueParentButton.toggled = newValue;
  515. this._presentationModel.setGlueRecords(newValue);
  516. this._glueRecordsSetting.set(newValue);
  517. this._repopulateRecords();
  518. },
  519. _repopulateRecords: function()
  520. {
  521. this._resetPanel();
  522. this._automaticallySizeWindow = false;
  523. var records = this._model.records;
  524. for (var i = 0; i < records.length; ++i)
  525. this._innerAddRecordToTimeline(records[i]);
  526. this._invalidateAndScheduleRefresh(false, false);
  527. },
  528. _onTimelineEventRecorded: function(event)
  529. {
  530. if (this._innerAddRecordToTimeline(event.data))
  531. this._invalidateAndScheduleRefresh(false, false);
  532. },
  533. _innerAddRecordToTimeline: function(record)
  534. {
  535. if (record.type === WebInspector.TimelineModel.RecordType.Program) {
  536. this._mainThreadTasks.push({
  537. startTime: WebInspector.TimelineModel.startTimeInSeconds(record),
  538. endTime: WebInspector.TimelineModel.endTimeInSeconds(record)
  539. });
  540. }
  541. var records = this._presentationModel.addRecord(record);
  542. this._allRecordsCount += records.length;
  543. var hasVisibleRecords = false;
  544. var presentationModel = this._presentationModel;
  545. function checkVisible(record)
  546. {
  547. hasVisibleRecords |= presentationModel.isVisible(record);
  548. }
  549. WebInspector.TimelinePresentationModel.forAllRecords(records, checkVisible);
  550. function isAdoptedRecord(record)
  551. {
  552. return record.parent !== presentationModel.rootRecord;
  553. }
  554. // Tell caller update is necessary either if we added a visible record or if we re-parented a record.
  555. return hasVisibleRecords || records.some(isAdoptedRecord);
  556. },
  557. sidebarResized: function(event)
  558. {
  559. var width = event.data;
  560. this._resize(width);
  561. this._overviewPane.sidebarResized(width);
  562. this._memoryStatistics.setSidebarWidth(width);
  563. this._timelineGrid.gridHeaderElement.style.left = width + "px";
  564. },
  565. onResize: function()
  566. {
  567. this._resize(this.splitView.sidebarWidth());
  568. },
  569. /**
  570. * @param {number} sidebarWidth
  571. */
  572. _resize: function(sidebarWidth)
  573. {
  574. this._closeRecordDetails();
  575. this._graphRowsElementWidth = this._graphRowsElement.offsetWidth;
  576. this._containerElementHeight = this._containerElement.clientHeight;
  577. this._scheduleRefresh(false, true);
  578. var lastItemElement = this._statusBarItems[this._statusBarItems.length - 1].element;
  579. var minFloatingStatusBarItemsOffset = lastItemElement.totalOffsetLeft() + lastItemElement.offsetWidth;
  580. this._timelineGrid.gridHeaderElement.style.width = this._itemsGraphsElement.offsetWidth + "px";
  581. this._miscStatusBarItems.style.left = Math.max(minFloatingStatusBarItemsOffset, sidebarWidth) + "px";
  582. },
  583. _clearPanel: function()
  584. {
  585. this._model.reset();
  586. },
  587. _onRecordsCleared: function()
  588. {
  589. this._resetPanel();
  590. this._invalidateAndScheduleRefresh(true, true);
  591. },
  592. _resetPanel: function()
  593. {
  594. this._presentationModel.reset();
  595. this._boundariesAreValid = false;
  596. this._adjustScrollPosition(0);
  597. this._closeRecordDetails();
  598. this._allRecordsCount = 0;
  599. this._automaticallySizeWindow = true;
  600. this._mainThreadTasks = [];
  601. },
  602. elementsToRestoreScrollPositionsFor: function()
  603. {
  604. return [this._containerElement];
  605. },
  606. wasShown: function()
  607. {
  608. WebInspector.Panel.prototype.wasShown.call(this);
  609. if (!WebInspector.TimelinePanel._categoryStylesInitialized) {
  610. WebInspector.TimelinePanel._categoryStylesInitialized = true;
  611. this._injectCategoryStyles();
  612. }
  613. this._overviewPane.setMode(this._overviewModeSetting.get());
  614. this._refresh();
  615. },
  616. willHide: function()
  617. {
  618. this._closeRecordDetails();
  619. WebInspector.Panel.prototype.willHide.call(this);
  620. },
  621. _onScroll: function(event)
  622. {
  623. this._closeRecordDetails();
  624. this._scrollTop = this._containerElement.scrollTop;
  625. var dividersTop = Math.max(0, this._scrollTop);
  626. this._timelineGrid.setScrollAndDividerTop(this._scrollTop, dividersTop);
  627. this._scheduleRefresh(true, true);
  628. },
  629. /**
  630. * @param {boolean} preserveBoundaries
  631. * @param {boolean} userGesture
  632. */
  633. _invalidateAndScheduleRefresh: function(preserveBoundaries, userGesture)
  634. {
  635. this._presentationModel.invalidateFilteredRecords();
  636. delete this._searchResults;
  637. this._scheduleRefresh(preserveBoundaries, userGesture);
  638. },
  639. /**
  640. * @param {boolean} preserveBoundaries
  641. * @param {boolean} userGesture
  642. */
  643. _scheduleRefresh: function(preserveBoundaries, userGesture)
  644. {
  645. this._closeRecordDetails();
  646. this._boundariesAreValid &= preserveBoundaries;
  647. if (!this.isShowing())
  648. return;
  649. if (preserveBoundaries || userGesture)
  650. this._refresh();
  651. else {
  652. if (!this._refreshTimeout)
  653. this._refreshTimeout = setTimeout(this._refresh.bind(this), 300);
  654. }
  655. },
  656. _refresh: function()
  657. {
  658. if (this._refreshTimeout) {
  659. clearTimeout(this._refreshTimeout);
  660. delete this._refreshTimeout;
  661. }
  662. this._timelinePaddingLeft = this._expandOffset;
  663. this._calculator.setWindow(this._overviewPane.windowStartTime(), this._overviewPane.windowEndTime());
  664. this._calculator.setDisplayWindow(this._timelinePaddingLeft, this._graphRowsElementWidth);
  665. var recordsInWindowCount = this._refreshRecords();
  666. this._updateRecordsCounter(recordsInWindowCount);
  667. if (!this._boundariesAreValid) {
  668. this._updateEventDividers();
  669. var frames = this._frameController && this._presentationModel.filteredFrames(this._overviewPane.windowStartTime(), this._overviewPane.windowEndTime());
  670. if (frames) {
  671. this._updateFrameStatistics(frames);
  672. const maxFramesForFrameBars = 30;
  673. if (frames.length && frames.length < maxFramesForFrameBars) {
  674. this._timelineGrid.removeDividers();
  675. this._updateFrameBars(frames);
  676. } else
  677. this._timelineGrid.updateDividers(this._calculator);
  678. } else
  679. this._timelineGrid.updateDividers(this._calculator);
  680. if (this._mainThreadMonitoringEnabled)
  681. this._refreshMainThreadBars();
  682. }
  683. if (this._memoryStatistics.visible())
  684. this._memoryStatistics.refresh();
  685. this._boundariesAreValid = true;
  686. },
  687. revealRecordAt: function(time)
  688. {
  689. var recordToReveal;
  690. function findRecordToReveal(record)
  691. {
  692. if (record.containsTime(time)) {
  693. recordToReveal = record;
  694. return true;
  695. }
  696. // If there is no record containing the time than use the latest one before that time.
  697. if (!recordToReveal || record.endTime < time && recordToReveal.endTime < record.endTime)
  698. recordToReveal = record;
  699. return false;
  700. }
  701. WebInspector.TimelinePresentationModel.forAllRecords(this._presentationModel.rootRecord().children, null, findRecordToReveal);
  702. // The record ends before the window left bound so scroll to the top.
  703. if (!recordToReveal) {
  704. this._containerElement.scrollTop = 0;
  705. return;
  706. }
  707. this._revealRecord(recordToReveal);
  708. },
  709. _revealRecord: function(recordToReveal)
  710. {
  711. // Expand all ancestors.
  712. for (var parent = recordToReveal.parent; parent !== this._rootRecord(); parent = parent.parent) {
  713. if (!parent.collapsed)
  714. continue;
  715. this._presentationModel.invalidateFilteredRecords();
  716. parent.collapsed = false;
  717. }
  718. var recordsInWindow = this._presentationModel.filteredRecords();
  719. var index = recordsInWindow.indexOf(recordToReveal);
  720. this._recordToHighlight = recordToReveal;
  721. var oldScrollTop = this._containerElement.scrollTop;
  722. this._containerElement.scrollTop = index * WebInspector.TimelinePanel.rowHeight;
  723. // The record to highlight will be only kept for one refresh cycle, so make sure
  724. // refresh is only called once, either via scroll event handler or directly, if
  725. // we're not about to scroll.
  726. if (this._containerElement.scrollTop === oldScrollTop)
  727. this._refresh();
  728. },
  729. _refreshRecords: function()
  730. {
  731. var recordsInWindow = this._presentationModel.filteredRecords();
  732. // Calculate the visible area.
  733. var visibleTop = this._scrollTop;
  734. var visibleBottom = visibleTop + this._containerElementHeight;
  735. const rowHeight = WebInspector.TimelinePanel.rowHeight;
  736. // Convert visible area to visible indexes. Always include top-level record for a visible nested record.
  737. var startIndex = Math.max(0, Math.min(Math.floor(visibleTop / rowHeight) - this._headerLineCount, recordsInWindow.length - 1));
  738. var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight));
  739. var lastVisibleLine = Math.max(0, Math.floor(visibleBottom / rowHeight) - this._headerLineCount);
  740. if (this._automaticallySizeWindow && recordsInWindow.length > lastVisibleLine) {
  741. this._automaticallySizeWindow = false;
  742. // If we're at the top, always use real timeline start as a left window bound so that expansion arrow padding logic works.
  743. var windowStartTime = startIndex ? recordsInWindow[startIndex].startTime : this._model.minimumRecordTime();
  744. this._overviewPane.setWindowTimes(windowStartTime, recordsInWindow[Math.max(0, lastVisibleLine - 1)].endTime);
  745. recordsInWindow = this._presentationModel.filteredRecords();
  746. endIndex = Math.min(recordsInWindow.length, lastVisibleLine);
  747. }
  748. // Resize gaps first.
  749. this._topGapElement.style.height = (startIndex * rowHeight) + "px";
  750. this.sidebarTreeElement.style.height = ((startIndex + this._headerLineCount) * rowHeight) + "px";
  751. this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px";
  752. // Update visible rows.
  753. var listRowElement = this._sidebarListElement.firstChild;
  754. var width = this._graphRowsElementWidth;
  755. this._itemsGraphsElement.removeChild(this._graphRowsElement);
  756. var graphRowElement = this._graphRowsElement.firstChild;
  757. var scheduleRefreshCallback = this._invalidateAndScheduleRefresh.bind(this, true, true);
  758. this._itemsGraphsElement.removeChild(this._expandElements);
  759. this._expandElements.removeChildren();
  760. var highlightedRecord = this._recordToHighlight;
  761. delete this._recordToHighlight;
  762. var highlightedListRowElement;
  763. var highlightedGraphRowElement;
  764. for (var i = 0; i < endIndex; ++i) {
  765. var record = recordsInWindow[i];
  766. var isEven = !(i % 2);
  767. if (i < startIndex) {
  768. var lastChildIndex = i + record.visibleChildrenCount;
  769. if (lastChildIndex >= startIndex && lastChildIndex < endIndex) {
  770. var expandElement = new WebInspector.TimelineExpandableElement(this._expandElements);
  771. var positions = this._calculator.computeBarGraphWindowPosition(record);
  772. expandElement._update(record, i, positions.left - this._expandOffset, positions.width);
  773. }
  774. } else {
  775. if (!listRowElement) {
  776. listRowElement = new WebInspector.TimelineRecordListRow().element;
  777. this._sidebarListElement.appendChild(listRowElement);
  778. }
  779. if (!graphRowElement) {
  780. graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, scheduleRefreshCallback).element;
  781. this._graphRowsElement.appendChild(graphRowElement);
  782. }
  783. if (highlightedRecord === record) {
  784. highlightedListRowElement = listRowElement;
  785. highlightedGraphRowElement = graphRowElement;
  786. }
  787. listRowElement.row.update(record, isEven, visibleTop);
  788. graphRowElement.row.update(record, isEven, this._calculator, this._expandOffset, i);
  789. listRowElement = listRowElement.nextSibling;
  790. graphRowElement = graphRowElement.nextSibling;
  791. }
  792. }
  793. // Remove extra rows.
  794. while (listRowElement) {
  795. var nextElement = listRowElement.nextSibling;
  796. listRowElement.row.dispose();
  797. listRowElement = nextElement;
  798. }
  799. while (graphRowElement) {
  800. var nextElement = graphRowElement.nextSibling;
  801. graphRowElement.row.dispose();
  802. graphRowElement = nextElement;
  803. }
  804. this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement);
  805. this._itemsGraphsElement.appendChild(this._expandElements);
  806. this._adjustScrollPosition((recordsInWindow.length + this._headerLineCount) * rowHeight);
  807. this._updateSearchHighlight(false, true);
  808. if (highlightedListRowElement) {
  809. highlightedListRowElement.addStyleClass("highlighted-timeline-record");
  810. highlightedGraphRowElement.addStyleClass("highlighted-timeline-record");
  811. }
  812. return recordsInWindow.length;
  813. },
  814. _refreshMainThreadBars: function()
  815. {
  816. const barOffset = 3;
  817. const minGap = 3;
  818. var minWidth = WebInspector.TimelineCalculator._minWidth;
  819. var widthAdjustment = minWidth / 2;
  820. var width = this._graphRowsElementWidth;
  821. var boundarySpan = this._overviewPane.windowEndTime() - this._overviewPane.windowStartTime();
  822. var scale = boundarySpan / (width - minWidth - this._timelinePaddingLeft);
  823. var startTime = this._overviewPane.windowStartTime() - this._timelinePaddingLeft * scale;
  824. var endTime = startTime + width * scale;
  825. var tasks = this._mainThreadMonitoringEnabled ? this._mainThreadTasks : [];
  826. /**
  827. * @param {number} value
  828. * @param {{startTime: number, endTime: number}} task
  829. * @return {number}
  830. */
  831. function compareEndTime(value, task)
  832. {
  833. return value < task.endTime ? -1 : 1;
  834. }
  835. var taskIndex = insertionIndexForObjectInListSortedByFunction(startTime, tasks, compareEndTime);
  836. var container = this._cpuBarsElement;
  837. var element = container.firstChild;
  838. var lastElement;
  839. var lastLeft;
  840. var lastRight;
  841. for (; taskIndex < tasks.length; ++taskIndex) {
  842. var task = tasks[taskIndex];
  843. if (task.startTime > endTime)
  844. break;
  845. var left = Math.max(0, this._calculator.computePosition(task.startTime) + barOffset - widthAdjustment);
  846. var right = Math.min(width, this._calculator.computePosition(task.endTime) + barOffset + widthAdjustment);
  847. if (lastElement) {
  848. var gap = Math.floor(left) - Math.ceil(lastRight);
  849. if (gap < minGap) {
  850. lastRight = right;
  851. lastElement._tasksInfo.lastTaskIndex = taskIndex;
  852. continue;
  853. }
  854. lastElement.style.width = (lastRight - lastLeft) + "px";
  855. }
  856. if (!element)
  857. element = container.createChild("div", "timeline-graph-bar");
  858. element.style.left = left + "px";
  859. element._tasksInfo = {tasks: tasks, firstTaskIndex: taskIndex, lastTaskIndex: taskIndex};
  860. lastLeft = left;
  861. lastRight = right;
  862. lastElement = element;
  863. element = element.nextSibling;
  864. }
  865. if (lastElement)
  866. lastElement.style.width = (lastRight - lastLeft) + "px";
  867. while (element) {
  868. var nextElement = element.nextSibling;
  869. element._tasksInfo = null;
  870. container.removeChild(element);
  871. element = nextElement;
  872. }
  873. },
  874. _adjustHeaderHeight: function()
  875. {
  876. const headerBorderWidth = 1;
  877. const headerMargin = 2;
  878. var headerHeight = this._headerLineCount * WebInspector.TimelinePanel.rowHeight;
  879. this.sidebarElement.firstChild.style.height = headerHeight + "px";
  880. this._timelineGrid.dividersLabelBarElement.style.height = headerHeight + headerMargin + "px";
  881. this._itemsGraphsElement.style.top = headerHeight + headerBorderWidth + "px";
  882. },
  883. _adjustScrollPosition: function(totalHeight)
  884. {
  885. // Prevent the container from being scrolled off the end.
  886. if ((this._scrollTop + this._containerElementHeight) > totalHeight + 1)
  887. this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight);
  888. },
  889. _getPopoverAnchor: function(element)
  890. {
  891. return element.enclosingNodeOrSelfWithClass("timeline-graph-bar") ||
  892. element.enclosingNodeOrSelfWithClass("timeline-tree-item") ||
  893. element.enclosingNodeOrSelfWithClass("timeline-frame-strip");
  894. },
  895. _mouseOut: function(e)
  896. {
  897. this._hideQuadHighlight();
  898. },
  899. /**
  900. * @param {Event} e
  901. */
  902. _mouseMove: function(e)
  903. {
  904. var anchor = this._getPopoverAnchor(e.target);
  905. if (anchor && anchor.row && anchor.row._record.highlightQuad)
  906. this._highlightQuad(anchor.row._record.highlightQuad);
  907. else
  908. this._hideQuadHighlight();
  909. if (anchor && anchor._tasksInfo) {
  910. var offset = anchor.offsetLeft;
  911. this._timelineGrid.showCurtains(offset >= 0 ? offset : 0, anchor.offsetWidth);
  912. } else
  913. this._timelineGrid.hideCurtains();
  914. },
  915. /**
  916. * @param {Array.<number>} quad
  917. */
  918. _highlightQuad: function(quad)
  919. {
  920. if (this._highlightedQuad === quad)
  921. return;
  922. this._highlightedQuad = quad;
  923. DOMAgent.highlightQuad(quad, WebInspector.Color.PageHighlight.Content.toProtocolRGBA(), WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA());
  924. },
  925. _hideQuadHighlight: function()
  926. {
  927. if (this._highlightedQuad) {
  928. delete this._highlightedQuad;
  929. DOMAgent.hideHighlight();
  930. }
  931. },
  932. /**
  933. * @param {Element} anchor
  934. * @param {WebInspector.Popover} popover
  935. */
  936. _showPopover: function(anchor, popover)
  937. {
  938. if (anchor.hasStyleClass("timeline-frame-strip")) {
  939. var frame = anchor._frame;
  940. popover.show(WebInspector.TimelinePresentationModel.generatePopupContentForFrame(frame), anchor);
  941. } else {
  942. if (anchor.row && anchor.row._record)
  943. anchor.row._record.generatePopupContent(showCallback);
  944. else if (anchor._tasksInfo)
  945. popover.show(this._presentationModel.generateMainThreadBarPopupContent(anchor._tasksInfo), anchor, null, null, WebInspector.Popover.Orientation.Bottom);
  946. }
  947. function showCallback(popupContent)
  948. {
  949. popover.show(popupContent, anchor);
  950. }
  951. },
  952. _closeRecordDetails: function()
  953. {
  954. this._popoverHelper.hidePopover();
  955. },
  956. _injectCategoryStyles: function()
  957. {
  958. var style = document.createElement("style");
  959. var categories = WebInspector.TimelinePresentationModel.categories();
  960. style.textContent = Object.values(categories).map(WebInspector.TimelinePresentationModel.createStyleRuleForCategory).join("\n");
  961. document.head.appendChild(style);
  962. },
  963. jumpToNextSearchResult: function()
  964. {
  965. if (!this._searchResults || !this._searchResults.length)
  966. return;
  967. var index = this._selectedSearchResult ? this._searchResults.indexOf(this._selectedSearchResult) : -1;
  968. this._jumpToSearchResult(index + 1);
  969. },
  970. jumpToPreviousSearchResult: function()
  971. {
  972. if (!this._searchResults || !this._searchResults.length)
  973. return;
  974. var index = this._selectedSearchResult ? this._searchResults.indexOf(this._selectedSearchResult) : 0;
  975. this._jumpToSearchResult(index - 1);
  976. },
  977. _jumpToSearchResult: function(index)
  978. {
  979. this._selectSearchResult((index + this._searchResults.length) % this._searchResults.length);
  980. this._highlightSelectedSearchResult(true);
  981. },
  982. _selectSearchResult: function(index)
  983. {
  984. this._selectedSearchResult = this._searchResults[index];
  985. WebInspector.searchController.updateCurrentMatchIndex(index, this);
  986. },
  987. _highlightSelectedSearchResult: function(revealRecord)
  988. {
  989. this._clearHighlight();
  990. if (this._searchFilter)
  991. return;
  992. var record = this._selectedSearchResult;
  993. if (!record)
  994. return;
  995. for (var element = this._sidebarListElement.firstChild; element; element = element.nextSibling) {
  996. if (element.row._record === record) {
  997. element.row.highlight(this._searchRegExp, this._highlightDomChanges);
  998. return;
  999. }
  1000. }
  1001. if (revealRecord)
  1002. this._revealRecord(record);
  1003. },
  1004. _clearHighlight: function()
  1005. {
  1006. if (this._highlightDomChanges)
  1007. WebInspector.revertDomChanges(this._highlightDomChanges);
  1008. this._highlightDomChanges = [];
  1009. },
  1010. /**
  1011. * @param {boolean} revealRecord
  1012. * @param {boolean} shouldJump
  1013. */
  1014. _updateSearchHighlight: function(revealRecord, shouldJump)
  1015. {
  1016. if (this._searchFilter || !this._searchRegExp) {
  1017. this._clearHighlight();
  1018. return;
  1019. }
  1020. if (!this._searchResults)
  1021. this._updateSearchResults(shouldJump);
  1022. this._highlightSelectedSearchResult(revealRecord);
  1023. },
  1024. _updateSearchResults: function(shouldJump)
  1025. {
  1026. var searchRegExp = this._searchRegExp;
  1027. if (!searchRegExp)
  1028. return;
  1029. var matches = [];
  1030. var presentationModel = this._presentationModel;
  1031. function processRecord(record)
  1032. {
  1033. if (presentationModel.isVisible(record) && WebInspector.TimelineRecordListRow.testContentMatching(record, searchRegExp))
  1034. matches.push(record);
  1035. return false;
  1036. }
  1037. WebInspector.TimelinePresentationModel.forAllRecords(presentationModel.rootRecord().children, processRecord);
  1038. var matchesCount = matches.length;
  1039. if (matchesCount) {
  1040. this._searchResults = matches;
  1041. WebInspector.searchController.updateSearchMatchesCount(matchesCount, this);
  1042. var selectedIndex = matches.indexOf(this._selectedSearchResult);
  1043. if (shouldJump && selectedIndex === -1)
  1044. selectedIndex = 0;
  1045. this._selectSearchResult(selectedIndex);
  1046. } else {
  1047. WebInspector.searchController.updateSearchMatchesCount(0, this);
  1048. delete this._selectedSearchResult;
  1049. }
  1050. },
  1051. searchCanceled: function()
  1052. {
  1053. this._clearHighlight();
  1054. delete this._searchResults;
  1055. delete this._selectedSearchResult;
  1056. delete this._searchRegExp;
  1057. },
  1058. /**
  1059. * @return {boolean}
  1060. */
  1061. canFilter: function()
  1062. {
  1063. return true;
  1064. },
  1065. performFilter: function(searchQuery)
  1066. {
  1067. this._presentationModel.setSearchFilter(null);
  1068. delete this._searchFilter;
  1069. function cleanRecord(record)
  1070. {
  1071. delete record.clicked;
  1072. }
  1073. WebInspector.TimelinePresentationModel.forAllRecords(this._presentationModel.rootRecord().children, cleanRecord);
  1074. this.searchCanceled();
  1075. if (searchQuery) {
  1076. this._searchFilter = new WebInspector.TimelineSearchFilter(createPlainTextSearchRegex(searchQuery, "i"));
  1077. this._presentationModel.setSearchFilter(this._searchFilter);
  1078. }
  1079. this._invalidateAndScheduleRefresh(true, true);
  1080. },
  1081. /**
  1082. * @param {string} query
  1083. * @param {boolean} shouldJump
  1084. */
  1085. performSearch: function(query, shouldJump)
  1086. {
  1087. this._searchRegExp = createPlainTextSearchRegex(query, "i");
  1088. delete this._searchResults;
  1089. this._updateSearchHighlight(true, shouldJump);
  1090. },
  1091. __proto__: WebInspector.Panel.prototype
  1092. }
  1093. /**
  1094. * @constructor
  1095. * @param {WebInspector.TimelineModel} model
  1096. * @implements {WebInspector.TimelineGrid.Calculator}
  1097. */
  1098. WebInspector.TimelineCalculator = function(model)
  1099. {
  1100. this._model = model;
  1101. }
  1102. WebInspector.TimelineCalculator._minWidth = 5;
  1103. WebInspector.TimelineCalculator.prototype = {
  1104. /**
  1105. * @param {number} time
  1106. */
  1107. computePosition: function(time)
  1108. {
  1109. return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea + this.paddingLeft;
  1110. },
  1111. computeBarGraphPercentages: function(record)
  1112. {
  1113. var start = (record.startTime - this._minimumBoundary) / this.boundarySpan() * 100;
  1114. var end = (record.startTime + record.selfTime - this._minimumBoundary) / this.boundarySpan() * 100;
  1115. var endWithChildren = (record.lastChildEndTime - this._minimumBoundary) / this.boundarySpan() * 100;
  1116. var cpuWidth = record.coalesced ? endWithChildren - start : record.cpuTime / this.boundarySpan() * 100;
  1117. return {start: start, end: end, endWithChildren: endWithChildren, cpuWidth: cpuWidth};
  1118. },
  1119. computeBarGraphWindowPosition: function(record)
  1120. {
  1121. var percentages = this.computeBarGraphPercentages(record);
  1122. var widthAdjustment = 0;
  1123. var left = this.computePosition(record.startTime);
  1124. var width = (percentages.end - percentages.start) / 100 * this._workingArea;
  1125. if (width < WebInspector.TimelineCalculator._minWidth) {
  1126. widthAdjustment = WebInspector.TimelineCalculator._minWidth - width;
  1127. width = WebInspector.TimelineCalculator._minWidth;
  1128. }
  1129. var widthWithChildren = (percentages.endWithChildren - percentages.start) / 100 * this._workingArea + widthAdjustment;
  1130. var cpuWidth = percentages.cpuWidth / 100 * this._workingArea + widthAdjustment;
  1131. if (percentages.endWithChildren > percentages.end)
  1132. widthWithChildren += widthAdjustment;
  1133. return {left: left, width: width, widthWithChildren: widthWithChildren, cpuWidth: cpuWidth};
  1134. },
  1135. setWindow: function(minimumBoundary, maximumBoundary)
  1136. {
  1137. this._minimumBoundary = minimumBoundary;
  1138. this._maximumBoundary = maximumBoundary;
  1139. },
  1140. /**
  1141. * @param {number} paddingLeft
  1142. * @param {number} clientWidth
  1143. */
  1144. setDisplayWindow: function(paddingLeft, clientWidth)
  1145. {
  1146. this._workingArea = clientWidth - WebInspector.TimelineCalculator._minWidth - paddingLeft;
  1147. this.paddingLeft = paddingLeft;
  1148. },
  1149. formatTime: function(value)
  1150. {
  1151. return Number.secondsToString(value + this._minimumBoundary - this._model.minimumRecordTime());
  1152. },
  1153. maximumBoundary: function()
  1154. {
  1155. return this._maximumBoundary;
  1156. },
  1157. minimumBoundary: function()
  1158. {
  1159. return this._minimumBoundary;
  1160. },
  1161. zeroTime: function()
  1162. {
  1163. return this._model.minimumRecordTime();
  1164. },
  1165. boundarySpan: function()
  1166. {
  1167. return this._maximumBoundary - this._minimumBoundary;
  1168. }
  1169. }
  1170. /**
  1171. * @constructor
  1172. */
  1173. WebInspector.TimelineRecordListRow = function()
  1174. {
  1175. this.element = document.createElement("div");
  1176. this.element.row = this;
  1177. this.element.style.cursor = "pointer";
  1178. var iconElement = document.createElement("span");
  1179. iconElement.className = "timeline-tree-icon";
  1180. this.element.appendChild(iconElement);
  1181. this._typeElement = document.createElement("span");
  1182. this._typeElement.className = "type";
  1183. this.element.appendChild(this._typeElement);
  1184. var separatorElement = document.createElement("span");
  1185. separatorElement.className = "separator";
  1186. separatorElement.textContent = " ";
  1187. this._dataElement = document.createElement("span");
  1188. this._dataElement.className = "data dimmed";
  1189. this.element.appendChild(separatorElement);
  1190. this.element.appendChild(this._dataElement);
  1191. }
  1192. WebInspector.TimelineRecordListRow.prototype = {
  1193. update: function(record, isEven, offset)
  1194. {
  1195. this._record = record;
  1196. this._offset = offset;
  1197. this.element.className = "timeline-tree-item timeline-category-" + record.category.name;
  1198. if (isEven)
  1199. this.element.addStyleClass("even");
  1200. if (record.hasWarning)
  1201. this.element.addStyleClass("warning");
  1202. else if (record.childHasWarning)
  1203. this.element.addStyleClass("child-warning");
  1204. if (record.isBackground)
  1205. this.element.addStyleClass("background");
  1206. this._typeElement.textContent = record.title;
  1207. if (this._dataElement.firstChild)
  1208. this._dataElement.removeChildren();
  1209. if (record.detailsNode())
  1210. this._dataElement.appendChild(record.detailsNode());
  1211. },
  1212. highlight: function(regExp, domChanges)
  1213. {
  1214. var matchInfo = this.element.textContent.match(regExp);
  1215. if (matchInfo)
  1216. WebInspector.highlightSearchResult(this.element, matchInfo.index, matchInfo[0].length, domChanges);
  1217. },
  1218. dispose: function()
  1219. {
  1220. this.element.remove();
  1221. }
  1222. }
  1223. /**
  1224. * @param {!WebInspector.TimelinePresentationModel.Record} record
  1225. * @param {!RegExp} regExp
  1226. */
  1227. WebInspector.TimelineRecordListRow.testContentMatching = function(record, regExp)
  1228. {
  1229. var toSearchText = record.title;
  1230. if (record.detailsNode())
  1231. toSearchText += " " + record.detailsNode().textContent;
  1232. return regExp.test(toSearchText);
  1233. }
  1234. /**
  1235. * @constructor
  1236. */
  1237. WebInspector.TimelineRecordGraphRow = function(graphContainer, scheduleRefresh)
  1238. {
  1239. this.element = document.createElement("div");
  1240. this.element.row = this;
  1241. this._barAreaElement = document.createElement("div");
  1242. this._barAreaElement.className = "timeline-graph-bar-area";
  1243. this.element.appendChild(this._barAreaElement);
  1244. this._barWithChildrenElement = document.createElement("div");
  1245. this._barWithChildrenElement.className = "timeline-graph-bar with-children";
  1246. this._barWithChildrenElement.row = this;
  1247. this._barAreaElement.appendChild(this._barWithChildrenElement);
  1248. this._barCpuElement = document.createElement("div");
  1249. this._barCpuElement.className = "timeline-graph-bar cpu"
  1250. this._barCpuElement.row = this;
  1251. this._barAreaElement.appendChild(this._barCpuElement);
  1252. this._barElement = document.createElement("div");
  1253. this._barElement.className = "timeline-graph-bar";
  1254. this._barElement.row = this;
  1255. this._barAreaElement.appendChild(this._barElement);
  1256. this._expandElement = new WebInspector.TimelineExpandableElement(graphContainer);
  1257. this._expandElement._element.addEventListener("click", this._onClick.bind(this));
  1258. this._scheduleRefresh = scheduleRefresh;
  1259. }
  1260. WebInspector.TimelineRecordGraphRow.prototype = {
  1261. update: function(record, isEven, calculator, expandOffset, index)
  1262. {
  1263. this._record = record;
  1264. this.element.className = "timeline-graph-side timeline-category-" + record.category.name;
  1265. if (isEven)
  1266. this.element.addStyleClass("even");
  1267. if (record.isBackground)
  1268. this.element.addStyleClass("background");
  1269. var barPosition = calculator.computeBarGraphWindowPosition(record);
  1270. this._barWithChildrenElement.style.left = barPosition.left + "px";
  1271. this._barWithChildrenElement.style.width = barPosition.widthWithChildren + "px";
  1272. this._barElement.style.left = barPosition.left + "px";
  1273. this._barElement.style.width = barPosition.width + "px";
  1274. this._barCpuElement.style.left = barPosition.left + "px";
  1275. this._barCpuElement.style.width = barPosition.cpuWidth + "px";
  1276. this._expandElement._update(record, index, barPosition.left - expandOffset, barPosition.width);
  1277. },
  1278. _onClick: function(event)
  1279. {
  1280. this._record.collapsed = !this._record.collapsed;
  1281. this._record.clicked = true;
  1282. this._scheduleRefresh(false, true);
  1283. },
  1284. dispose: function()
  1285. {
  1286. this.element.remove();
  1287. this._expandElement._dispose();
  1288. }
  1289. }
  1290. /**
  1291. * @constructor
  1292. */
  1293. WebInspector.TimelineExpandableElement = function(container)
  1294. {
  1295. this._element = container.createChild("div", "timeline-expandable");
  1296. this._element.createChild("div", "timeline-expandable-left");
  1297. this._element.createChild("div", "timeline-expandable-arrow");
  1298. }
  1299. WebInspector.TimelineExpandableElement.prototype = {
  1300. _update: function(record, index, left, width)
  1301. {
  1302. const rowHeight = WebInspector.TimelinePanel.rowHeight;
  1303. if (record.visibleChildrenCount || record.expandable) {
  1304. this._element.style.top = index * rowHeight + "px";
  1305. this._element.style.left = left + "px";
  1306. this._element.style.width = Math.max(12, width + 25) + "px";
  1307. if (!record.collapsed) {
  1308. this._element.style.height = (record.visibleChildrenCount + 1) * rowHeight + "px";
  1309. this._element.addStyleClass("timeline-expandable-expanded");
  1310. this._element.removeStyleClass("timeline-expandable-collapsed");
  1311. } else {
  1312. this._element.style.height = rowHeight + "px";
  1313. this._element.addStyleClass("timeline-expandable-collapsed");
  1314. this._element.removeStyleClass("timeline-expandable-expanded");
  1315. }
  1316. this._element.removeStyleClass("hidden");
  1317. } else
  1318. this._element.addStyleClass("hidden");
  1319. },
  1320. _dispose: function()
  1321. {
  1322. this._element.remove();
  1323. }
  1324. }
  1325. /**
  1326. * @constructor
  1327. * @implements {WebInspector.TimelinePresentationModel.Filter}
  1328. */
  1329. WebInspector.TimelineCategoryFilter = function()
  1330. {
  1331. }
  1332. WebInspector.TimelineCategoryFilter.prototype = {
  1333. /**
  1334. * @param {!WebInspector.TimelinePresentationModel.Record} record
  1335. * @return {boolean}
  1336. */
  1337. accept: function(record)
  1338. {
  1339. return !record.category.hidden && record.type !== WebInspector.TimelineModel.RecordType.BeginFrame;
  1340. }
  1341. }
  1342. /**
  1343. * @constructor
  1344. * @implements {WebInspector.TimelinePresentationModel.Filter}
  1345. */
  1346. WebInspector.TimelineIsLongFilter = function()
  1347. {
  1348. this._minimumRecordDuration = 0;
  1349. }
  1350. WebInspector.TimelineIsLongFilter.prototype = {
  1351. /**
  1352. * @param {number} value
  1353. */
  1354. setMinimumRecordDuration: function(value)
  1355. {
  1356. this._minimumRecordDuration = value;
  1357. },
  1358. /**
  1359. * @param {!WebInspector.TimelinePresentationModel.Record} record
  1360. * @return {boolean}
  1361. */
  1362. accept: function(record)
  1363. {
  1364. return this._minimumRecordDuration ? ((record.lastChildEndTime - record.startTime) >= this._minimumRecordDuration) : true;
  1365. }
  1366. }
  1367. /**
  1368. * @param {!RegExp} regExp
  1369. * @constructor
  1370. * @implements {WebInspector.TimelinePresentationModel.Filter}
  1371. */
  1372. WebInspector.TimelineSearchFilter = function(regExp)
  1373. {
  1374. this._regExp = regExp;
  1375. }
  1376. WebInspector.TimelineSearchFilter.prototype = {
  1377. /**
  1378. * @param {!WebInspector.TimelinePresentationModel.Record} record
  1379. * @return {boolean}
  1380. */
  1381. accept: function(record)
  1382. {
  1383. return WebInspector.TimelineRecordListRow.testContentMatching(record, this._regExp);
  1384. }
  1385. }