Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / timeline / TimelineModelImpl.js
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6  * @constructor
7  * @extends {WebInspector.TimelineModel}
8  * @implements {WebInspector.TargetManager.Observer}
9  */
10 WebInspector.TimelineModelImpl = function()
11 {
12     WebInspector.TimelineModel.call(this);
13     /** @type {?WebInspector.Target} */
14     this._currentTarget = null;
15     this._filters = [];
16     this._bindings = new WebInspector.TimelineModelImpl.InterRecordBindings();
17
18     this.reset();
19
20     WebInspector.targetManager.addModelListener(WebInspector.TimelineManager, WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, this._onRecordAdded, this);
21     WebInspector.targetManager.addModelListener(WebInspector.TimelineManager, WebInspector.TimelineManager.EventTypes.TimelineStarted, this._onStarted, this);
22     WebInspector.targetManager.addModelListener(WebInspector.TimelineManager, WebInspector.TimelineManager.EventTypes.TimelineStopped, this._onStopped, this);
23     WebInspector.targetManager.addModelListener(WebInspector.TimelineManager, WebInspector.TimelineManager.EventTypes.TimelineProgress, this._onProgress, this);
24     WebInspector.targetManager.observeTargets(this);
25 }
26
27 WebInspector.TimelineModelImpl.TransferChunkLengthBytes = 5000000;
28
29 WebInspector.TimelineModelImpl.prototype = {
30     /**
31      * @param {!WebInspector.Target} target
32      */
33     targetAdded: function(target) { },
34
35     /**
36      * @param {!WebInspector.Target} target
37      */
38     targetRemoved: function(target)
39     {
40         if (this._currentTarget === target)
41             this._currentTarget = null;
42     },
43
44     /**
45      * @param {boolean} captureCauses
46      * @param {boolean} captureMemory
47      * @param {boolean} capturePictures
48      */
49     startRecording: function(captureCauses, captureMemory, capturePictures)
50     {
51         console.assert(!capturePictures, "Legacy timeline does not support capturing pictures");
52         this.reset();
53         this._currentTarget = WebInspector.context.flavor(WebInspector.Target);
54         console.assert(this._currentTarget);
55
56         this._clientInitiatedRecording = true;
57         var maxStackFrames = captureCauses ? 30 : 0;
58         var includeGPUEvents = Runtime.experiments.isEnabled("gpuTimeline");
59         var liveEvents = [ WebInspector.TimelineModel.RecordType.BeginFrame,
60                            WebInspector.TimelineModel.RecordType.DrawFrame,
61                            WebInspector.TimelineModel.RecordType.RequestMainThreadFrame,
62                            WebInspector.TimelineModel.RecordType.ActivateLayerTree ];
63         this._currentTarget.timelineManager.start(maxStackFrames, liveEvents.join(","), captureMemory, includeGPUEvents, this._fireRecordingStarted.bind(this));
64     },
65
66     stopRecording: function()
67     {
68         if (!this._currentTarget)
69             return;
70
71         if (!this._clientInitiatedRecording) {
72             this._currentTarget.timelineManager.start(undefined, undefined, undefined, undefined, stopTimeline.bind(this));
73             return;
74         }
75
76         /**
77          * Console started this one and we are just sniffing it. Initiate recording so that we
78          * could stop it.
79          * @this {WebInspector.TimelineModelImpl}
80          */
81         function stopTimeline()
82         {
83             this._currentTarget.timelineManager.stop(this._fireRecordingStopped.bind(this));
84         }
85
86         this._clientInitiatedRecording = false;
87         this._currentTarget.timelineManager.stop(this._fireRecordingStopped.bind(this));
88     },
89
90     /**
91      * @return {!Array.<!WebInspector.TimelineModel.Record>}
92      */
93     records: function()
94     {
95         return this._records;
96     },
97
98     /**
99      * @param {!WebInspector.Event} event
100      */
101     _onRecordAdded: function(event)
102     {
103         var timelineManager = /** @type {!WebInspector.TimelineManager} */ (event.target);
104         if (this._collectionEnabled && timelineManager.target() === this._currentTarget)
105             this._addRecord(/** @type {!TimelineAgent.TimelineEvent} */(event.data));
106     },
107
108     /**
109      * @param {!WebInspector.Event} event
110      */
111     _onStarted: function(event)
112     {
113         if (!event.data || this._collectionEnabled)
114             return;
115         // Started from console.
116         var timelineManager = /** @type {!WebInspector.TimelineManager} */ (event.target);
117         if (this._currentTarget !== timelineManager.target()) {
118             this.reset();
119             this._currentTarget = timelineManager.target();
120         }
121         this._fireRecordingStarted();
122     },
123
124     /**
125      * @param {!WebInspector.Event} event
126      */
127     _onStopped: function(event)
128     {
129         var timelineManager = /** @type {!WebInspector.TimelineManager} */ (event.target);
130         if (timelineManager.target() !== this._currentTarget)
131             return;
132         // We were buffering events, discard those that got through, the real ones are coming!
133         this.reset();
134         this._currentTarget = timelineManager.target();
135
136         var events = /** @type {!Array.<!TimelineAgent.TimelineEvent>} */ (event.data.events);
137         for (var i = 0; i < events.length; ++i)
138             this._addRecord(events[i]);
139
140         if (event.data.consoleTimeline) {
141             // Stopped from console.
142             this._fireRecordingStopped(null);
143         }
144
145         this._collectionEnabled = false;
146     },
147
148     /**
149      * @param {!WebInspector.Event} event
150      */
151     _onProgress: function(event)
152     {
153         var timelineManager = /** @type {!WebInspector.TimelineManager} */ (event.target);
154         if (timelineManager.target() === this._currentTarget)
155             this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingProgress, event.data);
156     },
157
158     _fireRecordingStarted: function()
159     {
160         this._collectionEnabled = true;
161         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStarted);
162     },
163
164     /**
165      * @param {?Protocol.Error} error
166      */
167     _fireRecordingStopped: function(error)
168     {
169         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped);
170     },
171
172     /**
173      * @param {!TimelineAgent.TimelineEvent} payload
174      */
175     _addRecord: function(payload)
176     {
177         this._internStrings(payload);
178         this._payloads.push(payload);
179
180         var record = this._innerAddRecord(payload, null);
181         this._updateBoundaries(record);
182         this._records.push(record);
183         if (record.type() === WebInspector.TimelineModel.RecordType.Program)
184             this._mainThreadTasks.push(record);
185         if (record.type() === WebInspector.TimelineModel.RecordType.GPUTask)
186             this._gpuThreadTasks.push(record);
187
188         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record);
189     },
190
191     /**
192      * @param {!TimelineAgent.TimelineEvent} payload
193      * @param {?WebInspector.TimelineModel.Record} parentRecord
194      * @return {!WebInspector.TimelineModel.Record}
195      */
196     _innerAddRecord: function(payload, parentRecord)
197     {
198         var record = new WebInspector.TimelineModel.RecordImpl(this, payload, parentRecord);
199         if (WebInspector.TimelineUIUtilsImpl.isEventDivider(record))
200             this._eventDividerRecords.push(record);
201
202         for (var i = 0; payload.children && i < payload.children.length; ++i)
203             this._innerAddRecord.call(this, payload.children[i], record);
204
205         if (parentRecord)
206             parentRecord._selfTime -= record.endTime() - record.startTime();
207         return record;
208     },
209
210     /**
211      * @param {!WebInspector.ChunkedFileReader} fileReader
212      * @param {!WebInspector.Progress} progress
213      * @return {!WebInspector.OutputStream}
214      */
215     createLoader: function(fileReader, progress)
216     {
217         return new WebInspector.TimelineModelLoader(this, fileReader, progress);
218     },
219
220     /**
221      * @param {!WebInspector.OutputStream} stream
222      */
223     writeToStream: function(stream)
224     {
225         var saver = new WebInspector.TimelineSaver(stream);
226         saver.save(this._payloads, window.navigator.appVersion);
227     },
228
229     reset: function()
230     {
231         if (!this._collectionEnabled)
232             this._currentTarget = null;
233         this._payloads = [];
234         this._stringPool = {};
235         this._bindings._reset();
236         this._minimumRecordTime = 0;
237         this._maximumRecordTime = 0;
238         WebInspector.TimelineModel.prototype.reset.call(this);
239     },
240
241     /**
242      * @return {number}
243      */
244     minimumRecordTime: function()
245     {
246         return this._minimumRecordTime;
247     },
248
249     /**
250      * @return {number}
251      */
252     maximumRecordTime: function()
253     {
254         return this._maximumRecordTime;
255     },
256
257     /**
258      * @param {!WebInspector.TimelineModel.Record} record
259      */
260     _updateBoundaries: function(record)
261     {
262         var startTime = record.startTime();
263         var endTime = record.endTime();
264
265         if (!this._minimumRecordTime || startTime < this._minimumRecordTime)
266             this._minimumRecordTime = startTime;
267         if (endTime > this._maximumRecordTime)
268             this._maximumRecordTime = endTime;
269     },
270
271     /**
272      * @param {!TimelineAgent.TimelineEvent} record
273      */
274     _internStrings: function(record)
275     {
276         for (var name in record) {
277             var value = record[name];
278             if (typeof value !== "string")
279                 continue;
280
281             var interned = this._stringPool[value];
282             if (typeof interned === "string")
283                 record[name] = interned;
284             else
285                 this._stringPool[value] = value;
286         }
287
288         var children = record.children;
289         for (var i = 0; children && i < children.length; ++i)
290             this._internStrings(children[i]);
291     },
292
293     __proto__: WebInspector.TimelineModel.prototype
294 }
295
296
297 /**
298  * @constructor
299  */
300 WebInspector.TimelineModelImpl.InterRecordBindings = function() {
301     this._reset();
302 }
303
304 WebInspector.TimelineModelImpl.InterRecordBindings.prototype = {
305     _reset: function()
306     {
307         this._sendRequestRecords = {};
308         this._timerRecords = {};
309         this._requestAnimationFrameRecords = {};
310         this._layoutInvalidate = {};
311         this._lastScheduleStyleRecalculation = {};
312         this._webSocketCreateRecords = {};
313     }
314 }
315
316 /**
317  * @constructor
318  * @implements {WebInspector.TimelineModel.Record}
319  * @param {!WebInspector.TimelineModel} model
320  * @param {!TimelineAgent.TimelineEvent} timelineEvent
321  * @param {?WebInspector.TimelineModel.Record} parentRecord
322  */
323 WebInspector.TimelineModel.RecordImpl = function(model, timelineEvent, parentRecord)
324 {
325     this._model = model;
326     var bindings = this._model._bindings;
327     this._record = timelineEvent;
328     this._thread = this._record.thread || WebInspector.TimelineModel.MainThreadName;
329     this._children = [];
330     if (parentRecord) {
331         this.parent = parentRecord;
332         parentRecord.children().push(this);
333     }
334
335     this._selfTime = this.endTime() - this.startTime();
336
337     var recordTypes = WebInspector.TimelineModel.RecordType;
338     switch (timelineEvent.type) {
339     case recordTypes.ResourceSendRequest:
340         // Make resource receive record last since request was sent; make finish record last since response received.
341         bindings._sendRequestRecords[timelineEvent.data["requestId"]] = this;
342         break;
343
344     case recordTypes.ResourceReceiveResponse:
345     case recordTypes.ResourceReceivedData:
346     case recordTypes.ResourceFinish:
347         this._initiator = bindings._sendRequestRecords[timelineEvent.data["requestId"]];
348         break;
349
350     case recordTypes.TimerInstall:
351         bindings._timerRecords[timelineEvent.data["timerId"]] = this;
352         break;
353
354     case recordTypes.TimerFire:
355         this._initiator = bindings._timerRecords[timelineEvent.data["timerId"]];
356         break;
357
358     case recordTypes.RequestAnimationFrame:
359         bindings._requestAnimationFrameRecords[timelineEvent.data["id"]] = this;
360         break;
361
362     case recordTypes.FireAnimationFrame:
363         this._initiator = bindings._requestAnimationFrameRecords[timelineEvent.data["id"]];
364         break;
365
366     case recordTypes.ScheduleStyleRecalculation:
367         bindings._lastScheduleStyleRecalculation[this.frameId()] = this;
368         break;
369
370     case recordTypes.RecalculateStyles:
371         this._initiator = bindings._lastScheduleStyleRecalculation[this.frameId()];
372         break;
373
374     case recordTypes.InvalidateLayout:
375         // Consider style recalculation as a reason for layout invalidation,
376         // but only if we had no earlier layout invalidation records.
377         var layoutInitator = this;
378         if (!bindings._layoutInvalidate[this.frameId()] && parentRecord.type() === recordTypes.RecalculateStyles)
379             layoutInitator = parentRecord._initiator;
380         bindings._layoutInvalidate[this.frameId()] = layoutInitator;
381         break;
382
383     case recordTypes.Layout:
384         this._initiator = bindings._layoutInvalidate[this.frameId()];
385         bindings._layoutInvalidate[this.frameId()] = null;
386         if (this.stackTrace())
387             this.addWarning(WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck."));
388         break;
389
390     case recordTypes.WebSocketCreate:
391         bindings._webSocketCreateRecords[timelineEvent.data["identifier"]] = this;
392         break;
393
394     case recordTypes.WebSocketSendHandshakeRequest:
395     case recordTypes.WebSocketReceiveHandshakeResponse:
396     case recordTypes.WebSocketDestroy:
397         this._initiator = bindings._webSocketCreateRecords[timelineEvent.data["identifier"]];
398         break;
399     }
400 }
401
402 WebInspector.TimelineModel.RecordImpl.prototype = {
403     /**
404      * @return {?Array.<!ConsoleAgent.CallFrame>}
405      */
406     callSiteStackTrace: function()
407     {
408         return this._initiator ? this._initiator.stackTrace() : null;
409     },
410
411     /**
412      * @return {?WebInspector.TimelineModel.Record}
413      */
414     initiator: function()
415     {
416         return this._initiator;
417     },
418
419     /**
420      * @return {?WebInspector.Target}
421      */
422     target: function()
423     {
424         return this._model._currentTarget;
425     },
426
427     /**
428      * @return {number}
429      */
430     selfTime: function()
431     {
432         return this._selfTime;
433     },
434
435     /**
436      * @return {!Array.<!WebInspector.TimelineModel.Record>}
437      */
438     children: function()
439     {
440         return this._children;
441     },
442
443     /**
444      * @return {number}
445      */
446     startTime: function()
447     {
448         return this._record.startTime;
449     },
450
451     /**
452      * @return {string}
453      */
454     thread: function()
455     {
456         return this._thread;
457     },
458
459     /**
460      * @return {number}
461      */
462     endTime: function()
463     {
464         return this._endTime || this._record.endTime || this._record.startTime;
465     },
466
467     /**
468      * @param {number} endTime
469      */
470     setEndTime: function(endTime)
471     {
472         this._endTime = endTime;
473     },
474
475     /**
476      * @return {!Object}
477      */
478     data: function()
479     {
480         return this._record.data;
481     },
482
483     /**
484      * @return {string}
485      */
486     type: function()
487     {
488         return this._record.type;
489     },
490
491     /**
492      * @return {string}
493      */
494     frameId: function()
495     {
496         return this._record.frameId || "";
497     },
498
499     /**
500      * @return {?Array.<!ConsoleAgent.CallFrame>}
501      */
502     stackTrace: function()
503     {
504         if (this._record.stackTrace && this._record.stackTrace.length)
505             return this._record.stackTrace;
506         return null;
507     },
508
509     /**
510      * @param {string} key
511      * @return {?Object}
512      */
513     getUserObject: function(key)
514     {
515         if (!this._userObjects)
516             return null;
517         return this._userObjects.get(key);
518     },
519
520     /**
521      * @param {string} key
522      * @param {?Object|undefined} value
523      */
524     setUserObject: function(key, value)
525     {
526         if (!this._userObjects)
527             this._userObjects = new Map();
528         this._userObjects.set(key, value);
529     },
530
531     /**
532      * @param {string} message
533      */
534     addWarning: function(message)
535     {
536         if (!this._warnings)
537             this._warnings = [];
538         this._warnings.push(message);
539     },
540
541     /**
542      * @return {?Array.<string>}
543      */
544     warnings: function()
545     {
546         return this._warnings;
547     }
548 }
549
550 /**
551  * @constructor
552  * @implements {WebInspector.OutputStream}
553  * @param {!WebInspector.TimelineModel} model
554  * @param {!{cancel: function()}} reader
555  * @param {!WebInspector.Progress} progress
556  */
557 WebInspector.TimelineModelLoader = function(model, reader, progress)
558 {
559     this._model = model;
560     this._reader = reader;
561     this._progress = progress;
562     this._buffer = "";
563     this._firstChunk = true;
564 }
565
566 WebInspector.TimelineModelLoader.prototype = {
567     /**
568      * @param {string} chunk
569      */
570     write: function(chunk)
571     {
572         var data = this._buffer + chunk;
573         var lastIndex = 0;
574         var index;
575         do {
576             index = lastIndex;
577             lastIndex = WebInspector.TextUtils.findBalancedCurlyBrackets(data, index);
578         } while (lastIndex !== -1)
579
580         var json = data.slice(0, index) + "]";
581         this._buffer = data.slice(index);
582
583         if (!index)
584             return;
585
586         if (this._firstChunk) {
587             this._firstChunk = false;
588             this._model.reset();
589         } else {
590             // Prepending "0" to turn string into valid JSON.
591             json = "[0" + json;
592         }
593
594         var items;
595         try {
596             items = /** @type {!Array.<!TimelineAgent.TimelineEvent>} */ (JSON.parse(json));
597         } catch (e) {
598             WebInspector.console.error("Malformed timeline data.");
599             this._model.reset();
600             this._reader.cancel();
601             this._progress.done();
602             return;
603         }
604
605         // Skip 0-th element - it is either version or 0.
606         for (var i = 1, size = items.length; i < size; ++i)
607             this._model._addRecord(items[i]);
608     },
609
610     close: function()
611     {
612     }
613 }
614
615 /**
616  * @constructor
617  * @param {!WebInspector.OutputStream} stream
618  */
619 WebInspector.TimelineSaver = function(stream)
620 {
621     this._stream = stream;
622 }
623
624 WebInspector.TimelineSaver.prototype = {
625     /**
626      * @param {!Array.<*>} payloads
627      * @param {string} version
628      */
629     save: function(payloads, version)
630     {
631         this._payloads = payloads;
632         this._recordIndex = 0;
633         this._prologue = "[" + JSON.stringify(version);
634
635         this._writeNextChunk(this._stream);
636     },
637
638     _writeNextChunk: function(stream)
639     {
640         const separator = ",\n";
641         var data = [];
642         var length = 0;
643
644         if (this._prologue) {
645             data.push(this._prologue);
646             length += this._prologue.length;
647             delete this._prologue;
648         } else {
649             if (this._recordIndex === this._payloads.length) {
650                 stream.close();
651                 return;
652             }
653             data.push("");
654         }
655         while (this._recordIndex < this._payloads.length) {
656             var item = JSON.stringify(this._payloads[this._recordIndex]);
657             var itemLength = item.length + separator.length;
658             if (length + itemLength > WebInspector.TimelineModelImpl.TransferChunkLengthBytes)
659                 break;
660             length += itemLength;
661             data.push(item);
662             ++this._recordIndex;
663         }
664         if (this._recordIndex === this._payloads.length)
665             data.push(data.pop() + "]");
666         stream.write(data.join(separator), this._writeNextChunk.bind(this));
667     }
668 }