Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / 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._filters = [];
38     this._bindings = new WebInspector.TimelineModel.InterRecordBindings();
39
40     this.reset();
41
42     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, this._onRecordAdded, this);
43     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineStarted, this._onStarted, this);
44     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineStopped, this._onStopped, this);
45     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineProgress, this._onProgress, this);
46 }
47
48 WebInspector.TimelineModel.TransferChunkLengthBytes = 5000000;
49
50 WebInspector.TimelineModel.RecordType = {
51     Root: "Root",
52     Program: "Program",
53     EventDispatch: "EventDispatch",
54
55     GPUTask: "GPUTask",
56
57     RequestMainThreadFrame: "RequestMainThreadFrame",
58     BeginFrame: "BeginFrame",
59     ActivateLayerTree: "ActivateLayerTree",
60     DrawFrame: "DrawFrame",
61     ScheduleStyleRecalculation: "ScheduleStyleRecalculation",
62     RecalculateStyles: "RecalculateStyles",
63     InvalidateLayout: "InvalidateLayout",
64     Layout: "Layout",
65     UpdateLayerTree: "UpdateLayerTree",
66     AutosizeText: "AutosizeText",
67     PaintSetup: "PaintSetup",
68     Paint: "Paint",
69     Rasterize: "Rasterize",
70     ScrollLayer: "ScrollLayer",
71     DecodeImage: "DecodeImage",
72     ResizeImage: "ResizeImage",
73     CompositeLayers: "CompositeLayers",
74
75     ParseHTML: "ParseHTML",
76
77     TimerInstall: "TimerInstall",
78     TimerRemove: "TimerRemove",
79     TimerFire: "TimerFire",
80
81     XHRReadyStateChange: "XHRReadyStateChange",
82     XHRLoad: "XHRLoad",
83     EvaluateScript: "EvaluateScript",
84
85     MarkLoad: "MarkLoad",
86     MarkDOMContent: "MarkDOMContent",
87     MarkFirstPaint: "MarkFirstPaint",
88
89     TimeStamp: "TimeStamp",
90     ConsoleTime: "ConsoleTime",
91
92     ScheduleResourceRequest: "ScheduleResourceRequest",
93     ResourceSendRequest: "ResourceSendRequest",
94     ResourceReceiveResponse: "ResourceReceiveResponse",
95     ResourceReceivedData: "ResourceReceivedData",
96     ResourceFinish: "ResourceFinish",
97
98     FunctionCall: "FunctionCall",
99     GCEvent: "GCEvent",
100
101     RequestAnimationFrame: "RequestAnimationFrame",
102     CancelAnimationFrame: "CancelAnimationFrame",
103     FireAnimationFrame: "FireAnimationFrame",
104
105     WebSocketCreate : "WebSocketCreate",
106     WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest",
107     WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse",
108     WebSocketDestroy : "WebSocketDestroy",
109
110     EmbedderCallback : "EmbedderCallback",
111 }
112
113 WebInspector.TimelineModel.Events = {
114     RecordAdded: "RecordAdded",
115     RecordsCleared: "RecordsCleared",
116     RecordingStarted: "RecordingStarted",
117     RecordingStopped: "RecordingStopped",
118     RecordingProgress: "RecordingProgress",
119     RecordFilterChanged: "RecordFilterChanged"
120 }
121
122 /**
123  * @param {!Array.<!WebInspector.TimelineModel.Record>} recordsArray
124  * @param {?function(!WebInspector.TimelineModel.Record)|?function(!WebInspector.TimelineModel.Record,number)} preOrderCallback
125  * @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)=} postOrderCallback
126  * @return {boolean}
127  */
128 WebInspector.TimelineModel.forAllRecords = function(recordsArray, preOrderCallback, postOrderCallback)
129 {
130     /**
131      * @param {!Array.<!WebInspector.TimelineModel.Record>} records
132      * @param {number} depth
133      * @return {boolean}
134      */
135     function processRecords(records, depth)
136     {
137         for (var i = 0; i < records.length; ++i) {
138             var record = records[i];
139             if (preOrderCallback && preOrderCallback(record, depth))
140                 return true;
141             if (processRecords(record.children, depth + 1))
142                 return true;
143             if (postOrderCallback && postOrderCallback(record, depth))
144                 return true;
145         }
146         return false;
147     }
148     return processRecords(recordsArray, 0);
149 }
150
151 WebInspector.TimelineModel.prototype = {
152     /**
153      * @param {?function(!WebInspector.TimelineModel.Record)|?function(!WebInspector.TimelineModel.Record,number)} preOrderCallback
154      * @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)=} postOrderCallback
155      */
156     forAllRecords: function(preOrderCallback, postOrderCallback)
157     {
158         WebInspector.TimelineModel.forAllRecords(this._records, preOrderCallback, postOrderCallback);
159     },
160
161     /**
162      * @param {!WebInspector.TimelineModel.Filter} filter
163      */
164     addFilter: function(filter)
165     {
166         this._filters.push(filter);
167         filter._model = this;
168     },
169
170     /**
171      * @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)} callback
172      */
173     forAllFilteredRecords: function(callback)
174     {
175         /**
176          * @param {!WebInspector.TimelineModel.Record} record
177          * @param {number} depth
178          * @this {WebInspector.TimelineModel}
179          * @return {boolean}
180          */
181         function processRecord(record, depth)
182         {
183             var visible = this.isVisible(record);
184             if (visible) {
185                 if (callback(record, depth))
186                     return true;
187             }
188
189             for (var i = 0; i < record.children.length; ++i) {
190                 if (processRecord.call(this, record.children[i], visible ? depth + 1 : depth))
191                     return true;
192             }
193             return false;
194         }
195
196         for (var i = 0; i < this._records.length; ++i)
197             processRecord.call(this, this._records[i], 0);
198     },
199
200     /**
201      * @param {!WebInspector.TimelineModel.Record} record
202      * @return {boolean}
203      */
204     isVisible: function(record)
205     {
206         for (var i = 0; i < this._filters.length; ++i) {
207             if (!this._filters[i].accept(record))
208                 return false;
209         }
210         return true;
211     },
212
213     _filterChanged: function()
214     {
215         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordFilterChanged);
216     },
217
218     startRecording: function()
219     {
220         this._clientInitiatedRecording = true;
221         this.reset();
222         var maxStackFrames = WebInspector.settings.timelineCaptureStacks.get() ? 30 : 0;
223         this._bufferEvents = WebInspector.experimentsSettings.timelineNoLiveUpdate.isEnabled();
224         var includeGPUEvents = WebInspector.experimentsSettings.gpuTimeline.isEnabled();
225         var liveEvents = [ WebInspector.TimelineModel.RecordType.BeginFrame,
226                            WebInspector.TimelineModel.RecordType.DrawFrame,
227                            WebInspector.TimelineModel.RecordType.RequestMainThreadFrame,
228                            WebInspector.TimelineModel.RecordType.ActivateLayerTree ];
229         var includeCounters = true;
230         WebInspector.timelineManager.start(maxStackFrames, this._bufferEvents, liveEvents.join(","), includeCounters, includeGPUEvents, this._fireRecordingStarted.bind(this));
231     },
232
233     stopRecording: function()
234     {
235         if (!this._clientInitiatedRecording) {
236             WebInspector.timelineManager.start(undefined, undefined, undefined, undefined, undefined, stopTimeline.bind(this));
237             return;
238         }
239
240         /**
241          * Console started this one and we are just sniffing it. Initiate recording so that we
242          * could stop it.
243          * @this {WebInspector.TimelineModel}
244          */
245         function stopTimeline()
246         {
247             WebInspector.timelineManager.stop(this._fireRecordingStopped.bind(this));
248         }
249
250         this._clientInitiatedRecording = false;
251         WebInspector.timelineManager.stop(this._fireRecordingStopped.bind(this));
252     },
253
254     /**
255      * @return {!Array.<!WebInspector.TimelineModel.Record>}
256      */
257     records: function()
258     {
259         return this._records;
260     },
261
262     /**
263      * @param {!WebInspector.Event} event
264      */
265     _onRecordAdded: function(event)
266     {
267         if (this._collectionEnabled)
268             this._addRecord(/** @type {!TimelineAgent.TimelineEvent} */(event.data));
269     },
270
271     /**
272      * @param {!WebInspector.Event} event
273      */
274     _onStarted: function(event)
275     {
276         if (event.data) {
277             // Started from console.
278             this._fireRecordingStarted();
279         }
280     },
281
282     /**
283      * @param {!WebInspector.Event} event
284      */
285     _onStopped: function(event)
286     {
287         if (event.data) {
288             // Stopped from console.
289             this._fireRecordingStopped(null);
290         }
291     },
292
293     /**
294      * @param {!WebInspector.Event} event
295      */
296     _onProgress: function(event)
297     {
298         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingProgress, event.data);
299     },
300
301     _fireRecordingStarted: function()
302     {
303         this._collectionEnabled = true;
304         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStarted);
305     },
306
307     /**
308      * @param {?Protocol.Error} error
309      * @param {!Array.<!TimelineAgent.TimelineEvent>=} events
310      */
311     _fireRecordingStopped: function(error, events)
312     {
313         this._bufferEvents = false;
314         this._collectionEnabled = false;
315         if (events && events.length) {
316             this.reset();
317             for (var i = 0; i < events.length; ++i)
318                 this._addRecord(events[i]);
319         }
320         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped);
321     },
322
323     /**
324      * @return {boolean}
325      */
326     bufferEvents: function()
327     {
328         return this._bufferEvents;
329     },
330
331     /**
332      * @param {!TimelineAgent.TimelineEvent} payload
333      */
334     _addRecord: function(payload)
335     {
336         this._internStrings(payload);
337         this._payloads.push(payload);
338         this._updateBoundaries(payload);
339
340         var record = this._innerAddRecord(payload, null);
341         this._records.push(record);
342         if (record.type === WebInspector.TimelineModel.RecordType.Program)
343             this._mainThreadTasks.push(record);
344         if (record.type === WebInspector.TimelineModel.RecordType.GPUTask)
345             this._gpuThreadTasks.push(record);
346
347         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record);
348     },
349
350     /**
351      * @param {!TimelineAgent.TimelineEvent} payload
352      * @param {?WebInspector.TimelineModel.Record} parentRecord
353      * @return {!WebInspector.TimelineModel.Record}
354      * @this {!WebInspector.TimelineModel}
355      */
356     _innerAddRecord: function(payload, parentRecord)
357     {
358         var record = new WebInspector.TimelineModel.Record(this, payload, parentRecord);
359         if (WebInspector.TimelineUIUtils.isEventDivider(record))
360             this._eventDividerRecords.push(record);
361
362         for (var i = 0; payload.children && i < payload.children.length; ++i)
363             this._innerAddRecord.call(this, payload.children[i], record);
364
365         record.calculateAggregatedStats();
366         if (parentRecord)
367             parentRecord._selfTime -= record.endTime - record.startTime;
368         return record;
369     },
370
371     /**
372      * @param {!Blob} file
373      * @param {!WebInspector.Progress} progress
374      */
375     loadFromFile: function(file, progress)
376     {
377         var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress);
378         var fileReader = this._createFileReader(file, delegate);
379         var loader = new WebInspector.TimelineModelLoader(this, fileReader, progress);
380         fileReader.start(loader);
381     },
382
383     /**
384      * @param {string} url
385      */
386     loadFromURL: function(url, progress)
387     {
388         var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress);
389         var urlReader = new WebInspector.ChunkedXHRReader(url, delegate);
390         var loader = new WebInspector.TimelineModelLoader(this, urlReader, progress);
391         urlReader.start(loader);
392     },
393
394     _createFileReader: function(file, delegate)
395     {
396         return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineModel.TransferChunkLengthBytes, delegate);
397     },
398
399     _createFileWriter: function()
400     {
401         return new WebInspector.FileOutputStream();
402     },
403
404     saveToFile: function()
405     {
406         var now = new Date();
407         var fileName = "TimelineRawData-" + now.toISO8601Compact() + ".json";
408         var stream = this._createFileWriter();
409
410         /**
411          * @param {boolean} accepted
412          * @this {WebInspector.TimelineModel}
413          */
414         function callback(accepted)
415         {
416             if (!accepted)
417                 return;
418             var saver = new WebInspector.TimelineSaver(stream);
419             saver.save(this._payloads, window.navigator.appVersion);
420         }
421         stream.open(fileName, callback.bind(this));
422     },
423
424     reset: function()
425     {
426         this._records = [];
427         this._payloads = [];
428         this._stringPool = {};
429         this._minimumRecordTime = -1;
430         this._maximumRecordTime = -1;
431         this._bindings._reset();
432         /** @type {!Array.<!WebInspector.TimelineModel.Record>} */
433         this._mainThreadTasks =  [];
434         /** @type {!Array.<!WebInspector.TimelineModel.Record>} */
435         this._gpuThreadTasks = [];
436         /** @type {!Array.<!WebInspector.TimelineModel.Record>} */
437         this._eventDividerRecords = [];
438         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordsCleared);
439     },
440
441     /**
442      * @return {number}
443      */
444     minimumRecordTime: function()
445     {
446         return this._minimumRecordTime;
447     },
448
449     /**
450      * @return {number}
451      */
452     maximumRecordTime: function()
453     {
454         return this._maximumRecordTime;
455     },
456
457     /**
458      * @param {!TimelineAgent.TimelineEvent} record
459      */
460     _updateBoundaries: function(record)
461     {
462         var startTime = record.startTime;
463         var endTime = record.endTime;
464
465         if (this._minimumRecordTime === -1 || startTime < this._minimumRecordTime)
466             this._minimumRecordTime = startTime;
467         if ((this._maximumRecordTime === -1 && endTime) || endTime > this._maximumRecordTime)
468             this._maximumRecordTime = endTime;
469     },
470
471     /**
472      * @return {!Array.<!WebInspector.TimelineModel.Record>}
473      */
474     mainThreadTasks: function()
475     {
476         return this._mainThreadTasks;
477     },
478
479     /**
480      * @return {!Array.<!WebInspector.TimelineModel.Record>}
481      */
482     gpuThreadTasks: function()
483     {
484         return this._gpuThreadTasks;
485     },
486
487     /**
488      * @return {!Array.<!WebInspector.TimelineModel.Record>}
489      */
490     eventDividerRecords: function()
491     {
492         return this._eventDividerRecords;
493     },
494
495     /**
496      * @param {!TimelineAgent.TimelineEvent} record
497      */
498     _internStrings: function(record)
499     {
500         for (var name in record) {
501             var value = record[name];
502             if (typeof value !== "string")
503                 continue;
504
505             var interned = this._stringPool[value];
506             if (typeof interned === "string")
507                 record[name] = interned;
508             else
509                 this._stringPool[value] = value;
510         }
511
512         var children = record.children;
513         for (var i = 0; children && i < children.length; ++i)
514             this._internStrings(children[i]);
515     },
516
517     __proto__: WebInspector.Object.prototype
518 }
519
520
521 /**
522  * @constructor
523  */
524 WebInspector.TimelineModel.InterRecordBindings = function() {
525     this._reset();
526 }
527
528 WebInspector.TimelineModel.InterRecordBindings.prototype = {
529     _reset: function()
530     {
531         this._sendRequestRecords = {};
532         this._timerRecords = {};
533         this._requestAnimationFrameRecords = {};
534         this._layoutInvalidateStack = {};
535         this._lastScheduleStyleRecalculation = {};
536         this._webSocketCreateRecords = {};
537     }
538 }
539
540 /**
541  * @constructor
542  * @param {!WebInspector.TimelineModel} model
543  * @param {!TimelineAgent.TimelineEvent} record
544  * @param {?WebInspector.TimelineModel.Record} parentRecord
545  */
546 WebInspector.TimelineModel.Record = function(model, record, parentRecord)
547 {
548     this._model = model;
549     var bindings = this._model._bindings;
550     this._aggregatedStats = {};
551     this._record = record;
552     this._children = [];
553     if (parentRecord) {
554         this.parent = parentRecord;
555         parentRecord.children.push(this);
556     }
557
558     this._selfTime = this.endTime - this.startTime;
559     this._lastChildEndTime = this.endTime;
560     this._startTimeOffset = this.startTime - model.minimumRecordTime();
561
562     if (record.data) {
563         if (record.data["url"])
564             this.url = record.data["url"];
565         if (record.data["rootNode"])
566             this._relatedBackendNodeId = record.data["rootNode"];
567         else if (record.data["elementId"])
568             this._relatedBackendNodeId = record.data["elementId"];
569         if (record.data["scriptName"]) {
570             this.scriptName = record.data["scriptName"];
571             this.scriptLine = record.data["scriptLine"];
572         }
573     }
574
575     if (parentRecord && parentRecord.callSiteStackTrace)
576         this.callSiteStackTrace = parentRecord.callSiteStackTrace;
577
578     var recordTypes = WebInspector.TimelineModel.RecordType;
579     switch (record.type) {
580     case recordTypes.ResourceSendRequest:
581         // Make resource receive record last since request was sent; make finish record last since response received.
582         bindings._sendRequestRecords[record.data["requestId"]] = this;
583         break;
584
585     case recordTypes.ResourceReceiveResponse:
586         var sendRequestRecord = bindings._sendRequestRecords[record.data["requestId"]];
587         if (sendRequestRecord) // False if we started instrumentation in the middle of request.
588             this.url = sendRequestRecord.url;
589         break;
590
591     case recordTypes.ResourceReceivedData:
592     case recordTypes.ResourceFinish:
593         var sendRequestRecord = bindings._sendRequestRecords[record.data["requestId"]];
594         if (sendRequestRecord) // False for main resource.
595             this.url = sendRequestRecord.url;
596         break;
597
598     case recordTypes.TimerInstall:
599         this.timeout = record.data["timeout"];
600         this.singleShot = record.data["singleShot"];
601         bindings._timerRecords[record.data["timerId"]] = this;
602         break;
603
604     case recordTypes.TimerFire:
605         var timerInstalledRecord = bindings._timerRecords[record.data["timerId"]];
606         if (timerInstalledRecord) {
607             this.callSiteStackTrace = timerInstalledRecord.stackTrace;
608             this.timeout = timerInstalledRecord.timeout;
609             this.singleShot = timerInstalledRecord.singleShot;
610         }
611         break;
612
613     case recordTypes.RequestAnimationFrame:
614         bindings._requestAnimationFrameRecords[record.data["id"]] = this;
615         break;
616
617     case recordTypes.FireAnimationFrame:
618         var requestAnimationRecord = bindings._requestAnimationFrameRecords[record.data["id"]];
619         if (requestAnimationRecord)
620             this.callSiteStackTrace = requestAnimationRecord.stackTrace;
621         break;
622
623     case recordTypes.ConsoleTime:
624         var message = record.data["message"];
625         break;
626
627     case recordTypes.ScheduleStyleRecalculation:
628         bindings._lastScheduleStyleRecalculation[this.frameId] = this;
629         break;
630
631     case recordTypes.RecalculateStyles:
632         var scheduleStyleRecalculationRecord = bindings._lastScheduleStyleRecalculation[this.frameId];
633         if (!scheduleStyleRecalculationRecord)
634             break;
635         this.callSiteStackTrace = scheduleStyleRecalculationRecord.stackTrace;
636         break;
637
638     case recordTypes.InvalidateLayout:
639         // Consider style recalculation as a reason for layout invalidation,
640         // but only if we had no earlier layout invalidation records.
641         var styleRecalcStack;
642         if (!bindings._layoutInvalidateStack[this.frameId]) {
643             if (parentRecord.type === recordTypes.RecalculateStyles)
644                 styleRecalcStack = parentRecord.callSiteStackTrace;
645         }
646         bindings._layoutInvalidateStack[this.frameId] = styleRecalcStack || this.stackTrace;
647         break;
648
649     case recordTypes.Layout:
650         var layoutInvalidateStack = bindings._layoutInvalidateStack[this.frameId];
651         if (layoutInvalidateStack)
652             this.callSiteStackTrace = layoutInvalidateStack;
653         if (this.stackTrace)
654             this.addWarning(WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck."));
655
656         bindings._layoutInvalidateStack[this.frameId] = null;
657         this.highlightQuad = record.data.root || WebInspector.TimelineModel._quadFromRectData(record.data);
658         this._relatedBackendNodeId = record.data["rootNode"];
659         break;
660
661     case recordTypes.AutosizeText:
662         if (record.data.needsRelayout && parentRecord.type === recordTypes.Layout)
663             parentRecord.addWarning(WebInspector.UIString("Layout required two passes due to text autosizing, consider setting viewport."));
664         break;
665
666     case recordTypes.Paint:
667         this.highlightQuad = record.data.clip || WebInspector.TimelineModel._quadFromRectData(record.data);
668         break;
669
670     case recordTypes.WebSocketCreate:
671         this.webSocketURL = record.data["url"];
672         if (typeof record.data["webSocketProtocol"] !== "undefined")
673             this.webSocketProtocol = record.data["webSocketProtocol"];
674         bindings._webSocketCreateRecords[record.data["identifier"]] = this;
675         break;
676
677     case recordTypes.WebSocketSendHandshakeRequest:
678     case recordTypes.WebSocketReceiveHandshakeResponse:
679     case recordTypes.WebSocketDestroy:
680         var webSocketCreateRecord = bindings._webSocketCreateRecords[record.data["identifier"]];
681         if (webSocketCreateRecord) { // False if we started instrumentation in the middle of request.
682             this.webSocketURL = webSocketCreateRecord.webSocketURL;
683             if (typeof webSocketCreateRecord.webSocketProtocol !== "undefined")
684                 this.webSocketProtocol = webSocketCreateRecord.webSocketProtocol;
685         }
686         break;
687
688     case recordTypes.EmbedderCallback:
689         this.embedderCallbackName = record.data["callbackName"];
690         break;
691     }
692 }
693
694 WebInspector.TimelineModel.Record.prototype = {
695     get lastChildEndTime()
696     {
697         return this._lastChildEndTime;
698     },
699
700     set lastChildEndTime(time)
701     {
702         this._lastChildEndTime = time;
703     },
704
705     get selfTime()
706     {
707         return this._selfTime;
708     },
709
710     get cpuTime()
711     {
712         return this._cpuTime;
713     },
714
715     /**
716      * @return {boolean}
717      */
718     isRoot: function()
719     {
720         return this.type === WebInspector.TimelineModel.RecordType.Root;
721     },
722
723     /**
724      * @return {!Array.<!WebInspector.TimelineModel.Record>}
725      */
726     get children()
727     {
728         return this._children;
729     },
730
731     /**
732      * @return {!WebInspector.TimelineCategory}
733      */
734     get category()
735     {
736         return WebInspector.TimelineUIUtils.categoryForRecord(this);
737     },
738
739     /**
740      * @return {string}
741      */
742     title: function()
743     {
744         return WebInspector.TimelineUIUtils.recordTitle(this);
745     },
746
747     /**
748      * @return {number}
749      */
750     get startTime()
751     {
752         return this._startTime || this._record.startTime;
753     },
754
755     set startTime(startTime)
756     {
757         this._startTime = startTime;
758     },
759
760     /**
761      * @return {string|undefined}
762      */
763     get thread()
764     {
765         return this._record.thread;
766     },
767
768     /**
769      * @return {number}
770      */
771     get startTimeOffset()
772     {
773         return this._startTimeOffset;
774     },
775
776     /**
777      * @return {number}
778      */
779     get endTime()
780     {
781         return this._endTime || this._record.endTime || this._record.startTime;
782     },
783
784     set endTime(endTime)
785     {
786         this._endTime = endTime;
787     },
788
789     /**
790      * @return {!Object}
791      */
792     get data()
793     {
794         return this._record.data;
795     },
796
797     /**
798      * @return {string}
799      */
800     get type()
801     {
802         return this._record.type;
803     },
804
805     /**
806      * @return {string}
807      */
808     get frameId()
809     {
810         return this._record.frameId || "";
811     },
812
813     /**
814      * @return {number}
815      */
816     get usedHeapSizeDelta()
817     {
818         return this._record.usedHeapSizeDelta || 0;
819     },
820
821     /**
822      * @return {number}
823      */
824     get jsHeapSizeUsed()
825     {
826         return this._record.counters ? this._record.counters.jsHeapSizeUsed || 0 : 0;
827     },
828
829     /**
830      * @return {!Object|undefined}
831      */
832     get counters()
833     {
834         return this._record.counters;
835     },
836
837     /**
838      * @return {?Array.<!ConsoleAgent.CallFrame>}
839      */
840     get stackTrace()
841     {
842         if (this._record.stackTrace && this._record.stackTrace.length)
843             return this._record.stackTrace;
844         return null;
845     },
846
847     /**
848      * @param {string} key
849      * @return {?Object}
850      */
851     getUserObject: function(key)
852     {
853         if (!this._userObjects)
854             return null;
855         return this._userObjects.get(key);
856     },
857
858     /**
859      * @param {string} key
860      * @param {?Object|undefined} value
861      */
862     setUserObject: function(key, value)
863     {
864         if (!this._userObjects)
865             this._userObjects = new StringMap();
866         this._userObjects.put(key, value);
867     },
868
869     /**
870      * @return {number} nodeId
871      */
872     relatedBackendNodeId: function()
873     {
874         return this._relatedBackendNodeId;
875     },
876
877     calculateAggregatedStats: function()
878     {
879         this._aggregatedStats = {};
880         this._cpuTime = this._selfTime;
881
882         for (var index = this._children.length; index; --index) {
883             var child = this._children[index - 1];
884             for (var category in child._aggregatedStats)
885                 this._aggregatedStats[category] = (this._aggregatedStats[category] || 0) + child._aggregatedStats[category];
886         }
887         for (var category in this._aggregatedStats)
888             this._cpuTime += this._aggregatedStats[category];
889         this._aggregatedStats[this.category.name] = (this._aggregatedStats[this.category.name] || 0) + this._selfTime;
890     },
891
892     get aggregatedStats()
893     {
894         return this._aggregatedStats;
895     },
896
897     /**
898      * @param {string} message
899      */
900     addWarning: function(message)
901     {
902         if (this._warnings)
903             this._warnings.push(message);
904         else {
905             this._warnings = [message];
906             for (var parent = this.parent; parent && !parent._childHasWarnings; parent = parent.parent)
907                 parent._childHasWarnings = true;
908         }
909     },
910
911     /**
912      * @return {?Array.<string>}
913      */
914     warnings: function()
915     {
916         return this._warnings;
917     },
918
919     /**
920      * @return {boolean}
921      */
922     childHasWarnings: function()
923     {
924         return !!this._childHasWarnings;
925     },
926
927     /**
928      * @param {!RegExp} regExp
929      * @return {boolean}
930      */
931     testContentMatching: function(regExp)
932     {
933         var tokens = [this.title()];
934         for (var key in this._record.data)
935             tokens.push(this._record.data[key])
936         return regExp.test(tokens.join("|"));
937     }
938 }
939
940
941 /**
942  * @constructor
943  */
944 WebInspector.TimelineModel.Filter = function()
945 {
946     /** @type {!WebInspector.TimelineModel} */
947     this._model;
948 }
949
950 WebInspector.TimelineModel.Filter.prototype = {
951     /**
952      * @param {!WebInspector.TimelineModel.Record} record
953      * @return {boolean}
954      */
955     accept: function(record)
956     {
957         return true;
958     },
959
960     notifyFilterChanged: function()
961     {
962         this._model._filterChanged();
963     }
964 }
965
966 /**
967  * @constructor
968  * @implements {WebInspector.OutputStream}
969  * @param {!WebInspector.TimelineModel} model
970  * @param {!{cancel: function()}} reader
971  * @param {!WebInspector.Progress} progress
972  */
973 WebInspector.TimelineModelLoader = function(model, reader, progress)
974 {
975     this._model = model;
976     this._reader = reader;
977     this._progress = progress;
978     this._buffer = "";
979     this._firstChunk = true;
980 }
981
982 WebInspector.TimelineModelLoader.prototype = {
983     /**
984      * @param {string} chunk
985      */
986     write: function(chunk)
987     {
988         var data = this._buffer + chunk;
989         var lastIndex = 0;
990         var index;
991         do {
992             index = lastIndex;
993             lastIndex = WebInspector.TextUtils.findBalancedCurlyBrackets(data, index);
994         } while (lastIndex !== -1)
995
996         var json = data.slice(0, index) + "]";
997         this._buffer = data.slice(index);
998
999         if (!index)
1000             return;
1001
1002         // Prepending "0" to turn string into valid JSON.
1003         if (!this._firstChunk)
1004             json = "[0" + json;
1005
1006         var items;
1007         try {
1008             items = /** @type {!Array.<!TimelineAgent.TimelineEvent>} */ (JSON.parse(json));
1009         } catch (e) {
1010             WebInspector.console.showErrorMessage("Malformed timeline data.");
1011             this._model.reset();
1012             this._reader.cancel();
1013             this._progress.done();
1014             return;
1015         }
1016
1017         if (this._firstChunk) {
1018             this._version = items[0];
1019             this._firstChunk = false;
1020             this._model.reset();
1021         }
1022
1023         // Skip 0-th element - it is either version or 0.
1024         for (var i = 1, size = items.length; i < size; ++i)
1025             this._model._addRecord(items[i]);
1026     },
1027
1028     close: function() { }
1029 }
1030
1031 /**
1032  * @constructor
1033  * @implements {WebInspector.OutputStreamDelegate}
1034  * @param {!WebInspector.TimelineModel} model
1035  * @param {!WebInspector.Progress} progress
1036  */
1037 WebInspector.TimelineModelLoadFromFileDelegate = function(model, progress)
1038 {
1039     this._model = model;
1040     this._progress = progress;
1041 }
1042
1043 WebInspector.TimelineModelLoadFromFileDelegate.prototype = {
1044     onTransferStarted: function()
1045     {
1046         this._progress.setTitle(WebInspector.UIString("Loading\u2026"));
1047     },
1048
1049     /**
1050      * @param {!WebInspector.ChunkedReader} reader
1051      */
1052     onChunkTransferred: function(reader)
1053     {
1054         if (this._progress.isCanceled()) {
1055             reader.cancel();
1056             this._progress.done();
1057             this._model.reset();
1058             return;
1059         }
1060
1061         var totalSize = reader.fileSize();
1062         if (totalSize) {
1063             this._progress.setTotalWork(totalSize);
1064             this._progress.setWorked(reader.loadedSize());
1065         }
1066     },
1067
1068     onTransferFinished: function()
1069     {
1070         this._progress.done();
1071     },
1072
1073     /**
1074      * @param {!WebInspector.ChunkedReader} reader
1075      */
1076     onError: function(reader, event)
1077     {
1078         this._progress.done();
1079         this._model.reset();
1080         switch (event.target.error.code) {
1081         case FileError.NOT_FOUND_ERR:
1082             WebInspector.console.showErrorMessage(WebInspector.UIString("File \"%s\" not found.", reader.fileName()));
1083             break;
1084         case FileError.NOT_READABLE_ERR:
1085             WebInspector.console.showErrorMessage(WebInspector.UIString("File \"%s\" is not readable", reader.fileName()));
1086             break;
1087         case FileError.ABORT_ERR:
1088             break;
1089         default:
1090             WebInspector.console.showErrorMessage(WebInspector.UIString("An error occurred while reading the file \"%s\"", reader.fileName()));
1091         }
1092     }
1093 }
1094
1095 /**
1096  * @constructor
1097  */
1098 WebInspector.TimelineSaver = function(stream)
1099 {
1100     this._stream = stream;
1101 }
1102
1103 WebInspector.TimelineSaver.prototype = {
1104     /**
1105      * @param {!Array.<*>} payloads
1106      * @param {string} version
1107      */
1108     save: function(payloads, version)
1109     {
1110         this._payloads = payloads;
1111         this._recordIndex = 0;
1112         this._prologue = "[" + JSON.stringify(version);
1113
1114         this._writeNextChunk(this._stream);
1115     },
1116
1117     _writeNextChunk: function(stream)
1118     {
1119         const separator = ",\n";
1120         var data = [];
1121         var length = 0;
1122
1123         if (this._prologue) {
1124             data.push(this._prologue);
1125             length += this._prologue.length;
1126             delete this._prologue;
1127         } else {
1128             if (this._recordIndex === this._payloads.length) {
1129                 stream.close();
1130                 return;
1131             }
1132             data.push("");
1133         }
1134         while (this._recordIndex < this._payloads.length) {
1135             var item = JSON.stringify(this._payloads[this._recordIndex]);
1136             var itemLength = item.length + separator.length;
1137             if (length + itemLength > WebInspector.TimelineModel.TransferChunkLengthBytes)
1138                 break;
1139             length += itemLength;
1140             data.push(item);
1141             ++this._recordIndex;
1142         }
1143         if (this._recordIndex === this._payloads.length)
1144             data.push(data.pop() + "]");
1145         stream.write(data.join(separator), this._writeNextChunk.bind(this));
1146     }
1147 }
1148
1149 /**
1150  * @constructor
1151  */
1152 WebInspector.TimelineMergingRecordBuffer = function()
1153 {
1154     this._backgroundRecordsBuffer = [];
1155 }
1156
1157 /**
1158  * @constructor
1159  */
1160 WebInspector.TimelineMergingRecordBuffer.prototype = {
1161     /**
1162      * @param {string} thread
1163      * @param {!Array.<!TimelineAgent.TimelineEvent>} records
1164      * @return {!Array.<!TimelineAgent.TimelineEvent>}
1165      */
1166     process: function(thread, records)
1167     {
1168         if (thread) {
1169             this._backgroundRecordsBuffer = this._backgroundRecordsBuffer.concat(records);
1170             return [];
1171         }
1172         /**
1173          * @param {!TimelineAgent.TimelineEvent} a
1174          * @param {!TimelineAgent.TimelineEvent} b
1175          */
1176         function recordTimestampComparator(a, b)
1177         {
1178             // Never return 0, as the merge function will squash identical entries.
1179             return a.startTime < b.startTime ? -1 : 1;
1180         }
1181         var result = this._backgroundRecordsBuffer.mergeOrdered(records, recordTimestampComparator);
1182         this._backgroundRecordsBuffer = [];
1183         return result;
1184     }
1185 }
1186
1187 /**
1188  * @param {!Object} data
1189  * @return {?Array.<number>}
1190  */
1191 WebInspector.TimelineModel._quadFromRectData = function(data)
1192 {
1193     if (typeof data["x"] === "undefined" || typeof data["y"] === "undefined")
1194         return null;
1195     var x0 = data["x"];
1196     var x1 = data["x"] + data["width"];
1197     var y0 = data["y"];
1198     var y1 = data["y"] + data["height"];
1199     return [x0, y0, x1, y0, x1, y1, x0, y1];
1200 }