1 /* 2 * Copyright (C) 2012 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /** 32 * @constructor 33 * @extends {WebInspector.Object} 34 */ 35 WebInspector.TimelineModel = function() 36 { 37 WebInspector.Object.call(this); 38 this._filters = []; 39 } 40 41 WebInspector.TimelineModel.RecordType = { 42 Root: "Root", 43 Program: "Program", 44 EventDispatch: "EventDispatch", 45 46 GPUTask: "GPUTask", 47 48 RequestMainThreadFrame: "RequestMainThreadFrame", 49 BeginFrame: "BeginFrame", 50 ActivateLayerTree: "ActivateLayerTree", 51 DrawFrame: "DrawFrame", 52 ScheduleStyleRecalculation: "ScheduleStyleRecalculation", 53 RecalculateStyles: "RecalculateStyles", 54 InvalidateLayout: "InvalidateLayout", 55 Layout: "Layout", 56 UpdateLayerTree: "UpdateLayerTree", 57 PaintSetup: "PaintSetup", 58 Paint: "Paint", 59 Rasterize: "Rasterize", 60 ScrollLayer: "ScrollLayer", 61 DecodeImage: "DecodeImage", 62 ResizeImage: "ResizeImage", 63 CompositeLayers: "CompositeLayers", 64 65 ParseHTML: "ParseHTML", 66 67 TimerInstall: "TimerInstall", 68 TimerRemove: "TimerRemove", 69 TimerFire: "TimerFire", 70 71 XHRReadyStateChange: "XHRReadyStateChange", 72 XHRLoad: "XHRLoad", 73 EvaluateScript: "EvaluateScript", 74 75 MarkLoad: "MarkLoad", 76 MarkDOMContent: "MarkDOMContent", 77 MarkFirstPaint: "MarkFirstPaint", 78 79 TimeStamp: "TimeStamp", 80 ConsoleTime: "ConsoleTime", 81 82 ResourceSendRequest: "ResourceSendRequest", 83 ResourceReceiveResponse: "ResourceReceiveResponse", 84 ResourceReceivedData: "ResourceReceivedData", 85 ResourceFinish: "ResourceFinish", 86 87 FunctionCall: "FunctionCall", 88 GCEvent: "GCEvent", 89 JSFrame: "JSFrame", 90 91 UpdateCounters: "UpdateCounters", 92 93 RequestAnimationFrame: "RequestAnimationFrame", 94 CancelAnimationFrame: "CancelAnimationFrame", 95 FireAnimationFrame: "FireAnimationFrame", 96 97 WebSocketCreate : "WebSocketCreate", 98 WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest", 99 WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse", 100 WebSocketDestroy : "WebSocketDestroy", 101 102 EmbedderCallback : "EmbedderCallback", 103 } 104 105 WebInspector.TimelineModel.Events = { 106 RecordAdded: "RecordAdded", 107 RecordsCleared: "RecordsCleared", 108 RecordingStarted: "RecordingStarted", 109 RecordingStopped: "RecordingStopped", 110 RecordingProgress: "RecordingProgress", 111 RecordFilterChanged: "RecordFilterChanged" 112 } 113 114 WebInspector.TimelineModel.MainThreadName = "main"; 115 116 /** 117 * @param {!Array.<!WebInspector.TimelineModel.Record>} recordsArray 118 * @param {?function(!WebInspector.TimelineModel.Record)|?function(!WebInspector.TimelineModel.Record,number)} preOrderCallback 119 * @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)=} postOrderCallback 120 * @return {boolean} 121 */ 122 WebInspector.TimelineModel.forAllRecords = function(recordsArray, preOrderCallback, postOrderCallback) 123 { 124 /** 125 * @param {!Array.<!WebInspector.TimelineModel.Record>} records 126 * @param {number} depth 127 * @return {boolean} 128 */ 129 function processRecords(records, depth) 130 { 131 for (var i = 0; i < records.length; ++i) { 132 var record = records[i]; 133 if (preOrderCallback && preOrderCallback(record, depth)) 134 return true; 135 if (processRecords(record.children(), depth + 1)) 136 return true; 137 if (postOrderCallback && postOrderCallback(record, depth)) 138 return true; 139 } 140 return false; 141 } 142 return processRecords(recordsArray, 0); 143 } 144 145 WebInspector.TimelineModel.prototype = { 146 /** 147 * @param {boolean} captureStacks 148 * @param {boolean} captureMemory 149 * @param {boolean} capturePictures 150 */ 151 startRecording: function(captureStacks, captureMemory, capturePictures) 152 { 153 }, 154 155 stopRecording: function() 156 { 157 }, 158 159 /** 160 * @param {?function(!WebInspector.TimelineModel.Record)|?function(!WebInspector.TimelineModel.Record,number)} preOrderCallback 161 * @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)=} postOrderCallback 162 */ 163 forAllRecords: function(preOrderCallback, postOrderCallback) 164 { 165 WebInspector.TimelineModel.forAllRecords(this._records, preOrderCallback, postOrderCallback); 166 }, 167 168 /** 169 * @param {!WebInspector.TimelineModel.Filter} filter 170 */ 171 addFilter: function(filter) 172 { 173 this._filters.push(filter); 174 filter._model = this; 175 }, 176 177 /** 178 * @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)} callback 179 */ 180 forAllFilteredRecords: function(callback) 181 { 182 /** 183 * @param {!WebInspector.TimelineModel.Record} record 184 * @param {number} depth 185 * @this {WebInspector.TimelineModel} 186 * @return {boolean} 187 */ 188 function processRecord(record, depth) 189 { 190 var visible = this.isVisible(record); 191 if (visible) { 192 if (callback(record, depth)) 193 return true; 194 } 195 196 for (var i = 0; i < record.children().length; ++i) { 197 if (processRecord.call(this, record.children()[i], visible ? depth + 1 : depth)) 198 return true; 199 } 200 return false; 201 } 202 203 for (var i = 0; i < this._records.length; ++i) 204 processRecord.call(this, this._records[i], 0); 205 }, 206 207 /** 208 * @param {!WebInspector.TimelineModel.Record} record 209 * @return {boolean} 210 */ 211 isVisible: function(record) 212 { 213 for (var i = 0; i < this._filters.length; ++i) { 214 if (!this._filters[i].accept(record)) 215 return false; 216 } 217 return true; 218 }, 219 220 _filterChanged: function() 221 { 222 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordFilterChanged); 223 }, 224 225 /** 226 * @return {!Array.<!WebInspector.TimelineModel.Record>} 227 */ 228 records: function() 229 { 230 return this._records; 231 }, 232 233 /** 234 * @param {!Blob} file 235 * @param {!WebInspector.Progress} progress 236 */ 237 loadFromFile: function(file, progress) 238 { 239 var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress); 240 var fileReader = this._createFileReader(file, delegate); 241 var loader = this.createLoader(fileReader, progress); 242 fileReader.start(loader); 243 }, 244 245 /** 246 * @param {!WebInspector.ChunkedFileReader} fileReader 247 * @param {!WebInspector.Progress} progress 248 * @return {!WebInspector.OutputStream} 249 */ 250 createLoader: function(fileReader, progress) 251 { 252 throw new Error("Not implemented."); 253 }, 254 255 _createFileReader: function(file, delegate) 256 { 257 return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineModelImpl.TransferChunkLengthBytes, delegate); 258 }, 259 260 _createFileWriter: function() 261 { 262 return new WebInspector.FileOutputStream(); 263 }, 264 265 saveToFile: function() 266 { 267 var now = new Date(); 268 var fileName = "TimelineRawData-" + now.toISO8601Compact() + ".json"; 269 var stream = this._createFileWriter(); 270 271 /** 272 * @param {boolean} accepted 273 * @this {WebInspector.TimelineModel} 274 */ 275 function callback(accepted) 276 { 277 if (!accepted) 278 return; 279 this.writeToStream(stream); 280 } 281 stream.open(fileName, callback.bind(this)); 282 }, 283 284 /** 285 * @param {!WebInspector.OutputStream} stream 286 */ 287 writeToStream: function(stream) 288 { 289 throw new Error("Not implemented."); 290 }, 291 292 reset: function() 293 { 294 this._records = []; 295 this._minimumRecordTime = 0; 296 this._maximumRecordTime = 0; 297 /** @type {!Array.<!WebInspector.TimelineModel.Record>} */ 298 this._mainThreadTasks = []; 299 /** @type {!Array.<!WebInspector.TimelineModel.Record>} */ 300 this._gpuThreadTasks = []; 301 /** @type {!Array.<!WebInspector.TimelineModel.Record>} */ 302 this._eventDividerRecords = []; 303 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordsCleared); 304 }, 305 306 /** 307 * @return {number} 308 */ 309 minimumRecordTime: function() 310 { 311 return this._minimumRecordTime; 312 }, 313 314 /** 315 * @return {number} 316 */ 317 maximumRecordTime: function() 318 { 319 return this._maximumRecordTime; 320 }, 321 322 /** 323 * @return {boolean} 324 */ 325 isEmpty: function() 326 { 327 return this.minimumRecordTime() === 0 && this.maximumRecordTime() === 0; 328 }, 329 330 /** 331 * @param {!WebInspector.TimelineModel.Record} record 332 */ 333 _updateBoundaries: function(record) 334 { 335 var startTime = record.startTime(); 336 var endTime = record.endTime(); 337 338 if (!this._minimumRecordTime || startTime < this._minimumRecordTime) 339 this._minimumRecordTime = startTime; 340 if (endTime > this._maximumRecordTime) 341 this._maximumRecordTime = endTime; 342 }, 343 344 /** 345 * @return {!Array.<!WebInspector.TimelineModel.Record>} 346 */ 347 mainThreadTasks: function() 348 { 349 return this._mainThreadTasks; 350 }, 351 352 /** 353 * @return {!Array.<!WebInspector.TimelineModel.Record>} 354 */ 355 gpuThreadTasks: function() 356 { 357 return this._gpuThreadTasks; 358 }, 359 360 /** 361 * @return {!Array.<!WebInspector.TimelineModel.Record>} 362 */ 363 eventDividerRecords: function() 364 { 365 return this._eventDividerRecords; 366 }, 367 368 __proto__: WebInspector.Object.prototype 369 } 370 371 /** 372 * @interface 373 */ 374 WebInspector.TimelineModel.Record = function() 375 { 376 } 377 378 WebInspector.TimelineModel.Record.prototype = { 379 /** 380 * @return {?Array.<!ConsoleAgent.CallFrame>} 381 */ 382 callSiteStackTrace: function() { }, 383 384 /** 385 * @return {?WebInspector.TimelineModel.Record} 386 */ 387 initiator: function() { }, 388 389 /** 390 * @return {?WebInspector.Target} 391 */ 392 target: function() { }, 393 394 /** 395 * @return {number} 396 */ 397 selfTime: function() { }, 398 399 /** 400 * @return {!Array.<!WebInspector.TimelineModel.Record>} 401 */ 402 children: function() { }, 403 404 /** 405 * @return {number} 406 */ 407 startTime: function() { }, 408 409 /** 410 * @return {string} 411 */ 412 thread: function() { }, 413 414 /** 415 * @return {number} 416 */ 417 endTime: function() { }, 418 419 /** 420 * @param {number} endTime 421 */ 422 setEndTime: function(endTime) { }, 423 424 /** 425 * @return {!Object} 426 */ 427 data: function() { }, 428 429 /** 430 * @return {string} 431 */ 432 type: function() { }, 433 434 /** 435 * @return {string} 436 */ 437 frameId: function() { }, 438 439 /** 440 * @return {?Array.<!ConsoleAgent.CallFrame>} 441 */ 442 stackTrace: function() { }, 443 444 /** 445 * @param {string} key 446 * @return {?Object} 447 */ 448 getUserObject: function(key) { }, 449 450 /** 451 * @param {string} key 452 * @param {?Object|undefined} value 453 */ 454 setUserObject: function(key, value) { }, 455 456 /** 457 * @return {?Array.<string>} 458 */ 459 warnings: function() { } 460 } 461 462 /** 463 * @constructor 464 */ 465 WebInspector.TimelineModel.Filter = function() 466 { 467 /** @type {!WebInspector.TimelineModel} */ 468 this._model; 469 } 470 471 WebInspector.TimelineModel.Filter.prototype = { 472 /** 473 * @param {!WebInspector.TimelineModel.Record} record 474 * @return {boolean} 475 */ 476 accept: function(record) 477 { 478 return true; 479 }, 480 481 notifyFilterChanged: function() 482 { 483 this._model._filterChanged(); 484 } 485 } 486 487 /** 488 * @constructor 489 * @extends {WebInspector.TimelineModel.Filter} 490 * @param {!Array.<string>} recordTypes 491 */ 492 WebInspector.TimelineRecordTypeFilter = function(recordTypes) 493 { 494 WebInspector.TimelineModel.Filter.call(this); 495 this._recordTypes = recordTypes.keySet(); 496 } 497 498 WebInspector.TimelineRecordTypeFilter.prototype = { 499 __proto__: WebInspector.TimelineModel.Filter.prototype 500 } 501 502 /** 503 * @constructor 504 * @extends {WebInspector.TimelineRecordTypeFilter} 505 * @param {!Array.<string>} recordTypes 506 */ 507 WebInspector.TimelineRecordHiddenEmptyTypeFilter = function(recordTypes) 508 { 509 WebInspector.TimelineRecordTypeFilter.call(this, recordTypes); 510 } 511 512 WebInspector.TimelineRecordHiddenEmptyTypeFilter.prototype = { 513 /** 514 * @param {!WebInspector.TimelineModel.Record} record 515 * @return {boolean} 516 */ 517 accept: function(record) 518 { 519 return record.children().length !== 0 || !this._recordTypes[record.type()]; 520 }, 521 522 __proto__: WebInspector.TimelineRecordTypeFilter.prototype 523 } 524 525 /** 526 * @constructor 527 * @extends {WebInspector.TimelineRecordTypeFilter} 528 * @param {!Array.<string>} recordTypes 529 */ 530 WebInspector.TimelineRecordHiddenTypeFilter = function(recordTypes) 531 { 532 WebInspector.TimelineRecordTypeFilter.call(this, recordTypes); 533 } 534 535 WebInspector.TimelineRecordHiddenTypeFilter.prototype = { 536 /** 537 * @param {!WebInspector.TimelineModel.Record} record 538 * @return {boolean} 539 */ 540 accept: function(record) 541 { 542 return !this._recordTypes[record.type()]; 543 }, 544 545 __proto__: WebInspector.TimelineRecordTypeFilter.prototype 546 } 547 548 /** 549 * @constructor 550 * @extends {WebInspector.TimelineRecordTypeFilter} 551 * @param {!Array.<string>} recordTypes 552 */ 553 WebInspector.TimelineRecordVisibleTypeFilter = function(recordTypes) 554 { 555 WebInspector.TimelineRecordTypeFilter.call(this, recordTypes); 556 } 557 558 WebInspector.TimelineRecordVisibleTypeFilter.prototype = { 559 /** 560 * @param {!WebInspector.TimelineModel.Record} record 561 * @return {boolean} 562 */ 563 accept: function(record) 564 { 565 return !!this._recordTypes[record.type()]; 566 }, 567 568 __proto__: WebInspector.TimelineRecordTypeFilter.prototype 569 } 570 571 /** 572 * @constructor 573 */ 574 WebInspector.TimelineMergingRecordBuffer = function() 575 { 576 this._backgroundRecordsBuffer = []; 577 } 578 579 /** 580 * @constructor 581 */ 582 WebInspector.TimelineMergingRecordBuffer.prototype = { 583 /** 584 * @param {string} thread 585 * @param {!Array.<!WebInspector.TimelineModel.Record>} records 586 * @return {!Array.<!WebInspector.TimelineModel.Record>} 587 */ 588 process: function(thread, records) 589 { 590 if (thread !== WebInspector.TimelineModel.MainThreadName) { 591 this._backgroundRecordsBuffer = this._backgroundRecordsBuffer.concat(records); 592 return []; 593 } 594 /** 595 * @param {!WebInspector.TimelineModel.Record} a 596 * @param {!WebInspector.TimelineModel.Record} b 597 */ 598 function recordTimestampComparator(a, b) 599 { 600 // Never return 0, as the merge function will squash identical entries. 601 return a.startTime() < b.startTime() ? -1 : 1; 602 } 603 var result = this._backgroundRecordsBuffer.mergeOrdered(records, recordTimestampComparator); 604 this._backgroundRecordsBuffer = []; 605 return result; 606 } 607 } 608 609 /** 610 * @constructor 611 * @implements {WebInspector.OutputStreamDelegate} 612 * @param {!WebInspector.TimelineModel} model 613 * @param {!WebInspector.Progress} progress 614 */ 615 WebInspector.TimelineModelLoadFromFileDelegate = function(model, progress) 616 { 617 this._model = model; 618 this._progress = progress; 619 } 620 621 WebInspector.TimelineModelLoadFromFileDelegate.prototype = { 622 onTransferStarted: function() 623 { 624 this._progress.setTitle(WebInspector.UIString("Loading\u2026")); 625 }, 626 627 /** 628 * @param {!WebInspector.ChunkedReader} reader 629 */ 630 onChunkTransferred: function(reader) 631 { 632 if (this._progress.isCanceled()) { 633 reader.cancel(); 634 this._progress.done(); 635 this._model.reset(); 636 return; 637 } 638 639 var totalSize = reader.fileSize(); 640 if (totalSize) { 641 this._progress.setTotalWork(totalSize); 642 this._progress.setWorked(reader.loadedSize()); 643 } 644 }, 645 646 onTransferFinished: function() 647 { 648 this._progress.done(); 649 }, 650 651 /** 652 * @param {!WebInspector.ChunkedReader} reader 653 * @param {!Event} event 654 */ 655 onError: function(reader, event) 656 { 657 this._progress.done(); 658 this._model.reset(); 659 switch (event.target.error.code) { 660 case FileError.NOT_FOUND_ERR: 661 WebInspector.console.error(WebInspector.UIString("File \"%s\" not found.", reader.fileName())); 662 break; 663 case FileError.NOT_READABLE_ERR: 664 WebInspector.console.error(WebInspector.UIString("File \"%s\" is not readable", reader.fileName())); 665 break; 666 case FileError.ABORT_ERR: 667 break; 668 default: 669 WebInspector.console.error(WebInspector.UIString("An error occurred while reading the file \"%s\"", reader.fileName())); 670 } 671 } 672 } 673