TimelinePresentationModel.js 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591
  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. /**
  32. * @constructor
  33. * @extends {WebInspector.Object}
  34. */
  35. WebInspector.TimelinePresentationModel = function()
  36. {
  37. this._linkifier = new WebInspector.Linkifier();
  38. this._glueRecords = false;
  39. this._filters = [];
  40. this.reset();
  41. }
  42. WebInspector.TimelinePresentationModel.categories = function()
  43. {
  44. if (WebInspector.TimelinePresentationModel._categories)
  45. return WebInspector.TimelinePresentationModel._categories;
  46. WebInspector.TimelinePresentationModel._categories = {
  47. loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), 0, "#5A8BCC", "#8EB6E9", "#70A2E3"),
  48. scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), 1, "#D8AA34", "#F3D07A", "#F1C453"),
  49. rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), 2, "#8266CC", "#AF9AEB", "#9A7EE6"),
  50. painting: new WebInspector.TimelineCategory("painting", WebInspector.UIString("Painting"), 2, "#5FA050", "#8DC286", "#71B363"),
  51. other: new WebInspector.TimelineCategory("other", WebInspector.UIString("Other"), -1, "#BBBBBB", "#DDDDDD", "#DDDDDD")
  52. };
  53. return WebInspector.TimelinePresentationModel._categories;
  54. };
  55. /**
  56. * @return {!Object.<string, {title: string, category}>}
  57. */
  58. WebInspector.TimelinePresentationModel._initRecordStyles = function()
  59. {
  60. if (WebInspector.TimelinePresentationModel._recordStylesMap)
  61. return WebInspector.TimelinePresentationModel._recordStylesMap;
  62. var recordTypes = WebInspector.TimelineModel.RecordType;
  63. var categories = WebInspector.TimelinePresentationModel.categories();
  64. var recordStyles = {};
  65. recordStyles[recordTypes.Root] = { title: "#root", category: categories["loading"] };
  66. recordStyles[recordTypes.Program] = { title: WebInspector.UIString("Other"), category: categories["other"] };
  67. recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: categories["scripting"] };
  68. recordStyles[recordTypes.BeginFrame] = { title: WebInspector.UIString("Frame Start"), category: categories["rendering"] };
  69. recordStyles[recordTypes.ScheduleStyleRecalculation] = { title: WebInspector.UIString("Schedule Style Recalculation"), category: categories["rendering"] };
  70. recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: categories["rendering"] };
  71. recordStyles[recordTypes.InvalidateLayout] = { title: WebInspector.UIString("Invalidate Layout"), category: categories["rendering"] };
  72. recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: categories["rendering"] };
  73. recordStyles[recordTypes.PaintSetup] = { title: WebInspector.UIString("Paint Setup"), category: categories["painting"] };
  74. recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: categories["painting"] };
  75. recordStyles[recordTypes.Rasterize] = { title: WebInspector.UIString("Rasterize"), category: categories["painting"] };
  76. recordStyles[recordTypes.ScrollLayer] = { title: WebInspector.UIString("Scroll"), category: categories["rendering"] };
  77. recordStyles[recordTypes.DecodeImage] = { title: WebInspector.UIString("Image Decode"), category: categories["painting"] };
  78. recordStyles[recordTypes.ResizeImage] = { title: WebInspector.UIString("Image Resize"), category: categories["painting"] };
  79. recordStyles[recordTypes.CompositeLayers] = { title: WebInspector.UIString("Composite Layers"), category: categories["painting"] };
  80. recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse HTML"), category: categories["loading"] };
  81. recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: categories["scripting"] };
  82. recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: categories["scripting"] };
  83. recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: categories["scripting"] };
  84. recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: categories["scripting"] };
  85. recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: categories["scripting"] };
  86. recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: categories["scripting"] };
  87. recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: categories["loading"] };
  88. recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: categories["loading"] };
  89. recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: categories["loading"] };
  90. recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: categories["scripting"] };
  91. recordStyles[recordTypes.ResourceReceivedData] = { title: WebInspector.UIString("Receive Data"), category: categories["loading"] };
  92. recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: categories["scripting"] };
  93. recordStyles[recordTypes.MarkDOMContent] = { title: WebInspector.UIString("DOMContentLoaded event"), category: categories["scripting"] };
  94. recordStyles[recordTypes.MarkLoad] = { title: WebInspector.UIString("Load event"), category: categories["scripting"] };
  95. recordStyles[recordTypes.TimeStamp] = { title: WebInspector.UIString("Stamp"), category: categories["scripting"] };
  96. recordStyles[recordTypes.Time] = { title: WebInspector.UIString("Time"), category: categories["scripting"] };
  97. recordStyles[recordTypes.TimeEnd] = { title: WebInspector.UIString("Time End"), category: categories["scripting"] };
  98. recordStyles[recordTypes.ScheduleResourceRequest] = { title: WebInspector.UIString("Schedule Request"), category: categories["loading"] };
  99. recordStyles[recordTypes.RequestAnimationFrame] = { title: WebInspector.UIString("Request Animation Frame"), category: categories["scripting"] };
  100. recordStyles[recordTypes.CancelAnimationFrame] = { title: WebInspector.UIString("Cancel Animation Frame"), category: categories["scripting"] };
  101. recordStyles[recordTypes.FireAnimationFrame] = { title: WebInspector.UIString("Animation Frame Fired"), category: categories["scripting"] };
  102. recordStyles[recordTypes.WebSocketCreate] = { title: WebInspector.UIString("Create WebSocket"), category: categories["scripting"] };
  103. recordStyles[recordTypes.WebSocketSendHandshakeRequest] = { title: WebInspector.UIString("Send WebSocket Handshake"), category: categories["scripting"] };
  104. recordStyles[recordTypes.WebSocketReceiveHandshakeResponse] = { title: WebInspector.UIString("Receive WebSocket Handshake"), category: categories["scripting"] };
  105. recordStyles[recordTypes.WebSocketDestroy] = { title: WebInspector.UIString("Destroy WebSocket"), category: categories["scripting"] };
  106. WebInspector.TimelinePresentationModel._recordStylesMap = recordStyles;
  107. return recordStyles;
  108. }
  109. /**
  110. * @param {Object} record
  111. */
  112. WebInspector.TimelinePresentationModel.recordStyle = function(record)
  113. {
  114. var recordStyles = WebInspector.TimelinePresentationModel._initRecordStyles();
  115. var result = recordStyles[record.type];
  116. if (!result) {
  117. result = {
  118. title: WebInspector.UIString("Unknown: %s", record.type),
  119. category: WebInspector.TimelinePresentationModel.categories()["other"]
  120. };
  121. recordStyles[record.type] = result;
  122. }
  123. return result;
  124. }
  125. WebInspector.TimelinePresentationModel.categoryForRecord = function(record)
  126. {
  127. return WebInspector.TimelinePresentationModel.recordStyle(record).category;
  128. }
  129. WebInspector.TimelinePresentationModel.isEventDivider = function(record)
  130. {
  131. var recordTypes = WebInspector.TimelineModel.RecordType;
  132. if (record.type === recordTypes.TimeStamp)
  133. return true;
  134. if (record.type === recordTypes.MarkDOMContent || record.type === recordTypes.MarkLoad) {
  135. if (record.data && ((typeof record.data.isMainFrame) === "boolean"))
  136. return record.data.isMainFrame;
  137. }
  138. return false;
  139. }
  140. /**
  141. * @param {Array} recordsArray
  142. * @param {?function(*)} preOrderCallback
  143. * @param {function(*)=} postOrderCallback
  144. */
  145. WebInspector.TimelinePresentationModel.forAllRecords = function(recordsArray, preOrderCallback, postOrderCallback)
  146. {
  147. if (!recordsArray)
  148. return;
  149. var stack = [{array: recordsArray, index: 0}];
  150. while (stack.length) {
  151. var entry = stack[stack.length - 1];
  152. var records = entry.array;
  153. if (entry.index < records.length) {
  154. var record = records[entry.index];
  155. if (preOrderCallback && preOrderCallback(record))
  156. return;
  157. if (record.children)
  158. stack.push({array: record.children, index: 0, record: record});
  159. else if (postOrderCallback && postOrderCallback(record))
  160. return;
  161. ++entry.index;
  162. } else {
  163. if (entry.record && postOrderCallback && postOrderCallback(entry.record))
  164. return;
  165. stack.pop();
  166. }
  167. }
  168. }
  169. /**
  170. * @param {string=} recordType
  171. * @return {boolean}
  172. */
  173. WebInspector.TimelinePresentationModel.needsPreviewElement = function(recordType)
  174. {
  175. if (!recordType)
  176. return false;
  177. const recordTypes = WebInspector.TimelineModel.RecordType;
  178. switch (recordType) {
  179. case recordTypes.ScheduleResourceRequest:
  180. case recordTypes.ResourceSendRequest:
  181. case recordTypes.ResourceReceiveResponse:
  182. case recordTypes.ResourceReceivedData:
  183. case recordTypes.ResourceFinish:
  184. return true;
  185. default:
  186. return false;
  187. }
  188. }
  189. /**
  190. * @param {string} recordType
  191. * @param {string=} title
  192. */
  193. WebInspector.TimelinePresentationModel.createEventDivider = function(recordType, title)
  194. {
  195. var eventDivider = document.createElement("div");
  196. eventDivider.className = "resources-event-divider";
  197. var recordTypes = WebInspector.TimelineModel.RecordType;
  198. if (recordType === recordTypes.MarkDOMContent)
  199. eventDivider.className += " resources-blue-divider";
  200. else if (recordType === recordTypes.MarkLoad)
  201. eventDivider.className += " resources-red-divider";
  202. else if (recordType === recordTypes.TimeStamp)
  203. eventDivider.className += " resources-orange-divider";
  204. else if (recordType === recordTypes.BeginFrame)
  205. eventDivider.className += " timeline-frame-divider";
  206. if (title)
  207. eventDivider.title = title;
  208. return eventDivider;
  209. }
  210. WebInspector.TimelinePresentationModel._hiddenRecords = { }
  211. WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkDOMContent] = 1;
  212. WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkLoad] = 1;
  213. WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation] = 1;
  214. WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.InvalidateLayout] = 1;
  215. WebInspector.TimelinePresentationModel.prototype = {
  216. /**
  217. * @param {!WebInspector.TimelinePresentationModel.Filter} filter
  218. */
  219. addFilter: function(filter)
  220. {
  221. this._filters.push(filter);
  222. },
  223. /**
  224. * @param {?WebInspector.TimelinePresentationModel.Filter} filter
  225. */
  226. setSearchFilter: function(filter)
  227. {
  228. this._searchFilter = filter;
  229. },
  230. rootRecord: function()
  231. {
  232. return this._rootRecord;
  233. },
  234. frames: function()
  235. {
  236. return this._frames;
  237. },
  238. reset: function()
  239. {
  240. this._linkifier.reset();
  241. this._rootRecord = new WebInspector.TimelinePresentationModel.Record(this, { type: WebInspector.TimelineModel.RecordType.Root }, null, null, null, false);
  242. this._sendRequestRecords = {};
  243. this._scheduledResourceRequests = {};
  244. this._timerRecords = {};
  245. this._requestAnimationFrameRecords = {};
  246. this._eventDividerRecords = [];
  247. this._timeRecords = {};
  248. this._timeRecordStack = [];
  249. this._frames = [];
  250. this._minimumRecordTime = -1;
  251. this._layoutInvalidateStack = {};
  252. this._lastScheduleStyleRecalculation = {};
  253. this._webSocketCreateRecords = {};
  254. this._coalescingBuckets = {};
  255. },
  256. addFrame: function(frame)
  257. {
  258. this._frames.push(frame);
  259. },
  260. addRecord: function(record)
  261. {
  262. if (this._minimumRecordTime === -1 || record.startTime < this._minimumRecordTime)
  263. this._minimumRecordTime = WebInspector.TimelineModel.startTimeInSeconds(record);
  264. var records;
  265. if (record.type === WebInspector.TimelineModel.RecordType.Program)
  266. records = record.children;
  267. else
  268. records = [record];
  269. var formattedRecords = [];
  270. var recordsCount = records.length;
  271. for (var i = 0; i < recordsCount; ++i)
  272. formattedRecords.push(this._innerAddRecord(records[i], this._rootRecord));
  273. return formattedRecords;
  274. },
  275. _innerAddRecord: function(record, parentRecord)
  276. {
  277. const recordTypes = WebInspector.TimelineModel.RecordType;
  278. var isHiddenRecord = record.type in WebInspector.TimelinePresentationModel._hiddenRecords;
  279. var origin;
  280. var coalescingBucket;
  281. if (!isHiddenRecord) {
  282. var newParentRecord = this._findParentRecord(record);
  283. if (newParentRecord) {
  284. origin = parentRecord;
  285. parentRecord = newParentRecord;
  286. }
  287. // On main thread, only coalesce if the last event is of same type.
  288. if (parentRecord === this._rootRecord)
  289. coalescingBucket = record.thread ? record.type : "mainThread";
  290. var coalescedRecord = this._findCoalescedParent(record, parentRecord, coalescingBucket);
  291. if (coalescedRecord) {
  292. if (!origin)
  293. origin = parentRecord;
  294. parentRecord = coalescedRecord;
  295. }
  296. }
  297. var children = record.children;
  298. var scriptDetails;
  299. if (record.data && record.data["scriptName"]) {
  300. scriptDetails = {
  301. scriptName: record.data["scriptName"],
  302. scriptLine: record.data["scriptLine"]
  303. }
  304. };
  305. if ((record.type === recordTypes.TimerFire || record.type === recordTypes.FireAnimationFrame) && children && children.length) {
  306. var childRecord = children[0];
  307. if (childRecord.type === recordTypes.FunctionCall) {
  308. scriptDetails = {
  309. scriptName: childRecord.data["scriptName"],
  310. scriptLine: childRecord.data["scriptLine"]
  311. };
  312. children = childRecord.children.concat(children.slice(1));
  313. }
  314. }
  315. var formattedRecord = new WebInspector.TimelinePresentationModel.Record(this, record, parentRecord, origin, scriptDetails, isHiddenRecord);
  316. if (WebInspector.TimelinePresentationModel.isEventDivider(formattedRecord))
  317. this._eventDividerRecords.push(formattedRecord);
  318. if (isHiddenRecord)
  319. return formattedRecord;
  320. formattedRecord.collapsed = parentRecord === this._rootRecord;
  321. if (coalescingBucket)
  322. this._coalescingBuckets[coalescingBucket] = formattedRecord;
  323. var childrenCount = children ? children.length : 0;
  324. for (var i = 0; i < childrenCount; ++i)
  325. this._innerAddRecord(children[i], formattedRecord);
  326. formattedRecord.calculateAggregatedStats();
  327. if (origin)
  328. this._updateAncestorStats(formattedRecord);
  329. if (parentRecord.coalesced && parentRecord.startTime > formattedRecord.startTime)
  330. parentRecord._record.startTime = record.startTime;
  331. origin = formattedRecord.origin();
  332. if (!origin.isRoot() && !origin.coalesced)
  333. origin.selfTime -= formattedRecord.endTime - formattedRecord.startTime;
  334. return formattedRecord;
  335. },
  336. /**
  337. * @param {WebInspector.TimelinePresentationModel.Record} record
  338. */
  339. _updateAncestorStats: function(record)
  340. {
  341. var lastChildEndTime = record.lastChildEndTime;
  342. var aggregatedStats = record.aggregatedStats;
  343. for (var currentRecord = record.parent; currentRecord && !currentRecord.isRoot(); currentRecord = currentRecord.parent) {
  344. currentRecord._cpuTime += record._cpuTime;
  345. if (currentRecord.lastChildEndTime < lastChildEndTime)
  346. currentRecord.lastChildEndTime = lastChildEndTime;
  347. for (var category in aggregatedStats)
  348. currentRecord.aggregatedStats[category] += aggregatedStats[category];
  349. }
  350. },
  351. /**
  352. * @param {Object} record
  353. * @param {Object} newParent
  354. * @param {String} bucket
  355. * @return {WebInspector.TimelinePresentationModel.Record?}
  356. */
  357. _findCoalescedParent: function(record, newParent, bucket)
  358. {
  359. const coalescingThresholdSeconds = 0.005;
  360. var lastRecord = bucket ? this._coalescingBuckets[bucket] : newParent.children.peekLast();
  361. if (lastRecord && lastRecord.coalesced)
  362. lastRecord = lastRecord.children.peekLast();
  363. var startTime = WebInspector.TimelineModel.startTimeInSeconds(record);
  364. var endTime = WebInspector.TimelineModel.endTimeInSeconds(record);
  365. if (!lastRecord)
  366. return null;
  367. if (lastRecord.type !== record.type)
  368. return null;
  369. if (lastRecord.endTime + coalescingThresholdSeconds < startTime)
  370. return null;
  371. if (endTime + coalescingThresholdSeconds < lastRecord.startTime)
  372. return null;
  373. if (WebInspector.TimelinePresentationModel.coalescingKeyForRecord(record) !== WebInspector.TimelinePresentationModel.coalescingKeyForRecord(lastRecord._record))
  374. return null;
  375. if (lastRecord.parent.coalesced)
  376. return lastRecord.parent;
  377. return this._replaceWithCoalescedRecord(lastRecord);
  378. },
  379. /**
  380. * @param {WebInspector.TimelinePresentationModel.Record} record
  381. * @return {WebInspector.TimelinePresentationModel.Record}
  382. */
  383. _replaceWithCoalescedRecord: function(record)
  384. {
  385. var rawRecord = {
  386. type: record._record.type,
  387. startTime: record._record.startTime,
  388. endTime: record._record.endTime,
  389. data: { }
  390. };
  391. if (record._record.thread)
  392. rawRecord.thread = "aggregated";
  393. if (record.type === WebInspector.TimelineModel.RecordType.TimeStamp)
  394. rawRecord.data.message = record.data.message;
  395. var coalescedRecord = new WebInspector.TimelinePresentationModel.Record(this, rawRecord, null, null, null, false);
  396. var parent = record.parent;
  397. coalescedRecord.coalesced = true;
  398. coalescedRecord.collapsed = true;
  399. coalescedRecord._children.push(record);
  400. record.parent = coalescedRecord;
  401. coalescedRecord.calculateAggregatedStats();
  402. if (record.hasWarning || record.childHasWarning)
  403. coalescedRecord.childHasWarning = true;
  404. coalescedRecord.parent = parent;
  405. parent._children[parent._children.indexOf(record)] = coalescedRecord;
  406. return coalescedRecord;
  407. },
  408. _findParentRecord: function(record)
  409. {
  410. if (!this._glueRecords)
  411. return null;
  412. var recordTypes = WebInspector.TimelineModel.RecordType;
  413. switch (record.type) {
  414. case recordTypes.ResourceReceiveResponse:
  415. case recordTypes.ResourceFinish:
  416. case recordTypes.ResourceReceivedData:
  417. return this._sendRequestRecords[record.data["requestId"]];
  418. case recordTypes.ResourceSendRequest:
  419. return this._rootRecord;
  420. case recordTypes.TimerFire:
  421. return this._timerRecords[record.data["timerId"]];
  422. case recordTypes.ResourceSendRequest:
  423. return this._scheduledResourceRequests[record.data["url"]];
  424. case recordTypes.FireAnimationFrame:
  425. return this._requestAnimationFrameRecords[record.data["id"]];
  426. case recordTypes.Time:
  427. return this._rootRecord;
  428. case recordTypes.TimeEnd:
  429. return this._timeRecords[record.data["message"]];
  430. }
  431. },
  432. setGlueRecords: function(glue)
  433. {
  434. this._glueRecords = glue;
  435. },
  436. invalidateFilteredRecords: function()
  437. {
  438. delete this._filteredRecords;
  439. },
  440. filteredRecords: function()
  441. {
  442. if (this._filteredRecords)
  443. return this._filteredRecords;
  444. var recordsInWindow = [];
  445. var stack = [{children: this._rootRecord.children, index: 0, parentIsCollapsed: false, parentRecord: {}}];
  446. var revealedDepth = 0;
  447. function revealRecordsInStack() {
  448. for (var depth = revealedDepth + 1; depth < stack.length; ++depth) {
  449. if (stack[depth - 1].parentIsCollapsed) {
  450. stack[depth].parentRecord.parent._expandable = true;
  451. return;
  452. }
  453. stack[depth - 1].parentRecord.collapsed = false;
  454. recordsInWindow.push(stack[depth].parentRecord);
  455. stack[depth].windowLengthBeforeChildrenTraversal = recordsInWindow.length;
  456. stack[depth].parentIsRevealed = true;
  457. revealedDepth = depth;
  458. }
  459. }
  460. while (stack.length) {
  461. var entry = stack[stack.length - 1];
  462. var records = entry.children;
  463. if (records && entry.index < records.length) {
  464. var record = records[entry.index];
  465. ++entry.index;
  466. if (this.isVisible(record)) {
  467. record.parent._expandable = true;
  468. if (this._searchFilter)
  469. revealRecordsInStack();
  470. if (!entry.parentIsCollapsed) {
  471. recordsInWindow.push(record);
  472. revealedDepth = stack.length;
  473. entry.parentRecord.collapsed = false;
  474. }
  475. }
  476. record._expandable = false;
  477. stack.push({children: record.children,
  478. index: 0,
  479. parentIsCollapsed: (entry.parentIsCollapsed || (record.collapsed && (!this._searchFilter || record.clicked))),
  480. parentRecord: record,
  481. windowLengthBeforeChildrenTraversal: recordsInWindow.length});
  482. } else {
  483. stack.pop();
  484. revealedDepth = Math.min(revealedDepth, stack.length - 1);
  485. entry.parentRecord._visibleChildrenCount = recordsInWindow.length - entry.windowLengthBeforeChildrenTraversal;
  486. }
  487. }
  488. this._filteredRecords = recordsInWindow;
  489. return recordsInWindow;
  490. },
  491. filteredFrames: function(startTime, endTime)
  492. {
  493. function compareStartTime(value, object)
  494. {
  495. return value - object.startTime;
  496. }
  497. function compareEndTime(value, object)
  498. {
  499. return value - object.endTime;
  500. }
  501. var firstFrame = insertionIndexForObjectInListSortedByFunction(startTime, this._frames, compareStartTime);
  502. var lastFrame = insertionIndexForObjectInListSortedByFunction(endTime, this._frames, compareEndTime);
  503. while (lastFrame < this._frames.length && this._frames[lastFrame].endTime <= endTime)
  504. ++lastFrame;
  505. return this._frames.slice(firstFrame, lastFrame);
  506. },
  507. eventDividerRecords: function()
  508. {
  509. return this._eventDividerRecords;
  510. },
  511. isVisible: function(record)
  512. {
  513. for (var i = 0; i < this._filters.length; ++i) {
  514. if (!this._filters[i].accept(record))
  515. return false;
  516. }
  517. return !this._searchFilter || this._searchFilter.accept(record);
  518. },
  519. /**
  520. * @param {{tasks: !Array.<{startTime: number, endTime: number}>, firstTaskIndex: number, lastTaskIndex: number}} info
  521. * @return {!Element}
  522. */
  523. generateMainThreadBarPopupContent: function(info)
  524. {
  525. var firstTaskIndex = info.firstTaskIndex;
  526. var lastTaskIndex = info.lastTaskIndex;
  527. var tasks = info.tasks;
  528. var messageCount = lastTaskIndex - firstTaskIndex + 1;
  529. var cpuTime = 0;
  530. for (var i = firstTaskIndex; i <= lastTaskIndex; ++i) {
  531. var task = tasks[i];
  532. cpuTime += task.endTime - task.startTime;
  533. }
  534. var startTime = tasks[firstTaskIndex].startTime;
  535. var endTime = tasks[lastTaskIndex].endTime;
  536. var duration = endTime - startTime;
  537. var offset = this._minimumRecordTime;
  538. var contentHelper = new WebInspector.PopoverContentHelper(WebInspector.UIString("CPU"));
  539. var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(duration, true),
  540. Number.secondsToString(startTime - offset, true));
  541. contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText);
  542. contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(cpuTime, true));
  543. contentHelper.appendTextRow(WebInspector.UIString("Message Count"), messageCount);
  544. return contentHelper.contentTable();
  545. },
  546. __proto__: WebInspector.Object.prototype
  547. }
  548. /**
  549. * @constructor
  550. * @param {WebInspector.TimelinePresentationModel} presentationModel
  551. * @param {Object} record
  552. * @param {WebInspector.TimelinePresentationModel.Record} parentRecord
  553. * @param {WebInspector.TimelinePresentationModel.Record} origin
  554. * @param {Object|undefined} scriptDetails
  555. * @param {boolean} hidden
  556. */
  557. WebInspector.TimelinePresentationModel.Record = function(presentationModel, record, parentRecord, origin, scriptDetails, hidden)
  558. {
  559. this._linkifier = presentationModel._linkifier;
  560. this._aggregatedStats = {};
  561. this._record = record;
  562. this._children = [];
  563. if (!hidden && parentRecord) {
  564. this.parent = parentRecord;
  565. if (this.isBackground)
  566. WebInspector.TimelinePresentationModel.insertRetrospectiveRecord(parentRecord, this);
  567. else
  568. parentRecord.children.push(this);
  569. }
  570. if (origin)
  571. this._origin = origin;
  572. this._selfTime = this.endTime - this.startTime;
  573. this._lastChildEndTime = this.endTime;
  574. this._startTimeOffset = this.startTime - presentationModel._minimumRecordTime;
  575. if (record.data) {
  576. if (record.data["url"])
  577. this.url = record.data["url"];
  578. if (record.data["layerRootNode"])
  579. this._relatedBackendNodeId = record.data["layerRootNode"];
  580. else if (record.data["elementId"])
  581. this._relatedBackendNodeId = record.data["elementId"];
  582. }
  583. if (scriptDetails) {
  584. this.scriptName = scriptDetails.scriptName;
  585. this.scriptLine = scriptDetails.scriptLine;
  586. }
  587. if (parentRecord && parentRecord.callSiteStackTrace)
  588. this.callSiteStackTrace = parentRecord.callSiteStackTrace;
  589. var recordTypes = WebInspector.TimelineModel.RecordType;
  590. switch (record.type) {
  591. case recordTypes.ResourceSendRequest:
  592. // Make resource receive record last since request was sent; make finish record last since response received.
  593. presentationModel._sendRequestRecords[record.data["requestId"]] = this;
  594. break;
  595. case recordTypes.ScheduleResourceRequest:
  596. presentationModel._scheduledResourceRequests[record.data["url"]] = this;
  597. break;
  598. case recordTypes.ResourceReceiveResponse:
  599. var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]];
  600. if (sendRequestRecord) { // False if we started instrumentation in the middle of request.
  601. this.url = sendRequestRecord.url;
  602. // Now that we have resource in the collection, recalculate details in order to display short url.
  603. sendRequestRecord._refreshDetails();
  604. if (sendRequestRecord.parent !== presentationModel._rootRecord && sendRequestRecord.parent.type === recordTypes.ScheduleResourceRequest)
  605. sendRequestRecord.parent._refreshDetails();
  606. }
  607. break;
  608. case recordTypes.ResourceReceivedData:
  609. case recordTypes.ResourceFinish:
  610. var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]];
  611. if (sendRequestRecord) // False for main resource.
  612. this.url = sendRequestRecord.url;
  613. break;
  614. case recordTypes.TimerInstall:
  615. this.timeout = record.data["timeout"];
  616. this.singleShot = record.data["singleShot"];
  617. presentationModel._timerRecords[record.data["timerId"]] = this;
  618. break;
  619. case recordTypes.TimerFire:
  620. var timerInstalledRecord = presentationModel._timerRecords[record.data["timerId"]];
  621. if (timerInstalledRecord) {
  622. this.callSiteStackTrace = timerInstalledRecord.stackTrace;
  623. this.timeout = timerInstalledRecord.timeout;
  624. this.singleShot = timerInstalledRecord.singleShot;
  625. }
  626. break;
  627. case recordTypes.RequestAnimationFrame:
  628. presentationModel._requestAnimationFrameRecords[record.data["id"]] = this;
  629. break;
  630. case recordTypes.FireAnimationFrame:
  631. var requestAnimationRecord = presentationModel._requestAnimationFrameRecords[record.data["id"]];
  632. if (requestAnimationRecord)
  633. this.callSiteStackTrace = requestAnimationRecord.stackTrace;
  634. break;
  635. case recordTypes.Time:
  636. var message = record.data["message"];
  637. var oldReference = presentationModel._timeRecords[message];
  638. if (oldReference)
  639. break;
  640. presentationModel._timeRecords[message] = this;
  641. if (origin)
  642. presentationModel._timeRecordStack.push(this);
  643. break;
  644. case recordTypes.TimeEnd:
  645. var message = record.data["message"];
  646. var timeRecord = presentationModel._timeRecords[message];
  647. delete presentationModel._timeRecords[message];
  648. if (timeRecord) {
  649. this.timeRecord = timeRecord;
  650. timeRecord.timeEndRecord = this;
  651. var intervalDuration = this.startTime - timeRecord.startTime;
  652. this.intervalDuration = intervalDuration;
  653. timeRecord.intervalDuration = intervalDuration;
  654. if (!origin)
  655. break;
  656. var recordStack = presentationModel._timeRecordStack;
  657. recordStack.splice(recordStack.indexOf(timeRecord), 1);
  658. for (var index = recordStack.length; index; --index) {
  659. var openRecord = recordStack[index - 1];
  660. if (openRecord.startTime > timeRecord.startTime)
  661. continue;
  662. WebInspector.TimelinePresentationModel.adoptRecord(openRecord, timeRecord);
  663. break;
  664. }
  665. }
  666. break;
  667. case recordTypes.ScheduleStyleRecalculation:
  668. presentationModel._lastScheduleStyleRecalculation[this.frameId] = this;
  669. break;
  670. case recordTypes.RecalculateStyles:
  671. var scheduleStyleRecalculationRecord = presentationModel._lastScheduleStyleRecalculation[this.frameId];
  672. if (!scheduleStyleRecalculationRecord)
  673. break;
  674. this.callSiteStackTrace = scheduleStyleRecalculationRecord.stackTrace;
  675. break;
  676. case recordTypes.InvalidateLayout:
  677. // Consider style recalculation as a reason for layout invalidation,
  678. // but only if we had no earlier layout invalidation records.
  679. var styleRecalcStack;
  680. if (!presentationModel._layoutInvalidateStack[this.frameId]) {
  681. for (var outerRecord = parentRecord; outerRecord; outerRecord = record.parent) {
  682. if (outerRecord.type === recordTypes.RecalculateStyles) {
  683. styleRecalcStack = outerRecord.callSiteStackTrace;
  684. break;
  685. }
  686. }
  687. }
  688. presentationModel._layoutInvalidateStack[this.frameId] = styleRecalcStack || this.stackTrace;
  689. break;
  690. case recordTypes.Layout:
  691. var layoutInvalidateStack = presentationModel._layoutInvalidateStack[this.frameId];
  692. if (layoutInvalidateStack)
  693. this.callSiteStackTrace = layoutInvalidateStack;
  694. if (this.stackTrace)
  695. this.setHasWarning();
  696. presentationModel._layoutInvalidateStack[this.frameId] = null;
  697. this.highlightQuad = record.data.root || WebInspector.TimelinePresentationModel.quadFromRectData(record.data);
  698. this._relatedBackendNodeId = record.data["rootNode"];
  699. break;
  700. case recordTypes.Paint:
  701. this.highlightQuad = record.data.clip || WebInspector.TimelinePresentationModel.quadFromRectData(record.data);
  702. break;
  703. case recordTypes.WebSocketCreate:
  704. this.webSocketURL = record.data["url"];
  705. if (typeof record.data["webSocketProtocol"] !== "undefined")
  706. this.webSocketProtocol = record.data["webSocketProtocol"];
  707. presentationModel._webSocketCreateRecords[record.data["identifier"]] = this;
  708. break;
  709. case recordTypes.WebSocketSendHandshakeRequest:
  710. case recordTypes.WebSocketReceiveHandshakeResponse:
  711. case recordTypes.WebSocketDestroy:
  712. var webSocketCreateRecord = presentationModel._webSocketCreateRecords[record.data["identifier"]];
  713. if (webSocketCreateRecord) { // False if we started instrumentation in the middle of request.
  714. this.webSocketURL = webSocketCreateRecord.webSocketURL;
  715. if (typeof webSocketCreateRecord.webSocketProtocol !== "undefined")
  716. this.webSocketProtocol = webSocketCreateRecord.webSocketProtocol;
  717. }
  718. break;
  719. }
  720. }
  721. WebInspector.TimelinePresentationModel.adoptRecord = function(newParent, record)
  722. {
  723. record.parent.children.splice(record.parent.children.indexOf(record));
  724. WebInspector.TimelinePresentationModel.insertRetrospectiveRecord(newParent, record);
  725. record.parent = newParent;
  726. }
  727. WebInspector.TimelinePresentationModel.insertRetrospectiveRecord = function(parent, record)
  728. {
  729. function compareStartTime(value, record)
  730. {
  731. return value < record.startTime ? -1 : 1;
  732. }
  733. parent.children.splice(insertionIndexForObjectInListSortedByFunction(record.startTime, parent.children, compareStartTime), 0, record);
  734. }
  735. WebInspector.TimelinePresentationModel.Record.prototype = {
  736. get lastChildEndTime()
  737. {
  738. return this._lastChildEndTime;
  739. },
  740. set lastChildEndTime(time)
  741. {
  742. this._lastChildEndTime = time;
  743. },
  744. get selfTime()
  745. {
  746. return this.coalesced ? this._lastChildEndTime - this.startTime : this._selfTime;
  747. },
  748. set selfTime(time)
  749. {
  750. this._selfTime = time;
  751. },
  752. get cpuTime()
  753. {
  754. return this._cpuTime;
  755. },
  756. /**
  757. * @return {boolean}
  758. */
  759. isRoot: function()
  760. {
  761. return this.type === WebInspector.TimelineModel.RecordType.Root;
  762. },
  763. /**
  764. * @return {WebInspector.TimelinePresentationModel.Record}
  765. */
  766. origin: function()
  767. {
  768. return this._origin || this.parent;
  769. },
  770. /**
  771. * @return {Array.<WebInspector.TimelinePresentationModel.Record>}
  772. */
  773. get children()
  774. {
  775. return this._children;
  776. },
  777. /**
  778. * @return {number}
  779. */
  780. get visibleChildrenCount()
  781. {
  782. return this._visibleChildrenCount || 0;
  783. },
  784. /**
  785. * @return {boolean}
  786. */
  787. get expandable()
  788. {
  789. return !!this._expandable;
  790. },
  791. /**
  792. * @return {WebInspector.TimelineCategory}
  793. */
  794. get category()
  795. {
  796. return WebInspector.TimelinePresentationModel.recordStyle(this._record).category
  797. },
  798. /**
  799. * @return {string}
  800. */
  801. get title()
  802. {
  803. return this.type === WebInspector.TimelineModel.RecordType.TimeStamp ? this._record.data["message"] :
  804. WebInspector.TimelinePresentationModel.recordStyle(this._record).title;
  805. },
  806. /**
  807. * @return {number}
  808. */
  809. get startTime()
  810. {
  811. return WebInspector.TimelineModel.startTimeInSeconds(this._record);
  812. },
  813. /**
  814. * @return {number}
  815. */
  816. get endTime()
  817. {
  818. return WebInspector.TimelineModel.endTimeInSeconds(this._record);
  819. },
  820. /**
  821. * @return {boolean}
  822. */
  823. get isBackground()
  824. {
  825. return !!this._record.thread;
  826. },
  827. /**
  828. * @return {Object}
  829. */
  830. get data()
  831. {
  832. return this._record.data;
  833. },
  834. /**
  835. * @return {string}
  836. */
  837. get type()
  838. {
  839. return this._record.type;
  840. },
  841. /**
  842. * @return {string}
  843. */
  844. get frameId()
  845. {
  846. return this._record.frameId;
  847. },
  848. /**
  849. * @return {number}
  850. */
  851. get usedHeapSizeDelta()
  852. {
  853. return this._record.usedHeapSizeDelta || 0;
  854. },
  855. /**
  856. * @return {number}
  857. */
  858. get usedHeapSize()
  859. {
  860. return this._record.usedHeapSize;
  861. },
  862. /**
  863. * @return {Array.<DebuggerAgent.CallFrame>?}
  864. */
  865. get stackTrace()
  866. {
  867. if (this._record.stackTrace && this._record.stackTrace.length)
  868. return this._record.stackTrace;
  869. return null;
  870. },
  871. containsTime: function(time)
  872. {
  873. return this.startTime <= time && time <= this.endTime;
  874. },
  875. /**
  876. * @param {function(Element)} callback
  877. */
  878. generatePopupContent: function(callback)
  879. {
  880. var barrier = new CallbackBarrier();
  881. if (WebInspector.TimelinePresentationModel.needsPreviewElement(this.type) && !this._imagePreviewElement)
  882. WebInspector.DOMPresentationUtils.buildImagePreviewContents(this.url, false, barrier.createCallback(this._setImagePreviewElement.bind(this)));
  883. if (this._relatedBackendNodeId && !this._relatedNode)
  884. WebInspector.domAgent.pushNodeByBackendIdToFrontend(this._relatedBackendNodeId, barrier.createCallback(this._setRelatedNode.bind(this)));
  885. barrier.callWhenDone(callbackWrapper.bind(this));
  886. function callbackWrapper()
  887. {
  888. callback(this._generatePopupContentSynchronously());
  889. }
  890. },
  891. /**
  892. * @param {Element} element
  893. */
  894. _setImagePreviewElement: function(element)
  895. {
  896. this._imagePreviewElement = element;
  897. },
  898. /**
  899. * @param {?DOMAgent.NodeId} nodeId
  900. */
  901. _setRelatedNode: function(nodeId)
  902. {
  903. if (typeof nodeId === "number")
  904. this._relatedNode = WebInspector.domAgent.nodeForId(nodeId);
  905. },
  906. /**
  907. * @return {Element}
  908. */
  909. _generatePopupContentSynchronously: function()
  910. {
  911. var contentHelper = new WebInspector.PopoverContentHelper(this.title);
  912. var text = WebInspector.UIString("%s (at %s)", Number.secondsToString(this._lastChildEndTime - this.startTime, true),
  913. Number.secondsToString(this._startTimeOffset));
  914. contentHelper.appendTextRow(WebInspector.UIString("Duration"), text);
  915. if (this._children.length) {
  916. if (!this.coalesced)
  917. contentHelper.appendTextRow(WebInspector.UIString("Self Time"), Number.secondsToString(this._selfTime, true));
  918. contentHelper.appendTextRow(WebInspector.UIString("CPU Time"), Number.secondsToString(this._cpuTime, true));
  919. contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"),
  920. WebInspector.TimelinePresentationModel._generateAggregatedInfo(this._aggregatedStats));
  921. }
  922. if (this.coalesced)
  923. return contentHelper.contentTable();
  924. const recordTypes = WebInspector.TimelineModel.RecordType;
  925. // The messages may vary per record type;
  926. var callSiteStackTraceLabel;
  927. var callStackLabel;
  928. switch (this.type) {
  929. case recordTypes.GCEvent:
  930. contentHelper.appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(this.data["usedHeapSizeDelta"]));
  931. break;
  932. case recordTypes.TimerFire:
  933. callSiteStackTraceLabel = WebInspector.UIString("Timer installed");
  934. // Fall-through intended.
  935. case recordTypes.TimerInstall:
  936. case recordTypes.TimerRemove:
  937. contentHelper.appendTextRow(WebInspector.UIString("Timer ID"), this.data["timerId"]);
  938. if (typeof this.timeout === "number") {
  939. contentHelper.appendTextRow(WebInspector.UIString("Timeout"), Number.secondsToString(this.timeout / 1000));
  940. contentHelper.appendTextRow(WebInspector.UIString("Repeats"), !this.singleShot);
  941. }
  942. break;
  943. case recordTypes.FireAnimationFrame:
  944. callSiteStackTraceLabel = WebInspector.UIString("Animation frame requested");
  945. contentHelper.appendTextRow(WebInspector.UIString("Callback ID"), this.data["id"]);
  946. break;
  947. case recordTypes.FunctionCall:
  948. contentHelper.appendElementRow(WebInspector.UIString("Location"), this._linkifyScriptLocation());
  949. break;
  950. case recordTypes.ScheduleResourceRequest:
  951. case recordTypes.ResourceSendRequest:
  952. case recordTypes.ResourceReceiveResponse:
  953. case recordTypes.ResourceReceivedData:
  954. case recordTypes.ResourceFinish:
  955. contentHelper.appendElementRow(WebInspector.UIString("Resource"), WebInspector.linkifyResourceAsNode(this.url));
  956. if (this._imagePreviewElement)
  957. contentHelper.appendElementRow(WebInspector.UIString("Preview"), this._imagePreviewElement);
  958. if (this.data["requestMethod"])
  959. contentHelper.appendTextRow(WebInspector.UIString("Request Method"), this.data["requestMethod"]);
  960. if (typeof this.data["statusCode"] === "number")
  961. contentHelper.appendTextRow(WebInspector.UIString("Status Code"), this.data["statusCode"]);
  962. if (this.data["mimeType"])
  963. contentHelper.appendTextRow(WebInspector.UIString("MIME Type"), this.data["mimeType"]);
  964. if (this.data["encodedDataLength"])
  965. contentHelper.appendTextRow(WebInspector.UIString("Encoded Data Length"), WebInspector.UIString("%d Bytes", this.data["encodedDataLength"]));
  966. break;
  967. case recordTypes.EvaluateScript:
  968. if (this.data && this.url)
  969. contentHelper.appendElementRow(WebInspector.UIString("Script"), this._linkifyLocation(this.url, this.data["lineNumber"]));
  970. break;
  971. case recordTypes.Paint:
  972. var clip = this.data["clip"];
  973. if (clip) {
  974. contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", clip[0], clip[1]));
  975. var clipWidth = WebInspector.TimelinePresentationModel.quadWidth(clip);
  976. var clipHeight = WebInspector.TimelinePresentationModel.quadHeight(clip);
  977. contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d × %d", clipWidth, clipHeight));
  978. } else {
  979. // Backward compatibility: older version used x, y, width, height fields directly in data.
  980. if (typeof this.data["x"] !== "undefined" && typeof this.data["y"] !== "undefined")
  981. contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", this.data["x"], this.data["y"]));
  982. if (typeof this.data["width"] !== "undefined" && typeof this.data["height"] !== "undefined")
  983. contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d\u2009\u00d7\u2009%d", this.data["width"], this.data["height"]));
  984. }
  985. // Fall-through intended.
  986. case recordTypes.PaintSetup:
  987. case recordTypes.Rasterize:
  988. case recordTypes.ScrollLayer:
  989. if (this._relatedNode)
  990. contentHelper.appendElementRow(WebInspector.UIString("Layer root"), this._createNodeAnchor(this._relatedNode));
  991. break;
  992. case recordTypes.DecodeImage:
  993. case recordTypes.ResizeImage:
  994. if (this._relatedNode)
  995. contentHelper.appendElementRow(WebInspector.UIString("Image element"), this._createNodeAnchor(this._relatedNode));
  996. if (this.url)
  997. contentHelper.appendElementRow(WebInspector.UIString("Image URL"), WebInspector.linkifyResourceAsNode(this.url));
  998. break;
  999. case recordTypes.RecalculateStyles: // We don't want to see default details.
  1000. if (this.data["elementCount"])
  1001. contentHelper.appendTextRow(WebInspector.UIString("Elements affected"), this.data["elementCount"]);
  1002. callStackLabel = WebInspector.UIString("Styles recalculation forced");
  1003. break;
  1004. case recordTypes.Layout:
  1005. if (this.data["dirtyObjects"])
  1006. contentHelper.appendTextRow(WebInspector.UIString("Nodes that need layout"), this.data["dirtyObjects"]);
  1007. if (this.data["totalObjects"])
  1008. contentHelper.appendTextRow(WebInspector.UIString("Layout tree size"), this.data["totalObjects"]);
  1009. if (typeof this.data["partialLayout"] === "boolean") {
  1010. contentHelper.appendTextRow(WebInspector.UIString("Layout scope"),
  1011. this.data["partialLayout"] ? WebInspector.UIString("Partial") : WebInspector.UIString("Whole document"));
  1012. }
  1013. callSiteStackTraceLabel = WebInspector.UIString("Layout invalidated");
  1014. if (this.stackTrace) {
  1015. callStackLabel = WebInspector.UIString("Layout forced");
  1016. contentHelper.appendTextRow(WebInspector.UIString("Note"), WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck."));
  1017. }
  1018. if (this._relatedNode)
  1019. contentHelper.appendElementRow(WebInspector.UIString("Layout root"), this._createNodeAnchor(this._relatedNode));
  1020. break;
  1021. case recordTypes.Time:
  1022. case recordTypes.TimeEnd:
  1023. contentHelper.appendTextRow(WebInspector.UIString("Message"), this.data["message"]);
  1024. if (typeof this.intervalDuration === "number")
  1025. contentHelper.appendTextRow(WebInspector.UIString("Interval Duration"), Number.secondsToString(this.intervalDuration, true));
  1026. break;
  1027. case recordTypes.WebSocketCreate:
  1028. case recordTypes.WebSocketSendHandshakeRequest:
  1029. case recordTypes.WebSocketReceiveHandshakeResponse:
  1030. case recordTypes.WebSocketDestroy:
  1031. if (typeof this.webSocketURL !== "undefined")
  1032. contentHelper.appendTextRow(WebInspector.UIString("URL"), this.webSocketURL);
  1033. if (typeof this.webSocketProtocol !== "undefined")
  1034. contentHelper.appendTextRow(WebInspector.UIString("WebSocket Protocol"), this.webSocketProtocol);
  1035. if (typeof this.data["message"] !== "undefined")
  1036. contentHelper.appendTextRow(WebInspector.UIString("Message"), this.data["message"])
  1037. break;
  1038. default:
  1039. if (this.detailsNode())
  1040. contentHelper.appendElementRow(WebInspector.UIString("Details"), this.detailsNode().childNodes[1].cloneNode());
  1041. break;
  1042. }
  1043. if (this.scriptName && this.type !== recordTypes.FunctionCall)
  1044. contentHelper.appendElementRow(WebInspector.UIString("Function Call"), this._linkifyScriptLocation());
  1045. if (this.usedHeapSize) {
  1046. if (this.usedHeapSizeDelta) {
  1047. var sign = this.usedHeapSizeDelta > 0 ? "+" : "-";
  1048. contentHelper.appendTextRow(WebInspector.UIString("Used Heap Size"),
  1049. WebInspector.UIString("%s (%s%s)", Number.bytesToString(this.usedHeapSize), sign, Number.bytesToString(Math.abs(this.usedHeapSizeDelta))));
  1050. } else if (this.category === WebInspector.TimelinePresentationModel.categories().scripting)
  1051. contentHelper.appendTextRow(WebInspector.UIString("Used Heap Size"), Number.bytesToString(this.usedHeapSize));
  1052. }
  1053. if (this.callSiteStackTrace)
  1054. contentHelper.appendStackTrace(callSiteStackTraceLabel || WebInspector.UIString("Call Site stack"), this.callSiteStackTrace, this._linkifyCallFrame.bind(this));
  1055. if (this.stackTrace)
  1056. contentHelper.appendStackTrace(callStackLabel || WebInspector.UIString("Call Stack"), this.stackTrace, this._linkifyCallFrame.bind(this));
  1057. return contentHelper.contentTable();
  1058. },
  1059. /**
  1060. * @param {WebInspector.DOMAgent} node
  1061. */
  1062. _createNodeAnchor: function(node)
  1063. {
  1064. var span = document.createElement("span");
  1065. span.classList.add("node-link");
  1066. span.addEventListener("click", onClick, false);
  1067. WebInspector.DOMPresentationUtils.decorateNodeLabel(node, span);
  1068. function onClick()
  1069. {
  1070. WebInspector.showPanel("elements").revealAndSelectNode(node.id);
  1071. }
  1072. return span;
  1073. },
  1074. _refreshDetails: function()
  1075. {
  1076. delete this._detailsNode;
  1077. },
  1078. /**
  1079. * @return {?Node}
  1080. */
  1081. detailsNode: function()
  1082. {
  1083. if (typeof this._detailsNode === "undefined") {
  1084. this._detailsNode = this._getRecordDetails();
  1085. if (this._detailsNode && !this.coalesced) {
  1086. this._detailsNode.insertBefore(document.createTextNode("("), this._detailsNode.firstChild);
  1087. this._detailsNode.appendChild(document.createTextNode(")"));
  1088. }
  1089. }
  1090. return this._detailsNode;
  1091. },
  1092. _createSpanWithText: function(textContent)
  1093. {
  1094. var node = document.createElement("span");
  1095. node.textContent = textContent;
  1096. return node;
  1097. },
  1098. /**
  1099. * @return {?Node}
  1100. */
  1101. _getRecordDetails: function()
  1102. {
  1103. var details;
  1104. if (this.coalesced)
  1105. return this._createSpanWithText(WebInspector.UIString("× %d", this.children.length));
  1106. switch (this.type) {
  1107. case WebInspector.TimelineModel.RecordType.GCEvent:
  1108. details = WebInspector.UIString("%s collected", Number.bytesToString(this.data["usedHeapSizeDelta"]));
  1109. break;
  1110. case WebInspector.TimelineModel.RecordType.TimerFire:
  1111. details = this._linkifyScriptLocation(this.data["timerId"]);
  1112. break;
  1113. case WebInspector.TimelineModel.RecordType.FunctionCall:
  1114. details = this._linkifyScriptLocation();
  1115. break;
  1116. case WebInspector.TimelineModel.RecordType.FireAnimationFrame:
  1117. details = this._linkifyScriptLocation(this.data["id"]);
  1118. break;
  1119. case WebInspector.TimelineModel.RecordType.EventDispatch:
  1120. details = this.data ? this.data["type"] : null;
  1121. break;
  1122. case WebInspector.TimelineModel.RecordType.Paint:
  1123. var width = this.data.clip ? WebInspector.TimelinePresentationModel.quadWidth(this.data.clip) : this.data.width;
  1124. var height = this.data.clip ? WebInspector.TimelinePresentationModel.quadHeight(this.data.clip) : this.data.height;
  1125. if (width && height)
  1126. details = WebInspector.UIString("%d\u2009\u00d7\u2009%d", width, height);
  1127. break;
  1128. case WebInspector.TimelineModel.RecordType.TimerInstall:
  1129. case WebInspector.TimelineModel.RecordType.TimerRemove:
  1130. details = this._linkifyTopCallFrame(this.data["timerId"]);
  1131. break;
  1132. case WebInspector.TimelineModel.RecordType.RequestAnimationFrame:
  1133. case WebInspector.TimelineModel.RecordType.CancelAnimationFrame:
  1134. details = this._linkifyTopCallFrame(this.data["id"]);
  1135. break;
  1136. case WebInspector.TimelineModel.RecordType.ParseHTML:
  1137. case WebInspector.TimelineModel.RecordType.RecalculateStyles:
  1138. details = this._linkifyTopCallFrame();
  1139. break;
  1140. case WebInspector.TimelineModel.RecordType.EvaluateScript:
  1141. details = this.url ? this._linkifyLocation(this.url, this.data["lineNumber"], 0) : null;
  1142. break;
  1143. case WebInspector.TimelineModel.RecordType.XHRReadyStateChange:
  1144. case WebInspector.TimelineModel.RecordType.XHRLoad:
  1145. case WebInspector.TimelineModel.RecordType.ScheduleResourceRequest:
  1146. case WebInspector.TimelineModel.RecordType.ResourceSendRequest:
  1147. case WebInspector.TimelineModel.RecordType.ResourceReceivedData:
  1148. case WebInspector.TimelineModel.RecordType.ResourceReceiveResponse:
  1149. case WebInspector.TimelineModel.RecordType.ResourceFinish:
  1150. case WebInspector.TimelineModel.RecordType.DecodeImage:
  1151. case WebInspector.TimelineModel.RecordType.ResizeImage:
  1152. details = WebInspector.displayNameForURL(this.url);
  1153. break;
  1154. case WebInspector.TimelineModel.RecordType.Time:
  1155. case WebInspector.TimelineModel.RecordType.TimeEnd:
  1156. details = this.data["message"];
  1157. break;
  1158. default:
  1159. details = this._linkifyScriptLocation() || this._linkifyTopCallFrame() || null;
  1160. break;
  1161. }
  1162. if (details) {
  1163. if (details instanceof Node)
  1164. details.tabIndex = -1;
  1165. else
  1166. return this._createSpanWithText("" + details);
  1167. }
  1168. return details || null;
  1169. },
  1170. /**
  1171. * @param {string} url
  1172. * @param {number} lineNumber
  1173. * @param {number=} columnNumber
  1174. */
  1175. _linkifyLocation: function(url, lineNumber, columnNumber)
  1176. {
  1177. // FIXME(62725): stack trace line/column numbers are one-based.
  1178. columnNumber = columnNumber ? columnNumber - 1 : 0;
  1179. return this._linkifier.linkifyLocation(url, lineNumber - 1, columnNumber, "timeline-details");
  1180. },
  1181. _linkifyCallFrame: function(callFrame)
  1182. {
  1183. return this._linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber);
  1184. },
  1185. /**
  1186. * @param {string=} defaultValue
  1187. */
  1188. _linkifyTopCallFrame: function(defaultValue)
  1189. {
  1190. if (this.stackTrace)
  1191. return this._linkifyCallFrame(this.stackTrace[0]);
  1192. if (this.callSiteStackTrace)
  1193. return this._linkifyCallFrame(this.callSiteStackTrace[0]);
  1194. return defaultValue;
  1195. },
  1196. /**
  1197. * @param {*=} defaultValue
  1198. * @return {Element|string}
  1199. */
  1200. _linkifyScriptLocation: function(defaultValue)
  1201. {
  1202. if (this.scriptName)
  1203. return this._linkifyLocation(this.scriptName, this.scriptLine, 0);
  1204. else
  1205. return defaultValue ? "" + defaultValue : null;
  1206. },
  1207. calculateAggregatedStats: function()
  1208. {
  1209. this._aggregatedStats = {};
  1210. this._cpuTime = this._selfTime;
  1211. for (var index = this._children.length; index; --index) {
  1212. var child = this._children[index - 1];
  1213. for (var category in child._aggregatedStats)
  1214. this._aggregatedStats[category] = (this._aggregatedStats[category] || 0) + child._aggregatedStats[category];
  1215. }
  1216. for (var category in this._aggregatedStats)
  1217. this._cpuTime += this._aggregatedStats[category];
  1218. this._aggregatedStats[this.category.name] = (this._aggregatedStats[this.category.name] || 0) + this._selfTime;
  1219. },
  1220. get aggregatedStats()
  1221. {
  1222. return this._aggregatedStats;
  1223. },
  1224. setHasWarning: function()
  1225. {
  1226. this.hasWarning = true;
  1227. for (var parent = this.parent; parent && !parent.childHasWarning; parent = parent.parent)
  1228. parent.childHasWarning = true;
  1229. }
  1230. }
  1231. /**
  1232. * @param {Object} aggregatedStats
  1233. */
  1234. WebInspector.TimelinePresentationModel._generateAggregatedInfo = function(aggregatedStats)
  1235. {
  1236. var cell = document.createElement("span");
  1237. cell.className = "timeline-aggregated-info";
  1238. for (var index in aggregatedStats) {
  1239. var label = document.createElement("div");
  1240. label.className = "timeline-aggregated-category timeline-" + index;
  1241. cell.appendChild(label);
  1242. var text = document.createElement("span");
  1243. text.textContent = Number.secondsToString(aggregatedStats[index], true);
  1244. cell.appendChild(text);
  1245. }
  1246. return cell;
  1247. }
  1248. WebInspector.TimelinePresentationModel.generatePopupContentForFrame = function(frame)
  1249. {
  1250. var contentHelper = new WebInspector.PopoverContentHelper(WebInspector.UIString("Frame"));
  1251. var durationInSeconds = frame.endTime - frame.startTime;
  1252. var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(frame.endTime - frame.startTime, true),
  1253. Number.secondsToString(frame.startTimeOffset, true));
  1254. contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText);
  1255. contentHelper.appendTextRow(WebInspector.UIString("FPS"), Math.floor(1 / durationInSeconds));
  1256. contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(frame.cpuTime, true));
  1257. contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"),
  1258. WebInspector.TimelinePresentationModel._generateAggregatedInfo(frame.timeByCategory));
  1259. return contentHelper.contentTable();
  1260. }
  1261. /**
  1262. * @param {WebInspector.FrameStatistics} statistics
  1263. */
  1264. WebInspector.TimelinePresentationModel.generatePopupContentForFrameStatistics = function(statistics)
  1265. {
  1266. /**
  1267. * @param {number} time
  1268. */
  1269. function formatTimeAndFPS(time)
  1270. {
  1271. return WebInspector.UIString("%s (%.0f FPS)", Number.secondsToString(time, true), 1 / time);
  1272. }
  1273. var contentHelper = new WebInspector.PopoverContentHelper(WebInspector.UIString("Selected Range"));
  1274. contentHelper.appendTextRow(WebInspector.UIString("Selected range"), WebInspector.UIString("%s\u2013%s (%d frames)",
  1275. Number.secondsToString(statistics.startOffset, true), Number.secondsToString(statistics.endOffset, true), statistics.frameCount));
  1276. contentHelper.appendTextRow(WebInspector.UIString("Minimum Time"), formatTimeAndFPS(statistics.minDuration));
  1277. contentHelper.appendTextRow(WebInspector.UIString("Average Time"), formatTimeAndFPS(statistics.average));
  1278. contentHelper.appendTextRow(WebInspector.UIString("Maximum Time"), formatTimeAndFPS(statistics.maxDuration));
  1279. contentHelper.appendTextRow(WebInspector.UIString("Standard Deviation"), Number.secondsToString(statistics.stddev, true));
  1280. contentHelper.appendElementRow(WebInspector.UIString("Time by category"),
  1281. WebInspector.TimelinePresentationModel._generateAggregatedInfo(statistics.timeByCategory));
  1282. return contentHelper.contentTable();
  1283. }
  1284. /**
  1285. * @param {CanvasRenderingContext2D} context
  1286. * @param {number} width
  1287. * @param {number} height
  1288. * @param {string} color0
  1289. * @param {string} color1
  1290. * @param {string} color2
  1291. */
  1292. WebInspector.TimelinePresentationModel.createFillStyle = function(context, width, height, color0, color1, color2)
  1293. {
  1294. var gradient = context.createLinearGradient(0, 0, width, height);
  1295. gradient.addColorStop(0, color0);
  1296. gradient.addColorStop(0.25, color1);
  1297. gradient.addColorStop(0.75, color1);
  1298. gradient.addColorStop(1, color2);
  1299. return gradient;
  1300. }
  1301. /**
  1302. * @param {CanvasRenderingContext2D} context
  1303. * @param {number} width
  1304. * @param {number} height
  1305. * @param {WebInspector.TimelineCategory} category
  1306. */
  1307. WebInspector.TimelinePresentationModel.createFillStyleForCategory = function(context, width, height, category)
  1308. {
  1309. return WebInspector.TimelinePresentationModel.createFillStyle(context, width, height, category.fillColorStop0, category.fillColorStop1, category.borderColor);
  1310. }
  1311. /**
  1312. * @param {WebInspector.TimelineCategory} category
  1313. */
  1314. WebInspector.TimelinePresentationModel.createStyleRuleForCategory = function(category)
  1315. {
  1316. var selector = ".timeline-category-" + category.name + " .timeline-graph-bar, " +
  1317. ".timeline-category-statusbar-item.timeline-category-" + category.name + " .timeline-category-checkbox, " +
  1318. ".popover .timeline-" + category.name + ", " +
  1319. ".timeline-category-" + category.name + " .timeline-tree-icon"
  1320. return selector + " { background-image: -webkit-linear-gradient(" +
  1321. category.fillColorStop0 + ", " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + ");" +
  1322. " border-color: " + category.borderColor +
  1323. "}";
  1324. }
  1325. /**
  1326. * @param {Object} rawRecord
  1327. * @return {string?}
  1328. */
  1329. WebInspector.TimelinePresentationModel.coalescingKeyForRecord = function(rawRecord)
  1330. {
  1331. var recordTypes = WebInspector.TimelineModel.RecordType;
  1332. switch (rawRecord.type)
  1333. {
  1334. case recordTypes.EventDispatch: return rawRecord.data["type"];
  1335. case recordTypes.TimeStamp: return rawRecord.data["message"];
  1336. default: return null;
  1337. }
  1338. }
  1339. /**
  1340. * @param {Array.<number>} quad
  1341. * @return {number}
  1342. */
  1343. WebInspector.TimelinePresentationModel.quadWidth = function(quad)
  1344. {
  1345. return Math.round(Math.sqrt(Math.pow(quad[0] - quad[2], 2) + Math.pow(quad[1] - quad[3], 2)));
  1346. }
  1347. /**
  1348. * @param {Array.<number>} quad
  1349. * @return {number}
  1350. */
  1351. WebInspector.TimelinePresentationModel.quadHeight = function(quad)
  1352. {
  1353. return Math.round(Math.sqrt(Math.pow(quad[0] - quad[6], 2) + Math.pow(quad[1] - quad[7], 2)));
  1354. }
  1355. /**
  1356. * @param {Object} data
  1357. * @return {Array.<number>?}
  1358. */
  1359. WebInspector.TimelinePresentationModel.quadFromRectData = function(data)
  1360. {
  1361. if (typeof data["x"] === "undefined" || typeof data["y"] === "undefined")
  1362. return null;
  1363. var x0 = data["x"];
  1364. var x1 = data["x"] + data["width"];
  1365. var y0 = data["y"];
  1366. var y1 = data["y"] + data["height"];
  1367. return [x0, y0, x1, y0, x1, y1, x0, y1];
  1368. }
  1369. /**
  1370. * @interface
  1371. */
  1372. WebInspector.TimelinePresentationModel.Filter = function()
  1373. {
  1374. }
  1375. WebInspector.TimelinePresentationModel.Filter.prototype = {
  1376. /**
  1377. * @param {!WebInspector.TimelinePresentationModel.Record} record
  1378. * @return {boolean}
  1379. */
  1380. accept: function(record) { return false; }
  1381. }
  1382. /**
  1383. * @constructor
  1384. * @extends {WebInspector.Object}
  1385. * @param {string} name
  1386. * @param {string} title
  1387. * @param {number} overviewStripGroupIndex
  1388. * @param {string} borderColor
  1389. * @param {string} fillColorStop0
  1390. * @param {string} fillColorStop1
  1391. */
  1392. WebInspector.TimelineCategory = function(name, title, overviewStripGroupIndex, borderColor, fillColorStop0, fillColorStop1)
  1393. {
  1394. this.name = name;
  1395. this.title = title;
  1396. this.overviewStripGroupIndex = overviewStripGroupIndex;
  1397. this.borderColor = borderColor;
  1398. this.fillColorStop0 = fillColorStop0;
  1399. this.fillColorStop1 = fillColorStop1;
  1400. this.hidden = false;
  1401. }
  1402. WebInspector.TimelineCategory.Events = {
  1403. VisibilityChanged: "VisibilityChanged"
  1404. };
  1405. WebInspector.TimelineCategory.prototype = {
  1406. /**
  1407. * @return {boolean}
  1408. */
  1409. get hidden()
  1410. {
  1411. return this._hidden;
  1412. },
  1413. set hidden(hidden)
  1414. {
  1415. this._hidden = hidden;
  1416. this.dispatchEventToListeners(WebInspector.TimelineCategory.Events.VisibilityChanged, this);
  1417. },
  1418. __proto__: WebInspector.Object.prototype
  1419. }