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 */
35WebInspector.TimelineModel = function()
36{
37    WebInspector.Object.call(this);
38    this._filters = [];
39}
40
41WebInspector.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
105WebInspector.TimelineModel.Events = {
106    RecordAdded: "RecordAdded",
107    RecordsCleared: "RecordsCleared",
108    RecordingStarted: "RecordingStarted",
109    RecordingStopped: "RecordingStopped",
110    RecordingProgress: "RecordingProgress",
111    RecordFilterChanged: "RecordFilterChanged"
112}
113
114WebInspector.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 */
122WebInspector.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
145WebInspector.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 */
374WebInspector.TimelineModel.Record = function()
375{
376}
377
378WebInspector.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 */
465WebInspector.TimelineModel.Filter = function()
466{
467    /** @type {!WebInspector.TimelineModel} */
468    this._model;
469}
470
471WebInspector.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 */
492WebInspector.TimelineRecordTypeFilter = function(recordTypes)
493{
494    WebInspector.TimelineModel.Filter.call(this);
495    this._recordTypes = recordTypes.keySet();
496}
497
498WebInspector.TimelineRecordTypeFilter.prototype = {
499    __proto__: WebInspector.TimelineModel.Filter.prototype
500}
501
502/**
503 * @constructor
504 * @extends {WebInspector.TimelineRecordTypeFilter}
505 * @param {!Array.<string>} recordTypes
506 */
507WebInspector.TimelineRecordHiddenEmptyTypeFilter = function(recordTypes)
508{
509    WebInspector.TimelineRecordTypeFilter.call(this, recordTypes);
510}
511
512WebInspector.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 */
530WebInspector.TimelineRecordHiddenTypeFilter = function(recordTypes)
531{
532    WebInspector.TimelineRecordTypeFilter.call(this, recordTypes);
533}
534
535WebInspector.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 */
553WebInspector.TimelineRecordVisibleTypeFilter = function(recordTypes)
554{
555    WebInspector.TimelineRecordTypeFilter.call(this, recordTypes);
556}
557
558WebInspector.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 */
574WebInspector.TimelineMergingRecordBuffer = function()
575{
576    this._backgroundRecordsBuffer = [];
577}
578
579/**
580 * @constructor
581 */
582WebInspector.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 */
615WebInspector.TimelineModelLoadFromFileDelegate = function(model, progress)
616{
617    this._model = model;
618    this._progress = progress;
619}
620
621WebInspector.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