Web Inspector: Embeddable Web Inspector
[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             try {
201                 var records = JSON.parse(data);
202                 parsingProgress.done();
203                 this.reset();
204                 processingProgress.setTotalWork(records.length);
205                 this._loadNextChunk(processingProgress, records, 1);
206             } catch (e) {
207                 WebInspector.showErrorMessage("Malformed timeline data.");
208                 progress.done();
209             }
210         }
211
212         function onLoad(e)
213         {
214             loadingProgress.done();
215             parsingProgress.setTotalWork(1);
216             setTimeout(parseAndImportData.bind(this, e.target.result), 0);
217         }
218
219         function onError(e)
220         {
221             progress.done();
222             switch(e.target.error.code) {
223             case e.target.error.NOT_FOUND_ERR:
224                 WebInspector.showErrorMessage(WebInspector.UIString("File \"%s\" not found.", file.name));
225             break;
226             case e.target.error.NOT_READABLE_ERR:
227                 WebInspector.showErrorMessage(WebInspector.UIString("File \"%s\" is not readable", file.name));
228             break;
229             case e.target.error.ABORT_ERR:
230                 break;
231             default:
232                 WebInspector.showErrorMessage(WebInspector.UIString("An error occurred while reading the file \"%s\"", file.name));
233             }
234         }
235
236         function onProgress(e)
237         {
238             if (e.lengthComputable)
239                 loadingProgress.setWorked(e.loaded / e.total);
240         }
241
242         var reader = new FileReader();
243         reader.onload = onLoad.bind(this);
244         reader.onerror = onError;
245         reader.onprogress = onProgress;
246         loadingProgress.setTitle(WebInspector.UIString("Loading\u2026"));
247         loadingProgress.setTotalWork(1);
248         reader.readAsText(file);
249     },
250
251     /**
252      * @param {string} url
253      */
254     loadFromURL: function(url, progress)
255     {
256         var compositeProgress = new WebInspector.CompositeProgress(progress);
257         var loadingProgress = compositeProgress.createSubProgress(1);
258         var parsingProgress = compositeProgress.createSubProgress(1);
259         var processingProgress = compositeProgress.createSubProgress(1);
260
261         // FIXME: extract parsing routines so that they did not require too many progress objects.
262         function parseAndImportData(data)
263         {
264             try {
265                 var records = JSON.parse(data);
266                 parsingProgress.done();
267                 this.reset();
268                 processingProgress.setTotalWork(records.length);
269                 this._loadNextChunk(processingProgress, records, 1);
270             } catch (e) {
271                 WebInspector.showErrorMessage("Malformed timeline data.");
272                 progress.done();
273             }
274         }
275
276         var responseText = loadXHR(url);
277         if (responseText) {
278             loadingProgress.done();
279             parsingProgress.setTotalWork(1);
280             setTimeout(parseAndImportData.bind(this, responseText), 0);
281         }
282     },
283
284     saveToFile: function()
285     {
286         var records = ['[' + JSON.stringify(new String(window.navigator.appVersion))];
287         for (var i = 0; i < this._records.length; ++i)
288             records.push(JSON.stringify(this._records[i]));
289
290         records[records.length - 1] = records[records.length - 1] + "]";
291
292         var now = new Date();
293         var fileName = "TimelineRawData-" + now.toISO8601Compact() + ".json";
294         WebInspector.fileManager.save(fileName, records.join(",\n"), true);
295
296     },
297
298     reset: function()
299     {
300         this._records = [];
301         this._stringPool.reset();
302         this._minimumRecordTime = -1;
303         this._maximumRecordTime = -1;
304         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordsCleared);
305     },
306
307     minimumRecordTime: function()
308     {
309         return this._minimumRecordTime;
310     },
311
312     maximumRecordTime: function()
313     {
314         return this._maximumRecordTime;
315     },
316
317     _updateBoundaries: function(record)
318     {
319         var startTime = WebInspector.TimelineModel.startTimeInSeconds(record);
320         var endTime = WebInspector.TimelineModel.endTimeInSeconds(record);
321
322         if (this._minimumRecordTime === -1 || startTime < this._minimumRecordTime)
323             this._minimumRecordTime = startTime;
324         if (this._maximumRecordTime === -1 || endTime > this._maximumRecordTime)
325             this._maximumRecordTime = endTime;
326     },
327
328     /**
329      * @param {Object} rawRecord
330      */
331     recordOffsetInSeconds: function(rawRecord)
332     {
333         return WebInspector.TimelineModel.startTimeInSeconds(rawRecord) - this._minimumRecordTime;
334     }
335 }
336
337 WebInspector.TimelineModel.prototype.__proto__ = WebInspector.Object.prototype;