Uncaught Exception: Unexpected enum value: CPU
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Controllers / TimelineManager.js
1 /*
2  * Copyright (C) 2013, 2016 Apple 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
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 // FIXME: TimelineManager lacks advanced multi-target support. (Instruments/Profilers per-target)
27
28 WI.TimelineManager = class TimelineManager extends WI.Object
29 {
30     constructor()
31     {
32         super();
33
34         WI.Frame.addEventListener(WI.Frame.Event.ProvisionalLoadStarted, this._provisionalLoadStarted, this);
35         WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
36
37         this._enabledTimelineTypesSetting = new WI.Setting("enabled-instrument-types", WI.TimelineManager.defaultTimelineTypes());
38
39         this._capturingState = TimelineManager.CapturingState.Inactive;
40         this._capturingInstrumentCount = 0;
41         this._capturingStartTime = NaN;
42         this._capturingEndTime = NaN;
43
44         this._initiatedByBackendStart = false;
45         this._initiatedByBackendStop = false;
46
47         this._isCapturingPageReload = false;
48         this._autoCaptureOnPageLoad = false;
49         this._mainResourceForAutoCapturing = null;
50         this._shouldSetAutoCapturingMainResource = false;
51         this._transitioningPageTarget = false;
52
53         this._webTimelineScriptRecordsExpectingScriptProfilerEvents = null;
54         this._scriptProfilerRecords = null;
55
56         this._boundStopCapturing = this.stopCapturing.bind(this);
57         this._stopCapturingTimeout = undefined;
58         this._deadTimeTimeout = undefined;
59         this._lastDeadTimeTickle = 0;
60
61         this.reset();
62     }
63
64     // Target
65
66     initializeTarget(target)
67     {
68         if (target.TimelineAgent) {
69             this._updateAutoCaptureInstruments([target]);
70
71             // COMPATIBILITY (iOS 9): Timeline.setAutoCaptureEnabled did not exist.
72             if (target.TimelineAgent.setAutoCaptureEnabled)
73                 target.TimelineAgent.setAutoCaptureEnabled(this._autoCaptureOnPageLoad);
74         }
75     }
76
77     transitionPageTarget()
78     {
79         this._transitioningPageTarget = true;
80     }
81
82     // Static
83
84     static defaultTimelineTypes()
85     {
86         if (WI.sharedApp.debuggableType === WI.DebuggableType.JavaScript) {
87             let defaultTypes = [WI.TimelineRecord.Type.Script];
88             if (WI.HeapAllocationsInstrument.supported())
89                 defaultTypes.push(WI.TimelineRecord.Type.HeapAllocations);
90             return defaultTypes;
91         }
92
93         if (WI.sharedApp.debuggableType === WI.DebuggableType.ServiceWorker) {
94             // FIXME: Support Network Timeline in ServiceWorker.
95             let defaultTypes = [WI.TimelineRecord.Type.Script];
96             if (WI.HeapAllocationsInstrument.supported())
97                 defaultTypes.push(WI.TimelineRecord.Type.HeapAllocations);
98             return defaultTypes;
99         }
100
101         let defaultTypes = [
102             WI.TimelineRecord.Type.Network,
103             WI.TimelineRecord.Type.Layout,
104             WI.TimelineRecord.Type.Script,
105         ];
106
107         if (WI.CPUInstrument.supported())
108             defaultTypes.push(WI.TimelineRecord.Type.CPU);
109
110         if (WI.FPSInstrument.supported())
111             defaultTypes.push(WI.TimelineRecord.Type.RenderingFrame);
112
113         return defaultTypes;
114     }
115
116     static availableTimelineTypes()
117     {
118         let types = WI.TimelineManager.defaultTimelineTypes();
119         if (WI.sharedApp.debuggableType === WI.DebuggableType.JavaScript || WI.sharedApp.debuggableType === WI.DebuggableType.ServiceWorker)
120             return types;
121
122         if (WI.MemoryInstrument.supported())
123             types.push(WI.TimelineRecord.Type.Memory);
124
125         if (WI.HeapAllocationsInstrument.supported())
126             types.push(WI.TimelineRecord.Type.HeapAllocations);
127
128         if (WI.MediaInstrument.supported()) {
129             let insertionIndex = types.indexOf(WI.TimelineRecord.Type.Layout) + 1;
130             types.insertAtIndex(WI.TimelineRecord.Type.Media, insertionIndex || types.length);
131         }
132
133         return types;
134     }
135
136     static synthesizeImportError(message)
137     {
138         message = WI.UIString("Timeline Recording Import Error: %s").format(message);
139
140         if (window.InspectorTest) {
141             console.error(message);
142             return;
143         }
144
145         let consoleMessage = new WI.ConsoleMessage(WI.mainTarget, WI.ConsoleMessage.MessageSource.Other, WI.ConsoleMessage.MessageLevel.Error, message);
146         consoleMessage.shouldRevealConsole = true;
147
148         WI.consoleLogViewController.appendConsoleMessage(consoleMessage);
149     }
150
151     // Public
152
153     get capturingState() { return this._capturingState; }
154
155     reset()
156     {
157         if (this.isCapturing())
158             this.stopCapturing();
159
160         this._recordings = [];
161         this._activeRecording = null;
162         this._nextRecordingIdentifier = 1;
163
164         this._loadNewRecording();
165     }
166
167     // The current recording that new timeline records will be appended to, if any.
168     get activeRecording()
169     {
170         console.assert(this._activeRecording || !this.isCapturing());
171         return this._activeRecording;
172     }
173
174     get recordings()
175     {
176         return this._recordings.slice();
177     }
178
179     get autoCaptureOnPageLoad()
180     {
181         return this._autoCaptureOnPageLoad;
182     }
183
184     set autoCaptureOnPageLoad(autoCapture)
185     {
186         autoCapture = !!autoCapture;
187
188         if (this._autoCaptureOnPageLoad === autoCapture)
189             return;
190
191         this._autoCaptureOnPageLoad = autoCapture;
192
193         for (let target of WI.targets) {
194             if (target.TimelineAgent)
195                 target.TimelineAgent.setAutoCaptureEnabled(this._autoCaptureOnPageLoad);
196         }
197     }
198
199     get enabledTimelineTypes()
200     {
201         let availableTimelineTypes = WI.TimelineManager.availableTimelineTypes();
202         return this._enabledTimelineTypesSetting.value.filter((type) => availableTimelineTypes.includes(type));
203     }
204
205     set enabledTimelineTypes(x)
206     {
207         this._enabledTimelineTypesSetting.value = x || [];
208
209         this._updateAutoCaptureInstruments(WI.targets);
210     }
211
212     isCapturing()
213     {
214         return this._capturingState !== TimelineManager.CapturingState.Inactive;
215     }
216
217     isCapturingPageReload()
218     {
219         return this._isCapturingPageReload;
220     }
221
222     willAutoStop()
223     {
224         return !!this._stopCapturingTimeout;
225     }
226
227     relaxAutoStop()
228     {
229         if (this._stopCapturingTimeout) {
230             clearTimeout(this._stopCapturingTimeout);
231             this._stopCapturingTimeout = undefined;
232         }
233
234         if (this._deadTimeTimeout) {
235             clearTimeout(this._deadTimeTimeout);
236             this._deadTimeTimeout = undefined;
237         }
238     }
239
240     startCapturing(shouldCreateRecording)
241     {
242         console.assert(this._capturingState === TimelineManager.CapturingState.Stopping || this._capturingState === TimelineManager.CapturingState.Inactive, "TimelineManager is already capturing.");
243         if (this._capturingState !== TimelineManager.CapturingState.Stopping && this._capturingState !== TimelineManager.CapturingState.Inactive)
244             return;
245
246         if (!this._activeRecording || shouldCreateRecording)
247             this._loadNewRecording();
248
249         this._updateCapturingState(TimelineManager.CapturingState.Starting);
250
251         this._capturingStartTime = NaN;
252         this._activeRecording.start(this._initiatedByBackendStart);
253     }
254
255     stopCapturing()
256     {
257         console.assert(this._capturingState === TimelineManager.CapturingState.Starting || this._capturingState === TimelineManager.CapturingState.Active, "TimelineManager is not capturing.");
258         if (this._capturingState !== TimelineManager.CapturingState.Starting && this._capturingState !== TimelineManager.CapturingState.Active)
259             return;
260
261         this._updateCapturingState(TimelineManager.CapturingState.Stopping);
262
263         this._capturingEndTime = NaN;
264         this._activeRecording.stop(this._initiatedByBackendStop);
265     }
266
267     async processJSON({filename, json, error})
268     {
269         if (error) {
270             WI.TimelineManager.synthesizeImportError(error);
271             return;
272         }
273
274         if (typeof json !== "object" || json === null) {
275             WI.TimelineManager.synthesizeImportError(WI.UIString("invalid JSON"));
276             return;
277         }
278
279         if (!json.recording  || typeof json.recording !== "object" || !json.overview || typeof json.overview !== "object" || typeof json.version !== "number") {
280             WI.TimelineManager.synthesizeImportError(WI.UIString("invalid JSON"));
281             return;
282         }
283
284         if (json.version !== WI.TimelineRecording.SerializationVersion) {
285             WI.NetworkManager.synthesizeImportError(WI.UIString("unsupported version"));
286             return;
287         }
288
289         let recordingData = json.recording;
290         let overviewData = json.overview;
291
292         let identifier = this._nextRecordingIdentifier++;
293         let newRecording = WI.TimelineRecording.import(identifier, recordingData, filename);
294         this._recordings.push(newRecording);
295
296         this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingCreated, {recording: newRecording});
297
298         if (this.isCapturing())
299             this.stopCapturing();
300
301         let oldRecording = this._activeRecording;
302         if (oldRecording) {
303             const importing = true;
304             oldRecording.unloaded(importing);
305         }
306
307         this._activeRecording = newRecording;
308
309         this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingLoaded, {oldRecording});
310         this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingImported, {overviewData});
311     }
312
313     computeElapsedTime(timestamp)
314     {
315         if (!this._activeRecording)
316             return 0;
317
318         return this._activeRecording.computeElapsedTime(timestamp);
319     }
320
321     scriptProfilerIsTracking()
322     {
323         return this._scriptProfilerRecords !== null;
324     }
325
326     // Protected
327
328     capturingStarted(startTime)
329     {
330         // Called from WI.TimelineObserver.
331
332         // The frontend didn't start capturing, so this was a programmatic start.
333         if (this._capturingState === TimelineManager.CapturingState.Inactive) {
334             this._initiatedByBackendStart = true;
335             this._activeRecording.addScriptInstrumentForProgrammaticCapture();
336             this.startCapturing();
337         }
338
339         if (!isNaN(startTime)) {
340             if (isNaN(this._capturingStartTime) || startTime < this._capturingStartTime)
341                 this._capturingStartTime = startTime;
342
343             this._activeRecording.initializeTimeBoundsIfNecessary(startTime);
344         }
345
346         this._capturingInstrumentCount++;
347         console.assert(this._capturingInstrumentCount);
348         if (this._capturingInstrumentCount > 1)
349             return;
350
351         if (this._capturingState === TimelineManager.CapturingState.Active)
352             return;
353
354         this._lastDeadTimeTickle = 0;
355
356         this._webTimelineScriptRecordsExpectingScriptProfilerEvents = [];
357
358         this._activeRecording.capturingStarted(this._capturingStartTime);
359
360         WI.settings.timelinesAutoStop.addEventListener(WI.Setting.Event.Changed, this._handleTimelinesAutoStopSettingChanged, this);
361
362         WI.Frame.addEventListener(WI.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this);
363         WI.Target.addEventListener(WI.Target.Event.ResourceAdded, this._resourceWasAdded, this);
364
365         WI.heapManager.addEventListener(WI.HeapManager.Event.GarbageCollected, this._garbageCollected, this);
366
367         WI.memoryManager.addEventListener(WI.MemoryManager.Event.MemoryPressure, this._memoryPressure, this);
368
369         WI.DOMNode.addEventListener(WI.DOMNode.Event.DidFireEvent, this._handleDOMNodeDidFireEvent, this);
370         WI.DOMNode.addEventListener(WI.DOMNode.Event.PowerEfficientPlaybackStateChanged, this._handleDOMNodePowerEfficientPlaybackStateChanged, this);
371
372         this._updateCapturingState(TimelineManager.CapturingState.Active, {startTime: this._capturingStartTime});
373     }
374
375     capturingStopped(endTime)
376     {
377         // Called from WI.TimelineObserver.
378
379         // The frontend didn't stop capturing, so this was a programmatic stop.
380         if (this._capturingState === TimelineManager.CapturingState.Active) {
381             this._initiatedByBackendStop = true;
382             this.stopCapturing();
383         }
384
385         if (!isNaN(endTime)) {
386             if (isNaN(this._capturingEndTime) || endTime > this._capturingEndTime)
387                 this._capturingEndTime = endTime;
388         }
389
390         this._capturingInstrumentCount--;
391         console.assert(this._capturingInstrumentCount >= 0);
392         if (this._capturingInstrumentCount)
393             return;
394
395         if (this._capturingState === TimelineManager.CapturingState.Inactive)
396             return;
397
398         WI.DOMNode.removeEventListener(null, null, this);
399         WI.memoryManager.removeEventListener(null, null, this);
400         WI.heapManager.removeEventListener(null, null, this);
401         WI.Target.removeEventListener(WI.Target.Event.ResourceAdded, this._resourceWasAdded, this);
402         WI.Frame.removeEventListener(WI.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this);
403         WI.settings.timelinesAutoStop.removeEventListener(null, null, this);
404
405         this._activeRecording.capturingStopped(this._capturingEndTime);
406
407         this.relaxAutoStop();
408
409         this._isCapturingPageReload = false;
410         this._shouldSetAutoCapturingMainResource = false;
411         this._mainResourceForAutoCapturing = null;
412         this._initiatedByBackendStart = false;
413         this._initiatedByBackendStop = false;
414
415         this._updateCapturingState(TimelineManager.CapturingState.Inactive, {endTime: this._capturingEndTime});
416     }
417
418     autoCaptureStarted()
419     {
420         // Called from WI.TimelineObserver.
421
422         let waitingForCapturingStartedEvent = this._capturingState === TimelineManager.CapturingState.Starting;
423
424         if (this.isCapturing())
425             this.stopCapturing();
426
427         this._initiatedByBackendStart = true;
428
429         // We may already have an fresh TimelineRecording created if autoCaptureStarted is received
430         // between sending the Timeline.start command and receiving Timeline.capturingStarted event.
431         // In that case, there is no need to call startCapturing again. Reuse the fresh recording.
432         if (!waitingForCapturingStartedEvent) {
433             const createNewRecording = true;
434             this.startCapturing(createNewRecording);
435         }
436
437         this._shouldSetAutoCapturingMainResource = true;
438     }
439
440     eventRecorded(recordPayload)
441     {
442         // Called from WI.TimelineObserver.
443
444         console.assert(this.isCapturing());
445         if (!this.isCapturing())
446             return;
447
448         var records = [];
449
450         // Iterate over the records tree using a stack. Doing this recursively has
451         // been known to cause a call stack overflow. https://webkit.org/b/79106
452         var stack = [{array: [recordPayload], parent: null, parentRecord: null, index: 0}];
453         while (stack.length) {
454             var entry = stack.lastValue;
455             var recordPayloads = entry.array;
456
457             if (entry.index < recordPayloads.length) {
458                 var recordPayload = recordPayloads[entry.index];
459                 var record = this._processEvent(recordPayload, entry.parent);
460                 if (record) {
461                     record.parent = entry.parentRecord;
462                     records.push(record);
463                     if (entry.parentRecord)
464                         entry.parentRecord.children.push(record);
465                 }
466
467                 if (recordPayload.children && recordPayload.children.length)
468                     stack.push({array: recordPayload.children, parent: recordPayload, parentRecord: record || entry.parentRecord, index: 0});
469                 ++entry.index;
470             } else
471                 stack.pop();
472         }
473
474         for (var record of records) {
475             if (record.type === WI.TimelineRecord.Type.RenderingFrame) {
476                 if (!record.children.length)
477                     continue;
478                 record.setupFrameIndex();
479             }
480
481             this._addRecord(record);
482         }
483     }
484
485     pageDOMContentLoadedEventFired(timestamp)
486     {
487         // Called from WI.PageObserver.
488
489         console.assert(this._activeRecording);
490         console.assert(isNaN(WI.networkManager.mainFrame.domContentReadyEventTimestamp));
491
492         let computedTimestamp = this._activeRecording.computeElapsedTime(timestamp);
493
494         WI.networkManager.mainFrame.markDOMContentReadyEvent(computedTimestamp);
495
496         let eventMarker = new WI.TimelineMarker(computedTimestamp, WI.TimelineMarker.Type.DOMContentEvent);
497         this._activeRecording.addEventMarker(eventMarker);
498     }
499
500     pageLoadEventFired(timestamp)
501     {
502         // Called from WI.PageObserver.
503
504         console.assert(this._activeRecording);
505         console.assert(isNaN(WI.networkManager.mainFrame.loadEventTimestamp));
506
507         let computedTimestamp = this._activeRecording.computeElapsedTime(timestamp);
508
509         WI.networkManager.mainFrame.markLoadEvent(computedTimestamp);
510
511         let eventMarker = new WI.TimelineMarker(computedTimestamp, WI.TimelineMarker.Type.LoadEvent);
512         this._activeRecording.addEventMarker(eventMarker);
513
514         this._stopAutoRecordingSoon();
515     }
516
517     cpuProfilerTrackingStarted(timestamp)
518     {
519         // Called from WI.CPUProfilerObserver.
520
521         this.capturingStarted(timestamp);
522     }
523
524     cpuProfilerTrackingUpdated(event)
525     {
526         // Called from WI.CPUProfilerObserver.
527
528         console.assert(this.isCapturing());
529         if (!this.isCapturing())
530             return;
531
532         this._addRecord(new WI.CPUTimelineRecord(event));
533     }
534
535     cpuProfilerTrackingCompleted(timestamp)
536     {
537         // Called from WI.CPUProfilerObserver.
538
539         this.capturingStopped(timestamp);
540     }
541
542     memoryTrackingStarted(timestamp)
543     {
544         // Called from WI.MemoryObserver.
545
546         this.capturingStarted(timestamp);
547     }
548
549     memoryTrackingUpdated(event)
550     {
551         // Called from WI.MemoryObserver.
552
553         console.assert(this.isCapturing());
554         if (!this.isCapturing())
555             return;
556
557         this._addRecord(new WI.MemoryTimelineRecord(event.timestamp, event.categories));
558     }
559
560     memoryTrackingCompleted(timestamp)
561     {
562         // Called from WI.MemoryObserver.
563
564         this.capturingStopped(timestamp);
565     }
566
567     heapTrackingStarted(timestamp, snapshot)
568     {
569         // Called from WI.HeapObserver.
570
571         this.capturingStarted(timestamp);
572
573         this._addRecord(new WI.HeapAllocationsTimelineRecord(timestamp, snapshot));
574     }
575
576     heapTrackingCompleted(timestamp, snapshot)
577     {
578         // Called from WI.HeapObserver.
579
580         this._addRecord(new WI.HeapAllocationsTimelineRecord(timestamp, snapshot));
581
582         this.capturingStopped();
583     }
584
585     heapSnapshotAdded(timestamp, snapshot)
586     {
587         // Called from WI.HeapAllocationsInstrument.
588
589         this._addRecord(new WI.HeapAllocationsTimelineRecord(timestamp, snapshot));
590     }
591
592     // Private
593
594     _updateCapturingState(state, data = {})
595     {
596         if (this._capturingState === state)
597             return;
598
599         this._capturingState = state;
600
601         this.dispatchEventToListeners(TimelineManager.Event.CapturingStateChanged, data);
602     }
603
604     _processRecord(recordPayload, parentRecordPayload)
605     {
606         console.assert(this.isCapturing());
607
608         var startTime = this._activeRecording.computeElapsedTime(recordPayload.startTime);
609         var endTime = this._activeRecording.computeElapsedTime(recordPayload.endTime);
610         var callFrames = this._callFramesFromPayload(recordPayload.stackTrace);
611
612         var significantCallFrame = null;
613         if (callFrames) {
614             for (var i = 0; i < callFrames.length; ++i) {
615                 if (callFrames[i].nativeCode)
616                     continue;
617                 significantCallFrame = callFrames[i];
618                 break;
619             }
620         }
621
622         var sourceCodeLocation = significantCallFrame && significantCallFrame.sourceCodeLocation;
623
624         switch (recordPayload.type) {
625         case TimelineAgent.EventType.ScheduleStyleRecalculation:
626             console.assert(isNaN(endTime));
627
628             // Pass the startTime as the endTime since this record type has no duration.
629             return new WI.LayoutTimelineRecord(WI.LayoutTimelineRecord.EventType.InvalidateStyles, startTime, startTime, callFrames, sourceCodeLocation);
630
631         case TimelineAgent.EventType.RecalculateStyles:
632             return new WI.LayoutTimelineRecord(WI.LayoutTimelineRecord.EventType.RecalculateStyles, startTime, endTime, callFrames, sourceCodeLocation);
633
634         case TimelineAgent.EventType.InvalidateLayout:
635             console.assert(isNaN(endTime));
636
637             // Pass the startTime as the endTime since this record type has no duration.
638             return new WI.LayoutTimelineRecord(WI.LayoutTimelineRecord.EventType.InvalidateLayout, startTime, startTime, callFrames, sourceCodeLocation);
639
640         case TimelineAgent.EventType.Layout:
641             var layoutRecordType = sourceCodeLocation ? WI.LayoutTimelineRecord.EventType.ForcedLayout : WI.LayoutTimelineRecord.EventType.Layout;
642             var quad = new WI.Quad(recordPayload.data.root);
643             return new WI.LayoutTimelineRecord(layoutRecordType, startTime, endTime, callFrames, sourceCodeLocation, quad);
644
645         case TimelineAgent.EventType.Paint:
646             var quad = new WI.Quad(recordPayload.data.clip);
647             return new WI.LayoutTimelineRecord(WI.LayoutTimelineRecord.EventType.Paint, startTime, endTime, callFrames, sourceCodeLocation, quad);
648
649         case TimelineAgent.EventType.Composite:
650             return new WI.LayoutTimelineRecord(WI.LayoutTimelineRecord.EventType.Composite, startTime, endTime, callFrames, sourceCodeLocation);
651
652         case TimelineAgent.EventType.RenderingFrame:
653             if (!recordPayload.children || !recordPayload.children.length)
654                 return null;
655
656             return new WI.RenderingFrameTimelineRecord(startTime, endTime);
657
658         case TimelineAgent.EventType.EvaluateScript:
659             if (!sourceCodeLocation) {
660                 var mainFrame = WI.networkManager.mainFrame;
661                 var scriptResource = mainFrame.url === recordPayload.data.url ? mainFrame.mainResource : mainFrame.resourceForURL(recordPayload.data.url, true);
662                 if (scriptResource) {
663                     // The lineNumber is 1-based, but we expect 0-based.
664                     let lineNumber = recordPayload.data.lineNumber - 1;
665                     let columnNumber = "columnNumber" in recordPayload.data ? recordPayload.data.columnNumber - 1 : 0;
666                     sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, columnNumber);
667                 }
668             }
669
670             var profileData = recordPayload.data.profile;
671
672             var record;
673             switch (parentRecordPayload && parentRecordPayload.type) {
674             case TimelineAgent.EventType.TimerFire:
675                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profileData);
676                 break;
677             case TimelineAgent.EventType.ObserverCallback:
678                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ObserverCallback, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.type, profileData);
679                 break;
680             case TimelineAgent.EventType.FireAnimationFrame:
681                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.AnimationFrameFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
682                 break;
683             default:
684                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, null, profileData);
685                 break;
686             }
687
688             this._webTimelineScriptRecordsExpectingScriptProfilerEvents.push(record);
689             return record;
690
691         case TimelineAgent.EventType.ConsoleProfile:
692             var profileData = recordPayload.data.profile;
693             // COMPATIBILITY (iOS 9): With the Sampling Profiler, profiles no longer include legacy profile data.
694             console.assert(profileData || TimelineAgent.setInstruments);
695             return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ConsoleProfileRecorded, startTime, endTime, callFrames, sourceCodeLocation, recordPayload.data.title, profileData);
696
697         case TimelineAgent.EventType.TimerFire:
698         case TimelineAgent.EventType.EventDispatch:
699         case TimelineAgent.EventType.FireAnimationFrame:
700         case TimelineAgent.EventType.ObserverCallback:
701             // These are handled when we see the child FunctionCall or EvaluateScript.
702             break;
703
704         case TimelineAgent.EventType.FunctionCall:
705             // FunctionCall always happens as a child of another record, and since the FunctionCall record
706             // has useful info we just make the timeline record here (combining the data from both records).
707             if (!parentRecordPayload) {
708                 console.warn("Unexpectedly received a FunctionCall timeline record without a parent record");
709                 break;
710             }
711
712             if (!sourceCodeLocation) {
713                 var mainFrame = WI.networkManager.mainFrame;
714                 var scriptResource = mainFrame.url === recordPayload.data.scriptName ? mainFrame.mainResource : mainFrame.resourceForURL(recordPayload.data.scriptName, true);
715                 if (scriptResource) {
716                     // The lineNumber is 1-based, but we expect 0-based.
717                     let lineNumber = recordPayload.data.scriptLine - 1;
718                     let columnNumber = "scriptColumn" in recordPayload.data ? recordPayload.data.scriptColumn - 1 : 0;
719                     sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, columnNumber);
720                 }
721             }
722
723             var profileData = recordPayload.data.profile;
724
725             var record;
726             switch (parentRecordPayload.type) {
727             case TimelineAgent.EventType.TimerFire:
728                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profileData);
729                 break;
730             case TimelineAgent.EventType.EventDispatch:
731                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.type, profileData, parentRecordPayload.data);
732                 break;
733             case TimelineAgent.EventType.ObserverCallback:
734                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ObserverCallback, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.type, profileData);
735                 break;
736             case TimelineAgent.EventType.FireAnimationFrame:
737                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.AnimationFrameFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
738                 break;
739             case TimelineAgent.EventType.FunctionCall:
740                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
741                 break;
742             case TimelineAgent.EventType.RenderingFrame:
743                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
744                 break;
745
746             default:
747                 console.assert(false, "Missed FunctionCall embedded inside of: " + parentRecordPayload.type);
748                 break;
749             }
750
751             if (record) {
752                 this._webTimelineScriptRecordsExpectingScriptProfilerEvents.push(record);
753                 return record;
754             }
755             break;
756
757         case TimelineAgent.EventType.ProbeSample:
758             // Pass the startTime as the endTime since this record type has no duration.
759             sourceCodeLocation = WI.debuggerManager.probeForIdentifier(recordPayload.data.probeId).breakpoint.sourceCodeLocation;
760             return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ProbeSampleRecorded, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.probeId);
761
762         case TimelineAgent.EventType.TimerInstall:
763             console.assert(isNaN(endTime));
764
765             // Pass the startTime as the endTime since this record type has no duration.
766             var timerDetails = {timerId: recordPayload.data.timerId, timeout: recordPayload.data.timeout, repeating: !recordPayload.data.singleShot};
767             return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.TimerInstalled, startTime, startTime, callFrames, sourceCodeLocation, timerDetails);
768
769         case TimelineAgent.EventType.TimerRemove:
770             console.assert(isNaN(endTime));
771
772             // Pass the startTime as the endTime since this record type has no duration.
773             return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.TimerRemoved, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId);
774
775         case TimelineAgent.EventType.RequestAnimationFrame:
776             console.assert(isNaN(endTime));
777
778             // Pass the startTime as the endTime since this record type has no duration.
779             return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.AnimationFrameRequested, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.id);
780
781         case TimelineAgent.EventType.CancelAnimationFrame:
782             console.assert(isNaN(endTime));
783
784             // Pass the startTime as the endTime since this record type has no duration.
785             return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.AnimationFrameCanceled, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.id);
786
787         default:
788             console.error("Missing handling of Timeline Event Type: " + recordPayload.type);
789         }
790
791         return null;
792     }
793
794     _processEvent(recordPayload, parentRecordPayload)
795     {
796         console.assert(this.isCapturing());
797
798         switch (recordPayload.type) {
799         case TimelineAgent.EventType.TimeStamp:
800             var timestamp = this._activeRecording.computeElapsedTime(recordPayload.startTime);
801             var eventMarker = new WI.TimelineMarker(timestamp, WI.TimelineMarker.Type.TimeStamp, recordPayload.data.message);
802             this._activeRecording.addEventMarker(eventMarker);
803             break;
804
805         case TimelineAgent.EventType.Time:
806         case TimelineAgent.EventType.TimeEnd:
807             // FIXME: <https://webkit.org/b/150690> Web Inspector: Show console.time/timeEnd ranges in Timeline
808             // FIXME: Make use of "message" payload properties.
809             break;
810
811         default:
812             return this._processRecord(recordPayload, parentRecordPayload);
813         }
814
815         return null;
816     }
817
818     _loadNewRecording()
819     {
820         if (this._activeRecording && this._activeRecording.isEmpty())
821             return;
822
823         let instruments = this.enabledTimelineTypes.map((type) => WI.Instrument.createForTimelineType(type));
824         let identifier = this._nextRecordingIdentifier++;
825         let newRecording = new WI.TimelineRecording(identifier, WI.UIString("Timeline Recording %d").format(identifier), instruments);
826
827         this._recordings.push(newRecording);
828         this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingCreated, {recording: newRecording});
829
830         if (this.isCapturing())
831             this.stopCapturing();
832
833         var oldRecording = this._activeRecording;
834         if (oldRecording)
835             oldRecording.unloaded();
836
837         this._activeRecording = newRecording;
838
839         // COMPATIBILITY (iOS 8): When using Legacy timestamps, a navigation will have computed
840         // the main resource's will send request timestamp in terms of the last page's base timestamp.
841         // Now that we have navigated, we should reset the legacy base timestamp and the
842         // will send request timestamp for the new main resource. This way, all new timeline
843         // records will be computed relative to the new navigation.
844         if (this._mainResourceForAutoCapturing && WI.TimelineRecording.isLegacy) {
845             console.assert(this._mainResourceForAutoCapturing.originalRequestWillBeSentTimestamp);
846             this._activeRecording.setLegacyBaseTimestamp(this._mainResourceForAutoCapturing.originalRequestWillBeSentTimestamp);
847             this._mainResourceForAutoCapturing._requestSentTimestamp = 0;
848         }
849
850         this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingLoaded, {oldRecording});
851     }
852
853     _callFramesFromPayload(payload)
854     {
855         if (!payload)
856             return null;
857
858         return payload.map((x) => WI.CallFrame.fromPayload(WI.assumingMainTarget(), x));
859     }
860
861     _addRecord(record)
862     {
863         this._activeRecording.addRecord(record);
864
865         // Only worry about dead time after the load event.
866         if (WI.networkManager.mainFrame && isNaN(WI.networkManager.mainFrame.loadEventTimestamp))
867             this._resetAutoRecordingDeadTimeTimeout();
868     }
869
870     _attemptAutoCapturingForFrame(frame)
871     {
872         if (!this._autoCaptureOnPageLoad)
873             return false;
874
875         if (!frame.isMainFrame())
876             return false;
877
878         // COMPATIBILITY (iOS 9): Timeline.setAutoCaptureEnabled did not exist.
879         // Perform auto capture in the frontend.
880         if (!InspectorBackend.domains.Timeline)
881             return false;
882         if (!InspectorBackend.domains.Timeline.setAutoCaptureEnabled)
883             return this._legacyAttemptStartAutoCapturingForFrame(frame);
884
885         if (!this._shouldSetAutoCapturingMainResource)
886             return false;
887
888         console.assert(this.isCapturing(), "We saw autoCaptureStarted so we should already be capturing");
889
890         let mainResource = frame.provisionalMainResource || frame.mainResource;
891         if (mainResource === this._mainResourceForAutoCapturing)
892             return false;
893
894         let oldMainResource = frame.mainResource || null;
895         this._isCapturingPageReload = oldMainResource !== null && oldMainResource.url === mainResource.url;
896
897         this._mainResourceForAutoCapturing = mainResource;
898
899         this._addRecord(new WI.ResourceTimelineRecord(mainResource));
900
901         this._resetAutoRecordingMaxTimeTimeout();
902
903         this._shouldSetAutoCapturingMainResource = false;
904
905         return true;
906     }
907
908     _legacyAttemptStartAutoCapturingForFrame(frame)
909     {
910         if (this.isCapturing() && !this._mainResourceForAutoCapturing)
911             return false;
912
913         let mainResource = frame.provisionalMainResource || frame.mainResource;
914         if (mainResource === this._mainResourceForAutoCapturing)
915             return false;
916
917         let oldMainResource = frame.mainResource || null;
918         this._isCapturingPageReload = oldMainResource !== null && oldMainResource.url === mainResource.url;
919
920         if (this.isCapturing())
921             this.stopCapturing();
922
923         this._mainResourceForAutoCapturing = mainResource;
924
925         this._loadNewRecording();
926
927         this.startCapturing();
928
929         this._addRecord(new WI.ResourceTimelineRecord(mainResource));
930
931         this._resetAutoRecordingMaxTimeTimeout();
932
933         return true;
934     }
935
936     _stopAutoRecordingSoon()
937     {
938         if (!WI.settings.timelinesAutoStop.value)
939             return;
940
941         // Only auto stop when auto capturing.
942         if (!this.isCapturing() || !this._mainResourceForAutoCapturing)
943             return;
944
945         if (this._stopCapturingTimeout)
946             clearTimeout(this._stopCapturingTimeout);
947         this._stopCapturingTimeout = setTimeout(this._boundStopCapturing, WI.TimelineManager.MaximumAutoRecordDurationAfterLoadEvent);
948     }
949
950     _resetAutoRecordingMaxTimeTimeout()
951     {
952         if (!WI.settings.timelinesAutoStop.value)
953             return;
954
955         if (this._stopCapturingTimeout)
956             clearTimeout(this._stopCapturingTimeout);
957         this._stopCapturingTimeout = setTimeout(this._boundStopCapturing, WI.TimelineManager.MaximumAutoRecordDuration);
958     }
959
960     _resetAutoRecordingDeadTimeTimeout()
961     {
962         if (!WI.settings.timelinesAutoStop.value)
963             return;
964
965         // Only monitor dead time when auto capturing.
966         if (!this.isCapturing() || !this._mainResourceForAutoCapturing)
967             return;
968
969         // Avoid unnecessary churning of timeout identifier by not tickling until 10ms have passed.
970         let now = Date.now();
971         if (now <= this._lastDeadTimeTickle)
972             return;
973         this._lastDeadTimeTickle = now + 10;
974
975         if (this._deadTimeTimeout)
976             clearTimeout(this._deadTimeTimeout);
977         this._deadTimeTimeout = setTimeout(this._boundStopCapturing, WI.TimelineManager.DeadTimeRequiredToStopAutoRecordingEarly);
978     }
979
980     _provisionalLoadStarted(event)
981     {
982         this._attemptAutoCapturingForFrame(event.target);
983     }
984
985     _mainResourceDidChange(event)
986     {
987         // Ignore resource events when there isn't a main frame yet. Those events are triggered by
988         // loading the cached resources when the inspector opens, and they do not have timing information.
989         if (!WI.networkManager.mainFrame)
990             return;
991
992         let frame = event.target;
993
994         // When performing a page transition start a recording once the main resource changes.
995         // We start a legacy capture because the backend wasn't available to automatically
996         // initiate the capture, so the frontend must start the capture.
997         if (this._transitioningPageTarget) {
998             this._transitioningPageTarget = false;
999             if (this._autoCaptureOnPageLoad)
1000                 this._legacyAttemptStartAutoCapturingForFrame(frame);
1001             return;
1002         }
1003
1004         if (this._attemptAutoCapturingForFrame(frame))
1005             return;
1006
1007         if (!this.isCapturing())
1008             return;
1009
1010         let mainResource = frame.mainResource;
1011         if (mainResource === this._mainResourceForAutoCapturing)
1012             return;
1013
1014         this._addRecord(new WI.ResourceTimelineRecord(mainResource));
1015     }
1016
1017     _resourceWasAdded(event)
1018     {
1019         // Ignore resource events when there isn't a main frame yet. Those events are triggered by
1020         // loading the cached resources when the inspector opens, and they do not have timing information.
1021         if (!WI.networkManager.mainFrame)
1022             return;
1023
1024         this._addRecord(new WI.ResourceTimelineRecord(event.data.resource));
1025     }
1026
1027     _garbageCollected(event)
1028     {
1029         let {collection} = event.data;
1030         this._addRecord(new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.GarbageCollected, collection.startTime, collection.endTime, null, null, collection));
1031     }
1032
1033     _memoryPressure(event)
1034     {
1035         this._activeRecording.addMemoryPressureEvent(event.data.memoryPressureEvent);
1036     }
1037
1038     _handleTimelinesAutoStopSettingChanged(event)
1039     {
1040         if (WI.settings.timelinesAutoStop.value) {
1041             if (this._mainResourceForAutoCapturing && !isNaN(this._mainResourceForAutoCapturing.parentFrame.loadEventTimestamp))
1042                 this._stopAutoRecordingSoon();
1043             else
1044                 this._resetAutoRecordingMaxTimeTimeout();
1045             this._resetAutoRecordingDeadTimeTimeout();
1046         } else
1047             this.relaxAutoStop();
1048     }
1049
1050     _scriptProfilerTypeToScriptTimelineRecordType(type)
1051     {
1052         switch (type) {
1053         case ScriptProfilerAgent.EventType.API:
1054             return WI.ScriptTimelineRecord.EventType.APIScriptEvaluated;
1055         case ScriptProfilerAgent.EventType.Microtask:
1056             return WI.ScriptTimelineRecord.EventType.MicrotaskDispatched;
1057         case ScriptProfilerAgent.EventType.Other:
1058             return WI.ScriptTimelineRecord.EventType.ScriptEvaluated;
1059         }
1060     }
1061
1062     scriptProfilerTrackingStarted(timestamp)
1063     {
1064         this._scriptProfilerRecords = [];
1065
1066         this.capturingStarted(timestamp);
1067     }
1068
1069     scriptProfilerTrackingUpdated(event)
1070     {
1071         let {startTime, endTime, type} = event;
1072         let scriptRecordType = this._scriptProfilerTypeToScriptTimelineRecordType(type);
1073         let record = new WI.ScriptTimelineRecord(scriptRecordType, startTime, endTime, null, null, null, null);
1074         record.__scriptProfilerType = type;
1075         this._scriptProfilerRecords.push(record);
1076
1077         // "Other" events, generated by Web content, will have wrapping Timeline records
1078         // and need to be merged. Non-Other events, generated purely by the JavaScript
1079         // engine or outside of the page via APIs, will not have wrapping Timeline
1080         // records, so these records can just be added right now.
1081         if (type !== ScriptProfilerAgent.EventType.Other)
1082             this._addRecord(record);
1083     }
1084
1085     scriptProfilerTrackingCompleted(timestamp, samples)
1086     {
1087         console.assert(!this._webTimelineScriptRecordsExpectingScriptProfilerEvents || this._scriptProfilerRecords.length >= this._webTimelineScriptRecordsExpectingScriptProfilerEvents.length);
1088
1089         if (samples) {
1090             let {stackTraces} = samples;
1091             let topDownCallingContextTree = this._activeRecording.topDownCallingContextTree;
1092
1093             // Calculate a per-sample duration.
1094             let timestampIndex = 0;
1095             let timestampCount = stackTraces.length;
1096             let sampleDurations = new Array(timestampCount);
1097             let sampleDurationIndex = 0;
1098             const defaultDuration = 1 / 1000; // 1ms.
1099             for (let i = 0; i < this._scriptProfilerRecords.length; ++i) {
1100                 let record = this._scriptProfilerRecords[i];
1101
1102                 // Use a default duration for timestamps recorded outside of ScriptProfiler events.
1103                 while (timestampIndex < timestampCount && stackTraces[timestampIndex].timestamp < record.startTime) {
1104                     sampleDurations[sampleDurationIndex++] = defaultDuration;
1105                     timestampIndex++;
1106                 }
1107
1108                 // Average the duration per sample across all samples during the record.
1109                 let samplesInRecord = 0;
1110                 while (timestampIndex < timestampCount && stackTraces[timestampIndex].timestamp < record.endTime) {
1111                     timestampIndex++;
1112                     samplesInRecord++;
1113                 }
1114                 if (samplesInRecord) {
1115                     let averageDuration = (record.endTime - record.startTime) / samplesInRecord;
1116                     sampleDurations.fill(averageDuration, sampleDurationIndex, sampleDurationIndex + samplesInRecord);
1117                     sampleDurationIndex += samplesInRecord;
1118                 }
1119             }
1120
1121             // Use a default duration for timestamps recorded outside of ScriptProfiler events.
1122             if (timestampIndex < timestampCount)
1123                 sampleDurations.fill(defaultDuration, sampleDurationIndex);
1124
1125             this._activeRecording.initializeCallingContextTrees(stackTraces, sampleDurations);
1126
1127             // FIXME: This transformation should not be needed after introducing ProfileView.
1128             // Once we eliminate ProfileNodeTreeElements and ProfileNodeDataGridNodes.
1129             // <https://webkit.org/b/154973> Web Inspector: Timelines UI redesign: Remove TimelineSidebarPanel
1130             for (let i = 0; i < this._scriptProfilerRecords.length; ++i) {
1131                 let record = this._scriptProfilerRecords[i];
1132                 record.profilePayload = topDownCallingContextTree.toCPUProfilePayload(record.startTime, record.endTime);
1133             }
1134         }
1135
1136         // Associate the ScriptProfiler created records with Web Timeline records.
1137         // Filter out the already added ScriptProfiler events which should not have been wrapped.
1138         if (WI.sharedApp.debuggableType !== WI.DebuggableType.JavaScript) {
1139             this._scriptProfilerRecords = this._scriptProfilerRecords.filter((x) => x.__scriptProfilerType === ScriptProfilerAgent.EventType.Other);
1140             this._mergeScriptProfileRecords();
1141         }
1142
1143         this._scriptProfilerRecords = null;
1144
1145         let timeline = this._activeRecording.timelineForRecordType(WI.TimelineRecord.Type.Script);
1146         timeline.refresh();
1147
1148         this.capturingStopped(timestamp);
1149     }
1150
1151     _mergeScriptProfileRecords()
1152     {
1153         let nextRecord = function(list) { return list.shift() || null; };
1154         let nextWebTimelineRecord = nextRecord.bind(null, this._webTimelineScriptRecordsExpectingScriptProfilerEvents);
1155         let nextScriptProfilerRecord = nextRecord.bind(null, this._scriptProfilerRecords);
1156         let recordEnclosesRecord = function(record1, record2) {
1157             return record1.startTime <= record2.startTime && record1.endTime >= record2.endTime;
1158         };
1159
1160         let webRecord = nextWebTimelineRecord();
1161         let profilerRecord = nextScriptProfilerRecord();
1162
1163         while (webRecord && profilerRecord) {
1164             // Skip web records with parent web records. For example an EvaluateScript with an EvaluateScript parent.
1165             if (webRecord.parent instanceof WI.ScriptTimelineRecord) {
1166                 console.assert(recordEnclosesRecord(webRecord.parent, webRecord), "Timeline Record incorrectly wrapping another Timeline Record");
1167                 webRecord = nextWebTimelineRecord();
1168                 continue;
1169             }
1170
1171             // Normal case of a Web record wrapping a Script record.
1172             if (recordEnclosesRecord(webRecord, profilerRecord)) {
1173                 webRecord.profilePayload = profilerRecord.profilePayload;
1174                 profilerRecord = nextScriptProfilerRecord();
1175
1176                 // If there are more script profile records in the same time interval, add them
1177                 // as individual script evaluated records with profiles. This can happen with
1178                 // web microtask checkpoints that are technically inside of other web records.
1179                 // FIXME: <https://webkit.org/b/152903> Web Inspector: Timeline Cleanup: Better Timeline Record for Microtask Checkpoints
1180                 while (profilerRecord && recordEnclosesRecord(webRecord, profilerRecord)) {
1181                     this._addRecord(profilerRecord);
1182                     profilerRecord = nextScriptProfilerRecord();
1183                 }
1184
1185                 webRecord = nextWebTimelineRecord();
1186                 continue;
1187             }
1188
1189             // Profiler Record is entirely after the Web Record. This would mean an empty web record.
1190             if (profilerRecord.startTime > webRecord.endTime) {
1191                 console.warn("Unexpected case of a Timeline record not containing a ScriptProfiler event and profile data");
1192                 webRecord = nextWebTimelineRecord();
1193                 continue;
1194             }
1195
1196             // Non-wrapped profiler record.
1197             console.warn("Unexpected case of a ScriptProfiler event not being contained by a Timeline record");
1198             this._addRecord(profilerRecord);
1199             profilerRecord = nextScriptProfilerRecord();
1200         }
1201
1202         // Skipping the remaining ScriptProfiler events to match the current UI for handling Timeline records.
1203         // However, the remaining ScriptProfiler records are valid and could be shown.
1204         // FIXME: <https://webkit.org/b/152904> Web Inspector: Timeline UI should keep up with processing all incoming records
1205     }
1206
1207     _updateAutoCaptureInstruments(targets)
1208     {
1209         let enabledTimelineTypes = this.enabledTimelineTypes;
1210
1211         for (let target of targets) {
1212             if (!target.TimelineAgent)
1213                 continue;
1214             if (!target.TimelineAgent.setInstruments)
1215                 continue;
1216
1217             let instrumentSet = new Set;
1218             for (let timelineType of enabledTimelineTypes) {
1219                 switch (timelineType) {
1220                 case WI.TimelineRecord.Type.Script:
1221                     instrumentSet.add(target.TimelineAgent.Instrument.ScriptProfiler);
1222                     break;
1223                 case WI.TimelineRecord.Type.HeapAllocations:
1224                     instrumentSet.add(target.TimelineAgent.Instrument.Heap);
1225                     break;
1226                 case WI.TimelineRecord.Type.Network:
1227                 case WI.TimelineRecord.Type.RenderingFrame:
1228                 case WI.TimelineRecord.Type.Layout:
1229                 case WI.TimelineRecord.Type.Media:
1230                     instrumentSet.add(target.TimelineAgent.Instrument.Timeline);
1231                     break;
1232                 case WI.TimelineRecord.Type.CPU:
1233                     instrumentSet.add(target.TimelineAgent.Instrument.CPU);
1234                     break;
1235                 case WI.TimelineRecord.Type.Memory:
1236                     instrumentSet.add(target.TimelineAgent.Instrument.Memory);
1237                     break;
1238                 }
1239             }
1240
1241             target.TimelineAgent.setInstruments(Array.from(instrumentSet));
1242         }
1243     }
1244
1245     _handleDOMNodeDidFireEvent(event)
1246     {
1247         let {domEvent} = event.data;
1248
1249         this._addRecord(new WI.MediaTimelineRecord(WI.MediaTimelineRecord.EventType.DOMEvent, domEvent.timestamp, {
1250             domNode: event.target,
1251             domEvent,
1252         }));
1253     }
1254
1255     _handleDOMNodePowerEfficientPlaybackStateChanged(event)
1256     {
1257         let {timestamp, isPowerEfficient} = event.data;
1258
1259         this._addRecord(new WI.MediaTimelineRecord(WI.MediaTimelineRecord.EventType.PowerEfficientPlaybackStateChanged, timestamp, {
1260             domNode: event.target,
1261             isPowerEfficient,
1262         }));
1263     }
1264 };
1265
1266 WI.TimelineManager.CapturingState = {
1267     Inactive: "inactive",
1268     Starting: "starting",
1269     Active: "active",
1270     Stopping: "stopping",
1271 };
1272
1273 WI.TimelineManager.Event = {
1274     CapturingStateChanged: "timeline-manager-capturing-started",
1275     RecordingCreated: "timeline-manager-recording-created",
1276     RecordingLoaded: "timeline-manager-recording-loaded",
1277     RecordingImported: "timeline-manager-recording-imported",
1278 };
1279
1280 WI.TimelineManager.MaximumAutoRecordDuration = 90000; // 90 seconds
1281 WI.TimelineManager.MaximumAutoRecordDurationAfterLoadEvent = 10000; // 10 seconds
1282 WI.TimelineManager.DeadTimeRequiredToStopAutoRecordingEarly = 2000; // 2 seconds