1 /* 2 * Copyright (C) 2013 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 /** 33 * @constructor 34 */ 35 WebInspector.TimelineUIUtils = function() { } 36 37 WebInspector.TimelineUIUtils.prototype = { 38 /** 39 * @param {!WebInspector.TimelineModel.Record} record 40 * @return {boolean} 41 */ 42 isBeginFrame: function(record) 43 { 44 throw new Error("Not implemented."); 45 }, 46 /** 47 * @param {!WebInspector.TimelineModel.Record} record 48 * @return {boolean} 49 */ 50 isProgram: function(record) 51 { 52 throw new Error("Not implemented."); 53 }, 54 /** 55 * @param {string} recordType 56 * @return {boolean} 57 */ 58 isCoalescable: function(recordType) 59 { 60 throw new Error("Not implemented."); 61 }, 62 /** 63 * @param {!WebInspector.TimelineModel.Record} record 64 * @return {boolean} 65 */ 66 isEventDivider: function(record) 67 { 68 throw new Error("Not implemented."); 69 }, 70 /** 71 * @param {!WebInspector.TimelineModel.Record} record 72 * @return {?Object} 73 */ 74 countersForRecord: function(record) 75 { 76 throw new Error("Not implemented."); 77 }, 78 /** 79 * @param {!WebInspector.TimelineModel.Record} record 80 * @return {?Object} 81 */ 82 highlightQuadForRecord: function(record) 83 { 84 throw new Error("Not implemented."); 85 }, 86 /** 87 * @param {!WebInspector.TimelineModel.Record} record 88 * @return {string} 89 */ 90 titleForRecord: function(record) 91 { 92 throw new Error("Not implemented."); 93 }, 94 /** 95 * @param {!WebInspector.TimelineModel.Record} record 96 * @return {!WebInspector.TimelineCategory} 97 */ 98 categoryForRecord: function(record) 99 { 100 throw new Error("Not implemented."); 101 }, 102 /** 103 * @param {!WebInspector.TimelineModel.Record} record 104 * @param {!WebInspector.Linkifier} linkifier 105 * @return {?Node} 106 */ 107 buildDetailsNode: function(record, linkifier) 108 { 109 throw new Error("Not implemented."); 110 }, 111 /** 112 * @param {!WebInspector.TimelineModel.Record} record 113 * @param {!WebInspector.TimelineModel} model 114 * @param {!WebInspector.Linkifier} linkifier 115 * @param {function(!DocumentFragment)} callback 116 */ 117 generateDetailsContent: function(record, model, linkifier, callback) 118 { 119 throw new Error("Not implemented."); 120 }, 121 /** 122 * @return {!Element} 123 */ 124 createBeginFrameDivider: function() 125 { 126 throw new Error("Not implemented."); 127 }, 128 /** 129 * @param {string} recordType 130 * @param {string=} title 131 * @return {!Element} 132 */ 133 createEventDivider: function(recordType, title) 134 { 135 throw new Error("Not implemented."); 136 }, 137 /** 138 * @param {!WebInspector.TimelineModel.Record} record 139 * @param {!RegExp} regExp 140 * @return {boolean} 141 */ 142 testContentMatching: function(record, regExp) 143 { 144 throw new Error("Not implemented."); 145 }, 146 /** 147 * @param {!Object} total 148 * @param {!WebInspector.TimelineModel.Record} record 149 */ 150 aggregateTimeForRecord: function(total, record) 151 { 152 throw new Error("Not implemented."); 153 }, 154 /** 155 * @return {!WebInspector.TimelineModel.Filter} 156 */ 157 hiddenRecordsFilter: function() 158 { 159 throw new Error("Not implemented."); 160 }, 161 /** 162 * @return {?WebInspector.TimelineModel.Filter} 163 */ 164 hiddenEmptyRecordsFilter: function() 165 { 166 return null; 167 } 168 } 169 170 /** 171 * @return {!Object.<string, !WebInspector.TimelineCategory>} 172 */ 173 WebInspector.TimelineUIUtils.categories = function() 174 { 175 if (WebInspector.TimelineUIUtils._categories) 176 return WebInspector.TimelineUIUtils._categories; 177 WebInspector.TimelineUIUtils._categories = { 178 loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), 0, "hsl(214, 53%, 58%)", "hsl(214, 67%, 90%)", "hsl(214, 67%, 74%)", "hsl(214, 67%, 66%)"), 179 scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), 1, "hsl(43, 90%, 45%)", "hsl(43, 83%, 90%)", "hsl(43, 83%, 72%)", "hsl(43, 83%, 64%) "), 180 rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), 2, "hsl(256, 50%, 60%)", "hsl(256, 67%, 90%)", "hsl(256, 67%, 76%)", "hsl(256, 67%, 70%)"), 181 painting: new WebInspector.TimelineCategory("painting", WebInspector.UIString("Painting"), 2, "hsl(109, 33%, 47%)", "hsl(109, 33%, 90%)", "hsl(109, 33%, 64%)", "hsl(109, 33%, 55%)"), 182 other: new WebInspector.TimelineCategory("other", WebInspector.UIString("Other"), -1, "hsl(0, 0%, 73%)", "hsl(0, 0%, 90%)", "hsl(0, 0%, 87%)", "hsl(0, 0%, 79%)"), 183 idle: new WebInspector.TimelineCategory("idle", WebInspector.UIString("Idle"), -1, "hsl(0, 0%, 87%)", "hsl(0, 100%, 100%)", "hsl(0, 100%, 100%)", "hsl(0, 100%, 100%)") 184 }; 185 return WebInspector.TimelineUIUtils._categories; 186 }; 187 188 /** 189 * @param {!WebInspector.TimelineModel} model 190 * @param {!{name: string, tasks: !Array.<!WebInspector.TimelineModel.Record>, firstTaskIndex: number, lastTaskIndex: number}} info 191 * @return {!Element} 192 */ 193 WebInspector.TimelineUIUtils.generateMainThreadBarPopupContent = function(model, info) 194 { 195 var firstTaskIndex = info.firstTaskIndex; 196 var lastTaskIndex = info.lastTaskIndex; 197 var tasks = info.tasks; 198 var messageCount = lastTaskIndex - firstTaskIndex + 1; 199 var cpuTime = 0; 200 201 for (var i = firstTaskIndex; i <= lastTaskIndex; ++i) { 202 var task = tasks[i]; 203 cpuTime += task.endTime() - task.startTime(); 204 } 205 var startTime = tasks[firstTaskIndex].startTime(); 206 var endTime = tasks[lastTaskIndex].endTime(); 207 var duration = endTime - startTime; 208 209 var contentHelper = new WebInspector.TimelinePopupContentHelper(info.name); 210 var durationText = WebInspector.UIString("%s (at %s)", Number.millisToString(duration, true), 211 Number.millisToString(startTime - model.minimumRecordTime(), true)); 212 contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText); 213 contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.millisToString(cpuTime, true)); 214 contentHelper.appendTextRow(WebInspector.UIString("Message Count"), messageCount); 215 return contentHelper.contentTable(); 216 } 217 218 /** 219 * @param {!Object} aggregatedStats 220 */ 221 WebInspector.TimelineUIUtils._generateAggregatedInfo = function(aggregatedStats) 222 { 223 var cell = document.createElement("span"); 224 cell.className = "timeline-aggregated-info"; 225 for (var index in aggregatedStats) { 226 var label = document.createElement("div"); 227 label.className = "timeline-aggregated-category timeline-" + index; 228 cell.appendChild(label); 229 var text = document.createElement("span"); 230 text.textContent = Number.millisToString(aggregatedStats[index], true); 231 cell.appendChild(text); 232 } 233 return cell; 234 } 235 236 /** 237 * @param {!Object} aggregatedStats 238 * @param {!WebInspector.TimelineCategory=} selfCategory 239 * @param {number=} selfTime 240 * @return {!Element} 241 */ 242 WebInspector.TimelineUIUtils.generatePieChart = function(aggregatedStats, selfCategory, selfTime) 243 { 244 var element = document.createElement("div"); 245 element.className = "timeline-aggregated-info"; 246 247 var total = 0; 248 for (var categoryName in aggregatedStats) 249 total += aggregatedStats[categoryName]; 250 251 function formatter(value) 252 { 253 return Number.millisToString(value, true); 254 } 255 var pieChart = new WebInspector.PieChart(100, formatter); 256 pieChart.setTotal(total); 257 element.appendChild(pieChart.element); 258 var footerElement = element.createChild("div", "timeline-aggregated-info-legend"); 259 260 // In case of self time, first add self, then children of the same category. 261 if (selfCategory && selfTime) { 262 // Self. 263 pieChart.addSlice(selfTime, selfCategory.fillColorStop1); 264 var rowElement = footerElement.createChild("div"); 265 rowElement.createChild("div", "timeline-aggregated-category timeline-" + selfCategory.name); 266 rowElement.createTextChild(WebInspector.UIString("%s %s (Self)", formatter(selfTime), selfCategory.title)); 267 268 // Children of the same category. 269 var categoryTime = aggregatedStats[selfCategory.name]; 270 var value = categoryTime - selfTime; 271 if (value > 0) { 272 pieChart.addSlice(value, selfCategory.fillColorStop0); 273 rowElement = footerElement.createChild("div"); 274 rowElement.createChild("div", "timeline-aggregated-category timeline-" + selfCategory.name); 275 rowElement.createTextChild(WebInspector.UIString("%s %s (Children)", formatter(value), selfCategory.title)); 276 } 277 } 278 279 // Add other categories. 280 for (var categoryName in WebInspector.TimelineUIUtils.categories()) { 281 var category = WebInspector.TimelineUIUtils.categories()[categoryName]; 282 if (category === selfCategory) 283 continue; 284 var value = aggregatedStats[category.name]; 285 if (!value) 286 continue; 287 pieChart.addSlice(value, category.fillColorStop0); 288 var rowElement = footerElement.createChild("div"); 289 rowElement.createChild("div", "timeline-aggregated-category timeline-" + category.name); 290 rowElement.createTextChild(WebInspector.UIString("%s %s", formatter(value), category.title)); 291 } 292 return element; 293 } 294 295 /** 296 * @param {!WebInspector.TimelineFrameModel} frameModel 297 * @param {!WebInspector.TimelineFrame} frame 298 * @return {!Element} 299 */ 300 WebInspector.TimelineUIUtils.generateDetailsContentForFrame = function(frameModel, frame) 301 { 302 var contentHelper = new WebInspector.TimelineDetailsContentHelper(null, null, true); 303 var durationInMillis = frame.endTime - frame.startTime; 304 var durationText = WebInspector.UIString("%s (at %s)", Number.millisToString(frame.endTime - frame.startTime, true), 305 Number.millisToString(frame.startTimeOffset, true)); 306 contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText); 307 contentHelper.appendTextRow(WebInspector.UIString("FPS"), Math.floor(1000 / durationInMillis)); 308 contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.millisToString(frame.cpuTime, true)); 309 contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"), 310 WebInspector.TimelineUIUtils._generateAggregatedInfo(frame.timeByCategory)); 311 if (Runtime.experiments.isEnabled("layersPanel") && frame.layerTree) { 312 contentHelper.appendElementRow(WebInspector.UIString("Layer tree"), 313 WebInspector.Linkifier.linkifyUsingRevealer(frame.layerTree, WebInspector.UIString("show"))); 314 } 315 return contentHelper.element; 316 } 317 318 /** 319 * @param {!CanvasRenderingContext2D} context 320 * @param {number} width 321 * @param {number} height 322 * @param {string} color0 323 * @param {string} color1 324 * @param {string} color2 325 * @return {!CanvasGradient} 326 */ 327 WebInspector.TimelineUIUtils.createFillStyle = function(context, width, height, color0, color1, color2) 328 { 329 var gradient = context.createLinearGradient(0, 0, width, height); 330 gradient.addColorStop(0, color0); 331 gradient.addColorStop(0.25, color1); 332 gradient.addColorStop(0.75, color1); 333 gradient.addColorStop(1, color2); 334 return gradient; 335 } 336 337 /** 338 * @param {!CanvasRenderingContext2D} context 339 * @param {number} width 340 * @param {number} height 341 * @param {!WebInspector.TimelineCategory} category 342 * @return {!CanvasGradient} 343 */ 344 WebInspector.TimelineUIUtils.createFillStyleForCategory = function(context, width, height, category) 345 { 346 return WebInspector.TimelineUIUtils.createFillStyle(context, width, height, category.fillColorStop0, category.fillColorStop1, category.borderColor); 347 } 348 349 /** 350 * @param {!WebInspector.TimelineCategory} category 351 * @return {string} 352 */ 353 WebInspector.TimelineUIUtils.createStyleRuleForCategory = function(category) 354 { 355 var selector = ".timeline-category-" + category.name + " .timeline-graph-bar, " + 356 ".panel.timeline .timeline-filters-header .filter-checkbox-filter.filter-checkbox-filter-" + category.name + " .checkbox-filter-checkbox, " + 357 ".timeline-details-view .timeline-" + category.name + ", " + 358 ".timeline-category-" + category.name + " .timeline-tree-icon" 359 360 return selector + " { background-image: linear-gradient(" + 361 category.fillColorStop0 + ", " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + ");" + 362 " border-color: " + category.borderColor + 363 "}"; 364 } 365 366 /** 367 * @param {!Array.<number>} quad 368 * @return {number} 369 */ 370 WebInspector.TimelineUIUtils.quadWidth = function(quad) 371 { 372 return Math.round(Math.sqrt(Math.pow(quad[0] - quad[2], 2) + Math.pow(quad[1] - quad[3], 2))); 373 } 374 375 /** 376 * @param {!Array.<number>} quad 377 * @return {number} 378 */ 379 WebInspector.TimelineUIUtils.quadHeight = function(quad) 380 { 381 return Math.round(Math.sqrt(Math.pow(quad[0] - quad[6], 2) + Math.pow(quad[1] - quad[7], 2))); 382 } 383 384 /** 385 * @constructor 386 * @extends {WebInspector.Object} 387 * @param {string} name 388 * @param {string} title 389 * @param {number} overviewStripGroupIndex 390 * @param {string} borderColor 391 * @param {string} backgroundColor 392 * @param {string} fillColorStop0 393 * @param {string} fillColorStop1 394 */ 395 WebInspector.TimelineCategory = function(name, title, overviewStripGroupIndex, borderColor, backgroundColor, fillColorStop0, fillColorStop1) 396 { 397 this.name = name; 398 this.title = title; 399 this.overviewStripGroupIndex = overviewStripGroupIndex; 400 this.borderColor = borderColor; 401 this.backgroundColor = backgroundColor; 402 this.fillColorStop0 = fillColorStop0; 403 this.fillColorStop1 = fillColorStop1; 404 this.hidden = false; 405 } 406 407 WebInspector.TimelineCategory.Events = { 408 VisibilityChanged: "VisibilityChanged" 409 }; 410 411 WebInspector.TimelineCategory.prototype = { 412 /** 413 * @return {boolean} 414 */ 415 get hidden() 416 { 417 return this._hidden; 418 }, 419 420 set hidden(hidden) 421 { 422 this._hidden = hidden; 423 this.dispatchEventToListeners(WebInspector.TimelineCategory.Events.VisibilityChanged, this); 424 }, 425 426 __proto__: WebInspector.Object.prototype 427 } 428 429 /** 430 * @constructor 431 * @param {string} title 432 */ 433 WebInspector.TimelinePopupContentHelper = function(title) 434 { 435 this._contentTable = document.createElement("table"); 436 var titleCell = this._createCell(WebInspector.UIString("%s - Details", title), "timeline-details-title"); 437 titleCell.colSpan = 2; 438 var titleRow = document.createElement("tr"); 439 titleRow.appendChild(titleCell); 440 this._contentTable.appendChild(titleRow); 441 } 442 443 WebInspector.TimelinePopupContentHelper.prototype = { 444 /** 445 * @return {!Element} 446 */ 447 contentTable: function() 448 { 449 return this._contentTable; 450 }, 451 452 /** 453 * @param {string|number} content 454 * @param {string=} styleName 455 */ 456 _createCell: function(content, styleName) 457 { 458 var text = document.createElement("label"); 459 text.createTextChild(String(content)); 460 var cell = document.createElement("td"); 461 cell.className = "timeline-details"; 462 if (styleName) 463 cell.className += " " + styleName; 464 cell.textContent = content; 465 return cell; 466 }, 467 468 /** 469 * @param {string} title 470 * @param {string|number} content 471 */ 472 appendTextRow: function(title, content) 473 { 474 var row = document.createElement("tr"); 475 row.appendChild(this._createCell(title, "timeline-details-row-title")); 476 row.appendChild(this._createCell(content, "timeline-details-row-data")); 477 this._contentTable.appendChild(row); 478 }, 479 480 /** 481 * @param {string} title 482 * @param {!Node|string} content 483 */ 484 appendElementRow: function(title, content) 485 { 486 var row = document.createElement("tr"); 487 var titleCell = this._createCell(title, "timeline-details-row-title"); 488 row.appendChild(titleCell); 489 var cell = document.createElement("td"); 490 cell.className = "details"; 491 if (content instanceof Node) 492 cell.appendChild(content); 493 else 494 cell.createTextChild(content || ""); 495 row.appendChild(cell); 496 this._contentTable.appendChild(row); 497 } 498 } 499 500 /** 501 * @constructor 502 * @param {?WebInspector.Target} target 503 * @param {?WebInspector.Linkifier} linkifier 504 * @param {boolean} monospaceValues 505 */ 506 WebInspector.TimelineDetailsContentHelper = function(target, linkifier, monospaceValues) 507 { 508 this._linkifier = linkifier; 509 this._target = target; 510 this.element = document.createElement("div"); 511 this.element.className = "timeline-details-view-block"; 512 this._monospaceValues = monospaceValues; 513 } 514 515 WebInspector.TimelineDetailsContentHelper.prototype = { 516 /** 517 * @param {string} title 518 * @param {string|number|boolean} value 519 */ 520 appendTextRow: function(title, value) 521 { 522 var rowElement = this.element.createChild("div", "timeline-details-view-row"); 523 rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title); 524 rowElement.createChild("span", "timeline-details-view-row-value" + (this._monospaceValues ? " monospace" : "")).textContent = value; 525 }, 526 527 /** 528 * @param {string} title 529 * @param {!Node|string} content 530 */ 531 appendElementRow: function(title, content) 532 { 533 var rowElement = this.element.createChild("div", "timeline-details-view-row"); 534 rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title); 535 var valueElement = rowElement.createChild("span", "timeline-details-view-row-details" + (this._monospaceValues ? " monospace" : "")); 536 if (content instanceof Node) 537 valueElement.appendChild(content); 538 else 539 valueElement.createTextChild(content || ""); 540 }, 541 542 /** 543 * @param {string} title 544 * @param {string} url 545 * @param {number} line 546 */ 547 appendLocationRow: function(title, url, line) 548 { 549 if (!this._linkifier || !this._target) 550 return; 551 this.appendElementRow(title, this._linkifier.linkifyScriptLocation(this._target, null, url, line - 1) || ""); 552 }, 553 554 /** 555 * @param {string} title 556 * @param {!Array.<!ConsoleAgent.CallFrame>} stackTrace 557 */ 558 appendStackTrace: function(title, stackTrace) 559 { 560 if (!this._linkifier || !this._target) 561 return; 562 563 var rowElement = this.element.createChild("div", "timeline-details-view-row"); 564 rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title); 565 var stackTraceElement = rowElement.createChild("div", "timeline-details-view-row-stack-trace monospace"); 566 567 for (var i = 0; i < stackTrace.length; ++i) { 568 var stackFrame = stackTrace[i]; 569 var row = stackTraceElement.createChild("div"); 570 row.createTextChild(stackFrame.functionName || WebInspector.UIString("(anonymous function)")); 571 row.createTextChild(" @ "); 572 var urlElement = this._linkifier.linkifyScriptLocation(this._target, stackFrame.scriptId, stackFrame.url, stackFrame.lineNumber - 1, stackFrame.columnNumber - 1); 573 row.appendChild(urlElement); 574 } 575 } 576 } 577