Upstream version 9.38.198.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} captureStacks
46      * @param {boolean} captureMemory
47      * @param {boolean} capturePictures
48      */
49     startRecording: function(captureStacks, 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 = captureStacks ? 30 : 0;
58         var includeGPUEvents = WebInspector.experimentsSettings.gpuTimeline.isEnabled();
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, 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      * @param {?ProfilerAgent.CPUProfile} cpuProfile
167      */
168     _fireRecordingStopped: function(error, cpuProfile)
169     {
170         if (cpuProfile)
171             WebInspector.TimelineJSProfileProcessor.mergeJSProfileIntoTimeline(this, cpuProfile);
172         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped);
173     },
174
175     /**
176      * @param {!TimelineAgent.TimelineEvent} payload
177      */
178     _addRecord: function(payload)
179     {
180         this._internStrings(payload);
181         this._payloads.push(payload);
182
183         var record = this._innerAddRecord(payload, null);
184         this._updateBoundaries(record);
185         this._records.push(record);
186         if (record.type() === WebInspector.TimelineModel.RecordType.Program)
187             this._mainThreadTasks.push(record);
188         if (record.type() === WebInspector.TimelineModel.RecordType.GPUTask)
189             this._gpuThreadTasks.push(record);
190
191         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record);
192     },
193
194     /**
195      * @param {!TimelineAgent.TimelineEvent} payload
196      * @param {?WebInspector.TimelineModel.Record} parentRecord
197      * @return {!WebInspector.TimelineModel.Record}
198      */
199     _innerAddRecord: function(payload, parentRecord)
200     {
201         var record = new WebInspector.TimelineModel.RecordImpl(this, payload, parentRecord);
202         if (WebInspector.TimelineUIUtilsImpl.isEventDivider(record))
203             this._eventDividerRecords.push(record);
204
205         for (var i = 0; payload.children && i < payload.children.length; ++i)
206             this._innerAddRecord.call(this, payload.children[i], record);
207
208         if (parentRecord)
209             parentRecord._selfTime -= record.endTime() - record.startTime();
210         return record;
211     },
212
213     /**
214      * @param {!WebInspector.ChunkedFileReader} fileReader
215      * @param {!WebInspector.Progress} progress
216      * @return {!WebInspector.OutputStream}
217      */
218     createLoader: function(fileReader, progress)
219     {
220         return new WebInspector.TimelineModelLoader(this, fileReader, progress);
221     },
222
223     /**
224      * @param {!WebInspector.OutputStream} stream
225      */
226     writeToStream: function(stream)
227     {
228         var saver = new WebInspector.TimelineSaver(stream);
229         saver.save(this._payloads, window.navigator.appVersion);
230     },
231
232     reset: function()
233     {
234         if (!this._collectionEnabled)
235             this._currentTarget = null;
236         this._payloads = [];
237         this._stringPool = {};
238         this._bindings._reset();
239         WebInspector.TimelineModel.prototype.reset.call(this);
240     },
241
242     /**
243      * @param {!TimelineAgent.TimelineEvent} record
244      */
245     _internStrings: function(record)
246     {
247         for (var name in record) {
248             var value = record[name];
249             if (typeof value !== "string")
250                 continue;
251
252             var interned = this._stringPool[value];
253             if (typeof interned === "string")
254                 record[name] = interned;
255             else
256                 this._stringPool[value] = value;
257         }
258
259         var children = record.children;
260         for (var i = 0; children && i < children.length; ++i)
261             this._internStrings(children[i]);
262     },
263
264     __proto__: WebInspector.TimelineModel.prototype
265 }
266
267
268 /**
269  * @constructor
270  */
271 WebInspector.TimelineModelImpl.InterRecordBindings = function() {
272     this._reset();
273 }
274
275 WebInspector.TimelineModelImpl.InterRecordBindings.prototype = {
276     _reset: function()
277     {
278         this._sendRequestRecords = {};
279         this._timerRecords = {};
280         this._requestAnimationFrameRecords = {};
281         this._layoutInvalidate = {};
282         this._lastScheduleStyleRecalculation = {};
283         this._webSocketCreateRecords = {};
284     }
285 }
286
287 /**
288  * @constructor
289  * @implements {WebInspector.TimelineModel.Record}
290  * @param {!WebInspector.TimelineModel} model
291  * @param {!TimelineAgent.TimelineEvent} timelineEvent
292  * @param {?WebInspector.TimelineModel.Record} parentRecord
293  */
294 WebInspector.TimelineModel.RecordImpl = function(model, timelineEvent, parentRecord)
295 {
296     this._model = model;
297     var bindings = this._model._bindings;
298     this._record = timelineEvent;
299     this._thread = this._record.thread || WebInspector.TimelineModel.MainThreadName;
300     this._children = [];
301     if (parentRecord) {
302         this.parent = parentRecord;
303         parentRecord.children().push(this);
304     }
305
306     this._selfTime = this.endTime() - this.startTime();
307
308     var recordTypes = WebInspector.TimelineModel.RecordType;
309     switch (timelineEvent.type) {
310     case recordTypes.ResourceSendRequest:
311         // Make resource receive record last since request was sent; make finish record last since response received.
312         bindings._sendRequestRecords[timelineEvent.data["requestId"]] = this;
313         break;
314
315     case recordTypes.ResourceReceiveResponse:
316     case recordTypes.ResourceReceivedData:
317     case recordTypes.ResourceFinish:
318         this._initiator = bindings._sendRequestRecords[timelineEvent.data["requestId"]];
319         break;
320
321     case recordTypes.TimerInstall:
322         bindings._timerRecords[timelineEvent.data["timerId"]] = this;
323         break;
324
325     case recordTypes.TimerFire:
326         this._initiator = bindings._timerRecords[timelineEvent.data["timerId"]];
327         break;
328
329     case recordTypes.RequestAnimationFrame:
330         bindings._requestAnimationFrameRecords[timelineEvent.data["id"]] = this;
331         break;
332
333     case recordTypes.FireAnimationFrame:
334         this._initiator = bindings._requestAnimationFrameRecords[timelineEvent.data["id"]];
335         break;
336
337     case recordTypes.ScheduleStyleRecalculation:
338         bindings._lastScheduleStyleRecalculation[this.frameId()] = this;
339         break;
340
341     case recordTypes.RecalculateStyles:
342         this._initiator = bindings._lastScheduleStyleRecalculation[this.frameId()];
343         break;
344
345     case recordTypes.InvalidateLayout:
346         // Consider style recalculation as a reason for layout invalidation,
347         // but only if we had no earlier layout invalidation records.
348         var layoutInitator = this;
349         if (!bindings._layoutInvalidate[this.frameId()] && parentRecord.type() === recordTypes.RecalculateStyles)
350             layoutInitator = parentRecord._initiator;
351         bindings._layoutInvalidate[this.frameId()] = layoutInitator;
352         break;
353
354     case recordTypes.Layout:
355         this._initiator = bindings._layoutInvalidate[this.frameId()];
356         bindings._layoutInvalidate[this.frameId()] = null;
357         if (this.stackTrace())
358             this.addWarning(WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck."));
359         break;
360
361     case recordTypes.WebSocketCreate:
362         bindings._webSocketCreateRecords[timelineEvent.data["identifier"]] = this;
363         break;
364
365     case recordTypes.WebSocketSendHandshakeRequest:
366     case recordTypes.WebSocketReceiveHandshakeResponse:
367     case recordTypes.WebSocketDestroy:
368         this._initiator = bindings._webSocketCreateRecords[timelineEvent.data["identifier"]];
369         break;
370     }
371 }
372
373 WebInspector.TimelineModel.RecordImpl.prototype = {
374     /**
375      * @return {?Array.<!ConsoleAgent.CallFrame>}
376      */
377     callSiteStackTrace: function()
378     {
379         return this._initiator ? this._initiator.stackTrace() : null;
380     },
381
382     /**
383      * @return {?WebInspector.TimelineModel.Record}
384      */
385     initiator: function()
386     {
387         return this._initiator;
388     },
389
390     /**
391      * @return {?WebInspector.Target}
392      */
393     target: function()
394     {
395         return this._model._currentTarget;
396     },
397
398     /**
399      * @return {number}
400      */
401     selfTime: function()
402     {
403         return this._selfTime;
404     },
405
406     /**
407      * @return {!Array.<!WebInspector.TimelineModel.Record>}
408      */
409     children: function()
410     {
411         return this._children;
412     },
413
414     /**
415      * @return {number}
416      */
417     startTime: function()
418     {
419         return this._record.startTime;
420     },
421
422     /**
423      * @return {string}
424      */
425     thread: function()
426     {
427         return this._thread;
428     },
429
430     /**
431      * @return {number}
432      */
433     endTime: function()
434     {
435         return this._endTime || this._record.endTime || this._record.startTime;
436     },
437
438     /**
439      * @param {number} endTime
440      */
441     setEndTime: function(endTime)
442     {
443         this._endTime = endTime;
444     },
445
446     /**
447      * @return {!Object}
448      */
449     data: function()
450     {
451         return this._record.data;
452     },
453
454     /**
455      * @return {string}
456      */
457     type: function()
458     {
459         return this._record.type;
460     },
461
462     /**
463      * @return {string}
464      */
465     frameId: function()
466     {
467         return this._record.frameId || "";
468     },
469
470     /**
471      * @return {?Array.<!ConsoleAgent.CallFrame>}
472      */
473     stackTrace: function()
474     {
475         if (this._record.stackTrace && this._record.stackTrace.length)
476             return this._record.stackTrace;
477         return null;
478     },
479
480     /**
481      * @param {string} key
482      * @return {?Object}
483      */
484     getUserObject: function(key)
485     {
486         if (!this._userObjects)
487             return null;
488         return this._userObjects.get(key);
489     },
490
491     /**
492      * @param {string} key
493      * @param {?Object|undefined} value
494      */
495     setUserObject: function(key, value)
496     {
497         if (!this._userObjects)
498             this._userObjects = new StringMap();
499         this._userObjects.put(key, value);
500     },
501
502     /**
503      * @param {string} message
504      */
505     addWarning: function(message)
506     {
507         if (!this._warnings)
508             this._warnings = [];
509         this._warnings.push(message);
510     },
511
512     /**
513      * @return {?Array.<string>}
514      */
515     warnings: function()
516     {
517         return this._warnings;
518     }
519 }
520
521 /**
522  * @constructor
523  * @implements {WebInspector.OutputStream}
524  * @param {!WebInspector.TimelineModel} model
525  * @param {!{cancel: function()}} reader
526  * @param {!WebInspector.Progress} progress
527  */
528 WebInspector.TimelineModelLoader = function(model, reader, progress)
529 {
530     this._model = model;
531     this._reader = reader;
532     this._progress = progress;
533     this._buffer = "";
534     this._firstChunk = true;
535 }
536
537 WebInspector.TimelineModelLoader.prototype = {
538     /**
539      * @param {string} chunk
540      */
541     write: function(chunk)
542     {
543         var data = this._buffer + chunk;
544         var lastIndex = 0;
545         var index;
546         do {
547             index = lastIndex;
548             lastIndex = WebInspector.TextUtils.findBalancedCurlyBrackets(data, index);
549         } while (lastIndex !== -1)
550
551         var json = data.slice(0, index) + "]";
552         this._buffer = data.slice(index);
553
554         if (!index)
555             return;
556
557         if (this._firstChunk) {
558             this._firstChunk = false;
559             this._model.reset();
560         } else {
561             // Prepending "0" to turn string into valid JSON.
562             json = "[0" + json;
563         }
564
565         var items;
566         try {
567             items = /** @type {!Array.<!TimelineAgent.TimelineEvent>} */ (JSON.parse(json));
568         } catch (e) {
569             WebInspector.console.error("Malformed timeline data.");
570             this._model.reset();
571             this._reader.cancel();
572             this._progress.done();
573             return;
574         }
575
576         // Skip 0-th element - it is either version or 0.
577         for (var i = 1, size = items.length; i < size; ++i)
578             this._model._addRecord(items[i]);
579     },
580
581     close: function()
582     {
583     }
584 }
585
586 /**
587  * @constructor
588  * @param {!WebInspector.OutputStream} stream
589  */
590 WebInspector.TimelineSaver = function(stream)
591 {
592     this._stream = stream;
593 }
594
595 WebInspector.TimelineSaver.prototype = {
596     /**
597      * @param {!Array.<*>} payloads
598      * @param {string} version
599      */
600     save: function(payloads, version)
601     {
602         this._payloads = payloads;
603         this._recordIndex = 0;
604         this._prologue = "[" + JSON.stringify(version);
605
606         this._writeNextChunk(this._stream);
607     },
608
609     _writeNextChunk: function(stream)
610     {
611         const separator = ",\n";
612         var data = [];
613         var length = 0;
614
615         if (this._prologue) {
616             data.push(this._prologue);
617             length += this._prologue.length;
618             delete this._prologue;
619         } else {
620             if (this._recordIndex === this._payloads.length) {
621                 stream.close();
622                 return;
623             }
624             data.push("");
625         }
626         while (this._recordIndex < this._payloads.length) {
627             var item = JSON.stringify(this._payloads[this._recordIndex]);
628             var itemLength = item.length + separator.length;
629             if (length + itemLength > WebInspector.TimelineModelImpl.TransferChunkLengthBytes)
630                 break;
631             length += itemLength;
632             data.push(item);
633             ++this._recordIndex;
634         }
635         if (this._recordIndex === this._payloads.length)
636             data.push(data.pop() + "]");
637         stream.write(data.join(separator), this._writeNextChunk.bind(this));
638     }
639 }