77a5b39f82e88987499a6d7ad58a49ba17714fa6
[WebKit-https.git] / Source / WebCore / inspector / front-end / TimelineModel.js
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     this._records = [];
38     this._stringPool = new StringPool();
39     this._minimumRecordTime = -1;
40     this._maximumRecordTime = -1;
41     this._collectionEnabled = false;
42
43     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, this._onRecordAdded, this);
44 }
45
46 WebInspector.TimelineModel.RecordType = {
47     Root: "Root",
48     Program: "Program",
49     EventDispatch: "EventDispatch",
50
51     BeginFrame: "BeginFrame",
52     Layout: "Layout",
53     RecalculateStyles: "RecalculateStyles",
54     Paint: "Paint",
55     DecodeImage: "DecodeImage",
56     ResizeImage: "ResizeImage",
57     CompositeLayers: "CompositeLayers",
58
59     ParseHTML: "ParseHTML",
60
61     TimerInstall: "TimerInstall",
62     TimerRemove: "TimerRemove",
63     TimerFire: "TimerFire",
64
65     XHRReadyStateChange: "XHRReadyStateChange",
66     XHRLoad: "XHRLoad",
67     EvaluateScript: "EvaluateScript",
68
69     MarkLoad: "MarkLoad",
70     MarkDOMContent: "MarkDOMContent",
71
72     TimeStamp: "TimeStamp",
73     Time: "Time",
74     TimeEnd: "TimeEnd",
75
76     ScheduleResourceRequest: "ScheduleResourceRequest",
77     ResourceSendRequest: "ResourceSendRequest",
78     ResourceReceiveResponse: "ResourceReceiveResponse",
79     ResourceReceivedData: "ResourceReceivedData",
80     ResourceFinish: "ResourceFinish",
81
82     FunctionCall: "FunctionCall",
83     GCEvent: "GCEvent",
84
85     RequestAnimationFrame: "RequestAnimationFrame",
86     CancelAnimationFrame: "CancelAnimationFrame",
87     FireAnimationFrame: "FireAnimationFrame"
88 }
89
90 WebInspector.TimelineModel.Events = {
91     RecordAdded: "RecordAdded",
92     RecordsCleared: "RecordsCleared"
93 }
94
95 WebInspector.TimelineModel.startTimeInSeconds = function(record)
96 {
97     return record.startTime / 1000;
98 }
99
100 WebInspector.TimelineModel.endTimeInSeconds = function(record)
101 {
102     return (typeof record.endTime === "undefined" ? record.startTime : record.endTime) / 1000;
103 }
104
105 WebInspector.TimelineModel.durationInSeconds = function(record)
106 {
107     return WebInspector.TimelineModel.endTimeInSeconds(record) - WebInspector.TimelineModel.startTimeInSeconds(record);
108 }
109
110 /**
111  * @param {Object} total
112  * @param {Object} rawRecord
113  */
114 WebInspector.TimelineModel.aggregateTimeForRecord = function(total, rawRecord)
115 {
116     var childrenTime = 0;
117     var children = rawRecord["children"] || [];
118     for (var i = 0; i < children.length; ++i)  {
119         WebInspector.TimelineModel.aggregateTimeForRecord(total, children[i]);
120         childrenTime += WebInspector.TimelineModel.durationInSeconds(children[i]);
121     }
122     var categoryName = WebInspector.TimelinePresentationModel.recordStyle(rawRecord).category.name;
123     var ownTime = WebInspector.TimelineModel.durationInSeconds(rawRecord) - childrenTime;
124     total[categoryName] = (total[categoryName] || 0) + ownTime;
125 }
126
127 WebInspector.TimelineModel.prototype = {
128     startRecord: function()
129     {
130         if (this._collectionEnabled)
131             return;
132         this.reset();
133         WebInspector.timelineManager.start(30);
134         this._collectionEnabled = true;
135     },
136
137     stopRecord: function()
138     {
139         if (!this._collectionEnabled)
140             return;
141         WebInspector.timelineManager.stop();
142         this._collectionEnabled = false;
143     },
144
145     get records()
146     {
147         return this._records;
148     },
149
150     _onRecordAdded: function(event)
151     {
152         if (this._collectionEnabled)
153             this._addRecord(event.data);
154     },
155
156     _addRecord: function(record)
157     {
158         this._stringPool.internObjectStrings(record);
159         this._records.push(record);
160         this._updateBoundaries(record);
161         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record);
162     },
163
164     /**
165      * @param {WebInspector.Progress} progress
166      * @param {Array.<Object>} data
167      * @param {number} index
168      */
169     _loadNextChunk: function(progress, data, index)
170     {
171         if (progress.isCanceled()) {
172             this.reset();
173             progress.done();
174             return;
175         }
176         progress.setWorked(index);
177
178         for (var i = 0; i < 100 && index < data.length; ++i, ++index)
179             this._addRecord(data[index]);
180
181         if (index !== data.length)
182             setTimeout(this._loadNextChunk.bind(this, progress, data, index), 0);
183         else
184             progress.done();
185     },
186
187     /**
188      * @param {!Blob} file
189      * @param {WebInspector.Progress} progress
190      */
191     loadFromFile: function(file, progress)
192     {
193         var compositeProgress = new WebInspector.CompositeProgress(progress);
194         var loadingProgress = compositeProgress.createSubProgress(1);
195         var parsingProgress = compositeProgress.createSubProgress(1);
196         var processingProgress = compositeProgress.createSubProgress(1);
197
198         function parseAndImportData(data)
199         {
200             var records = JSON.parse(data);
201             parsingProgress.done();
202             this.reset();
203             processingProgress.setTotalWork(records.length);
204             this._loadNextChunk(processingProgress, records, 1);
205         }
206
207         function onLoad(e)
208         {
209             loadingProgress.done();
210             parsingProgress.setTotalWork(1);
211             setTimeout(parseAndImportData.bind(this, e.target.result), 0);
212         }
213
214         function onError(e)
215         {
216             progress.done();
217             switch(e.target.error.code) {
218             case e.target.error.NOT_FOUND_ERR:
219                 WebInspector.log(WebInspector.UIString('Timeline.loadFromFile: File "%s" not found.', file.name));
220             break;
221             case e.target.error.NOT_READABLE_ERR:
222                 WebInspector.log(WebInspector.UIString('Timeline.loadFromFile: File "%s" is not readable', file.name));
223             break;
224             case e.target.error.ABORT_ERR:
225                 break;
226             default:
227                 WebInspector.log(WebInspector.UIString('Timeline.loadFromFile: An error occurred while reading the file "%s"', file.name));
228             }
229         }
230
231         function onProgress(e)
232         {
233             if (e.lengthComputable)
234                 loadingProgress.setWorked(e.loaded / e.total);
235         }
236
237         var reader = new FileReader();
238         reader.onload = onLoad.bind(this);
239         reader.onerror = onError;
240         reader.onprogress = onProgress;
241         loadingProgress.setTitle(WebInspector.UIString("Loading\u2026"));
242         loadingProgress.setTotalWork(1);
243         reader.readAsText(file);
244     },
245
246     saveToFile: function()
247     {
248         var records = ['[' + JSON.stringify(new String(window.navigator.appVersion))];
249         for (var i = 0; i < this._records.length; ++i)
250             records.push(JSON.stringify(this._records[i]));
251
252         records[records.length - 1] = records[records.length - 1] + "]";
253
254         var now = new Date();
255         var fileName = "TimelineRawData-" + now.toISO8601Compact() + ".json";
256         WebInspector.fileManager.save(fileName, records.join(",\n"), true);
257
258     },
259
260     reset: function()
261     {
262         this._records = [];
263         this._stringPool.reset();
264         this._minimumRecordTime = -1;
265         this._maximumRecordTime = -1;
266         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordsCleared);
267     },
268
269     minimumRecordTime: function()
270     {
271         return this._minimumRecordTime;
272     },
273
274     maximumRecordTime: function()
275     {
276         return this._maximumRecordTime;
277     },
278
279     _updateBoundaries: function(record)
280     {
281         var startTime = WebInspector.TimelineModel.startTimeInSeconds(record);
282         var endTime = WebInspector.TimelineModel.endTimeInSeconds(record);
283
284         if (this._minimumRecordTime === -1 || startTime < this._minimumRecordTime)
285             this._minimumRecordTime = startTime;
286         if (this._maximumRecordTime === -1 || endTime > this._maximumRecordTime)
287             this._maximumRecordTime = endTime;
288     },
289
290     /**
291      * @param {Object} rawRecord
292      */
293     recordOffsetInSeconds: function(rawRecord)
294     {
295         return WebInspector.TimelineModel.startTimeInSeconds(rawRecord) - this._minimumRecordTime;
296     }
297 }
298
299 WebInspector.TimelineModel.prototype.__proto__ = WebInspector.Object.prototype;