Web Inspector: drag/drop over the sidebar should load an imported file in Canvas...
[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         WI.settings.timelinesAutoStop.addEventListener(WI.Setting.Event.Changed, this._handleTimelinesAutoStopSettingChanged, this);
359
360         WI.Frame.addEventListener(WI.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this);
361         WI.Target.addEventListener(WI.Target.Event.ResourceAdded, this._resourceWasAdded, this);
362
363         WI.heapManager.addEventListener(WI.HeapManager.Event.GarbageCollected, this._garbageCollected, this);
364
365         WI.memoryManager.addEventListener(WI.MemoryManager.Event.MemoryPressure, this._memoryPressure, this);
366
367         WI.DOMNode.addEventListener(WI.DOMNode.Event.DidFireEvent, this._handleDOMNodeDidFireEvent, this);
368         WI.DOMNode.addEventListener(WI.DOMNode.Event.LowPowerChanged, this._handleDOMNodeLowPowerChanged, this);
369
370         this._updateCapturingState(TimelineManager.CapturingState.Active, {startTime: this._capturingStartTime});
371     }
372
373     capturingStopped(endTime)
374     {
375         // Called from WI.TimelineObserver.
376
377         // The frontend didn't stop capturing, so this was a programmatic stop.
378         if (this._capturingState === TimelineManager.CapturingState.Active) {
379             this._initiatedByBackendStop = true;
380             this.stopCapturing();
381         }
382
383         if (!isNaN(endTime)) {
384             if (isNaN(this._capturingEndTime) || endTime > this._capturingEndTime)
385                 this._capturingEndTime = endTime;
386         }
387
388         this._capturingInstrumentCount--;
389         console.assert(this._capturingInstrumentCount >= 0);
390         if (this._capturingInstrumentCount)
391             return;
392
393         if (this._capturingState === TimelineManager.CapturingState.Inactive)
394             return;
395
396         WI.DOMNode.removeEventListener(null, null, this);
397         WI.memoryManager.removeEventListener(null, null, this);
398         WI.heapManager.removeEventListener(null, null, this);
399         WI.Target.removeEventListener(WI.Target.Event.ResourceAdded, this._resourceWasAdded, this);
400         WI.Frame.removeEventListener(WI.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this);
401         WI.settings.timelinesAutoStop.removeEventListener(null, null, this);
402
403         this.relaxAutoStop();
404
405         this._isCapturingPageReload = false;
406         this._shouldSetAutoCapturingMainResource = false;
407         this._mainResourceForAutoCapturing = null;
408         this._initiatedByBackendStart = false;
409         this._initiatedByBackendStop = false;
410
411         this._updateCapturingState(TimelineManager.CapturingState.Inactive, {endTime: this._capturingEndTime});
412     }
413
414     autoCaptureStarted()
415     {
416         // Called from WI.TimelineObserver.
417
418         let waitingForCapturingStartedEvent = this._capturingState === TimelineManager.CapturingState.Starting;
419
420         if (this.isCapturing())
421             this.stopCapturing();
422
423         this._initiatedByBackendStart = true;
424
425         // We may already have an fresh TimelineRecording created if autoCaptureStarted is received
426         // between sending the Timeline.start command and receiving Timeline.capturingStarted event.
427         // In that case, there is no need to call startCapturing again. Reuse the fresh recording.
428         if (!waitingForCapturingStartedEvent) {
429             const createNewRecording = true;
430             this.startCapturing(createNewRecording);
431         }
432
433         this._shouldSetAutoCapturingMainResource = true;
434     }
435
436     eventRecorded(recordPayload)
437     {
438         // Called from WI.TimelineObserver.
439
440         console.assert(this.isCapturing());
441         if (!this.isCapturing())
442             return;
443
444         var records = [];
445
446         // Iterate over the records tree using a stack. Doing this recursively has
447         // been known to cause a call stack overflow. https://webkit.org/b/79106
448         var stack = [{array: [recordPayload], parent: null, parentRecord: null, index: 0}];
449         while (stack.length) {
450             var entry = stack.lastValue;
451             var recordPayloads = entry.array;
452
453             if (entry.index < recordPayloads.length) {
454                 var recordPayload = recordPayloads[entry.index];
455                 var record = this._processEvent(recordPayload, entry.parent);
456                 if (record) {
457                     record.parent = entry.parentRecord;
458                     records.push(record);
459                     if (entry.parentRecord)
460                         entry.parentRecord.children.push(record);
461                 }
462
463                 if (recordPayload.children && recordPayload.children.length)
464                     stack.push({array: recordPayload.children, parent: recordPayload, parentRecord: record || entry.parentRecord, index: 0});
465                 ++entry.index;
466             } else
467                 stack.pop();
468         }
469
470         for (var record of records) {
471             if (record.type === WI.TimelineRecord.Type.RenderingFrame) {
472                 if (!record.children.length)
473                     continue;
474                 record.setupFrameIndex();
475             }
476
477             this._addRecord(record);
478         }
479     }
480
481     pageDOMContentLoadedEventFired(timestamp)
482     {
483         // Called from WI.PageObserver.
484
485         console.assert(this._activeRecording);
486         console.assert(isNaN(WI.networkManager.mainFrame.domContentReadyEventTimestamp));
487
488         let computedTimestamp = this._activeRecording.computeElapsedTime(timestamp);
489
490         WI.networkManager.mainFrame.markDOMContentReadyEvent(computedTimestamp);
491
492         let eventMarker = new WI.TimelineMarker(computedTimestamp, WI.TimelineMarker.Type.DOMContentEvent);
493         this._activeRecording.addEventMarker(eventMarker);
494     }
495
496     pageLoadEventFired(timestamp)
497     {
498         // Called from WI.PageObserver.
499
500         console.assert(this._activeRecording);
501         console.assert(isNaN(WI.networkManager.mainFrame.loadEventTimestamp));
502
503         let computedTimestamp = this._activeRecording.computeElapsedTime(timestamp);
504
505         WI.networkManager.mainFrame.markLoadEvent(computedTimestamp);
506
507         let eventMarker = new WI.TimelineMarker(computedTimestamp, WI.TimelineMarker.Type.LoadEvent);
508         this._activeRecording.addEventMarker(eventMarker);
509
510         this._stopAutoRecordingSoon();
511     }
512
513     cpuProfilerTrackingStarted(timestamp)
514     {
515         // Called from WI.CPUProfilerObserver.
516
517         this.capturingStarted(timestamp);
518     }
519
520     cpuProfilerTrackingUpdated(event)
521     {
522         // Called from WI.CPUProfilerObserver.
523
524         console.assert(this.isCapturing());
525         if (!this.isCapturing())
526             return;
527
528         this._addRecord(new WI.CPUTimelineRecord(event));
529     }
530
531     cpuProfilerTrackingCompleted(timestamp)
532     {
533         // Called from WI.CPUProfilerObserver.
534
535         this.capturingStopped(timestamp);
536     }
537
538     memoryTrackingStarted(timestamp)
539     {
540         // Called from WI.MemoryObserver.
541
542         this.capturingStarted(timestamp);
543     }
544
545     memoryTrackingUpdated(event)
546     {
547         // Called from WI.MemoryObserver.
548
549         console.assert(this.isCapturing());
550         if (!this.isCapturing())
551             return;
552
553         this._addRecord(new WI.MemoryTimelineRecord(event.timestamp, event.categories));
554     }
555
556     memoryTrackingCompleted(timestamp)
557     {
558         // Called from WI.MemoryObserver.
559
560         this.capturingStopped(timestamp);
561     }
562
563     heapTrackingStarted(timestamp, snapshot)
564     {
565         // Called from WI.HeapObserver.
566
567         this.capturingStarted(timestamp);
568
569         this._addRecord(new WI.HeapAllocationsTimelineRecord(timestamp, snapshot));
570     }
571
572     heapTrackingCompleted(timestamp, snapshot)
573     {
574         // Called from WI.HeapObserver.
575
576         this._addRecord(new WI.HeapAllocationsTimelineRecord(timestamp, snapshot));
577
578         this.capturingStopped();
579     }
580
581     heapSnapshotAdded(timestamp, snapshot)
582     {
583         // Called from WI.HeapAllocationsInstrument.
584
585         this._addRecord(new WI.HeapAllocationsTimelineRecord(timestamp, snapshot));
586     }
587
588     // Private
589
590     _updateCapturingState(state, data = {})
591     {
592         if (this._capturingState === state)
593             return;
594
595         this._capturingState = state;
596
597         this.dispatchEventToListeners(TimelineManager.Event.CapturingStateChanged, data);
598     }
599
600     _processRecord(recordPayload, parentRecordPayload)
601     {
602         console.assert(this.isCapturing());
603
604         var startTime = this._activeRecording.computeElapsedTime(recordPayload.startTime);
605         var endTime = this._activeRecording.computeElapsedTime(recordPayload.endTime);
606         var callFrames = this._callFramesFromPayload(recordPayload.stackTrace);
607
608         var significantCallFrame = null;
609         if (callFrames) {
610             for (var i = 0; i < callFrames.length; ++i) {
611                 if (callFrames[i].nativeCode)
612                     continue;
613                 significantCallFrame = callFrames[i];
614                 break;
615             }
616         }
617
618         var sourceCodeLocation = significantCallFrame && significantCallFrame.sourceCodeLocation;
619
620         switch (recordPayload.type) {
621         case TimelineAgent.EventType.ScheduleStyleRecalculation:
622             console.assert(isNaN(endTime));
623
624             // Pass the startTime as the endTime since this record type has no duration.
625             return new WI.LayoutTimelineRecord(WI.LayoutTimelineRecord.EventType.InvalidateStyles, startTime, startTime, callFrames, sourceCodeLocation);
626
627         case TimelineAgent.EventType.RecalculateStyles:
628             return new WI.LayoutTimelineRecord(WI.LayoutTimelineRecord.EventType.RecalculateStyles, startTime, endTime, callFrames, sourceCodeLocation);
629
630         case TimelineAgent.EventType.InvalidateLayout:
631             console.assert(isNaN(endTime));
632
633             // Pass the startTime as the endTime since this record type has no duration.
634             return new WI.LayoutTimelineRecord(WI.LayoutTimelineRecord.EventType.InvalidateLayout, startTime, startTime, callFrames, sourceCodeLocation);
635
636         case TimelineAgent.EventType.Layout:
637             var layoutRecordType = sourceCodeLocation ? WI.LayoutTimelineRecord.EventType.ForcedLayout : WI.LayoutTimelineRecord.EventType.Layout;
638             var quad = new WI.Quad(recordPayload.data.root);
639             return new WI.LayoutTimelineRecord(layoutRecordType, startTime, endTime, callFrames, sourceCodeLocation, quad);
640
641         case TimelineAgent.EventType.Paint:
642             var quad = new WI.Quad(recordPayload.data.clip);
643             return new WI.LayoutTimelineRecord(WI.LayoutTimelineRecord.EventType.Paint, startTime, endTime, callFrames, sourceCodeLocation, quad);
644
645         case TimelineAgent.EventType.Composite:
646             return new WI.LayoutTimelineRecord(WI.LayoutTimelineRecord.EventType.Composite, startTime, endTime, callFrames, sourceCodeLocation);
647
648         case TimelineAgent.EventType.RenderingFrame:
649             if (!recordPayload.children || !recordPayload.children.length)
650                 return null;
651
652             return new WI.RenderingFrameTimelineRecord(startTime, endTime);
653
654         case TimelineAgent.EventType.EvaluateScript:
655             if (!sourceCodeLocation) {
656                 var mainFrame = WI.networkManager.mainFrame;
657                 var scriptResource = mainFrame.url === recordPayload.data.url ? mainFrame.mainResource : mainFrame.resourceForURL(recordPayload.data.url, true);
658                 if (scriptResource) {
659                     // The lineNumber is 1-based, but we expect 0-based.
660                     let lineNumber = recordPayload.data.lineNumber - 1;
661                     let columnNumber = "columnNumber" in recordPayload.data ? recordPayload.data.columnNumber - 1 : 0;
662                     sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, columnNumber);
663                 }
664             }
665
666             var profileData = recordPayload.data.profile;
667
668             var record;
669             switch (parentRecordPayload && parentRecordPayload.type) {
670             case TimelineAgent.EventType.TimerFire:
671                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profileData);
672                 break;
673             case TimelineAgent.EventType.ObserverCallback:
674                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ObserverCallback, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.type, profileData);
675                 break;
676             case TimelineAgent.EventType.FireAnimationFrame:
677                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.AnimationFrameFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
678                 break;
679             default:
680                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, null, profileData);
681                 break;
682             }
683
684             this._webTimelineScriptRecordsExpectingScriptProfilerEvents.push(record);
685             return record;
686
687         case TimelineAgent.EventType.ConsoleProfile:
688             var profileData = recordPayload.data.profile;
689             // COMPATIBILITY (iOS 9): With the Sampling Profiler, profiles no longer include legacy profile data.
690             console.assert(profileData || TimelineAgent.setInstruments);
691             return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ConsoleProfileRecorded, startTime, endTime, callFrames, sourceCodeLocation, recordPayload.data.title, profileData);
692
693         case TimelineAgent.EventType.TimerFire:
694         case TimelineAgent.EventType.EventDispatch:
695         case TimelineAgent.EventType.FireAnimationFrame:
696         case TimelineAgent.EventType.ObserverCallback:
697             // These are handled when we see the child FunctionCall or EvaluateScript.
698             break;
699
700         case TimelineAgent.EventType.FunctionCall:
701             // FunctionCall always happens as a child of another record, and since the FunctionCall record
702             // has useful info we just make the timeline record here (combining the data from both records).
703             if (!parentRecordPayload) {
704                 console.warn("Unexpectedly received a FunctionCall timeline record without a parent record");
705                 break;
706             }
707
708             if (!sourceCodeLocation) {
709                 var mainFrame = WI.networkManager.mainFrame;
710                 var scriptResource = mainFrame.url === recordPayload.data.scriptName ? mainFrame.mainResource : mainFrame.resourceForURL(recordPayload.data.scriptName, true);
711                 if (scriptResource) {
712                     // The lineNumber is 1-based, but we expect 0-based.
713                     let lineNumber = recordPayload.data.scriptLine - 1;
714                     let columnNumber = "scriptColumn" in recordPayload.data ? recordPayload.data.scriptColumn - 1 : 0;
715                     sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, columnNumber);
716                 }
717             }
718
719             var profileData = recordPayload.data.profile;
720
721             var record;
722             switch (parentRecordPayload.type) {
723             case TimelineAgent.EventType.TimerFire:
724                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profileData);
725                 break;
726             case TimelineAgent.EventType.EventDispatch:
727                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.type, profileData, parentRecordPayload.data);
728                 break;
729             case TimelineAgent.EventType.ObserverCallback:
730                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ObserverCallback, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.type, profileData);
731                 break;
732             case TimelineAgent.EventType.FireAnimationFrame:
733                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.AnimationFrameFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
734                 break;
735             case TimelineAgent.EventType.FunctionCall:
736                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
737                 break;
738             case TimelineAgent.EventType.RenderingFrame:
739                 record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
740                 break;
741
742             default:
743                 console.assert(false, "Missed FunctionCall embedded inside of: " + parentRecordPayload.type);
744                 break;
745             }
746
747             if (record) {
748                 this._webTimelineScriptRecordsExpectingScriptProfilerEvents.push(record);
749                 return record;
750             }
751             break;
752
753         case TimelineAgent.EventType.ProbeSample:
754             // Pass the startTime as the endTime since this record type has no duration.
755             sourceCodeLocation = WI.debuggerManager.probeForIdentifier(recordPayload.data.probeId).breakpoint.sourceCodeLocation;
756             return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ProbeSampleRecorded, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.probeId);
757
758         case TimelineAgent.EventType.TimerInstall:
759             console.assert(isNaN(endTime));
760
761             // Pass the startTime as the endTime since this record type has no duration.
762             var timerDetails = {timerId: recordPayload.data.timerId, timeout: recordPayload.data.timeout, repeating: !recordPayload.data.singleShot};
763             return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.TimerInstalled, startTime, startTime, callFrames, sourceCodeLocation, timerDetails);
764
765         case TimelineAgent.EventType.TimerRemove:
766             console.assert(isNaN(endTime));
767
768             // Pass the startTime as the endTime since this record type has no duration.
769             return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.TimerRemoved, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId);
770
771         case TimelineAgent.EventType.RequestAnimationFrame:
772             console.assert(isNaN(endTime));
773
774             // Pass the startTime as the endTime since this record type has no duration.
775             return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.AnimationFrameRequested, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.id);
776
777         case TimelineAgent.EventType.CancelAnimationFrame:
778             console.assert(isNaN(endTime));
779
780             // Pass the startTime as the endTime since this record type has no duration.
781             return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.AnimationFrameCanceled, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.id);
782
783         default:
784             console.error("Missing handling of Timeline Event Type: " + recordPayload.type);
785         }
786
787         return null;
788     }
789
790     _processEvent(recordPayload, parentRecordPayload)
791     {
792         console.assert(this.isCapturing());
793
794         switch (recordPayload.type) {
795         case TimelineAgent.EventType.TimeStamp:
796             var timestamp = this._activeRecording.computeElapsedTime(recordPayload.startTime);
797             var eventMarker = new WI.TimelineMarker(timestamp, WI.TimelineMarker.Type.TimeStamp, recordPayload.data.message);
798             this._activeRecording.addEventMarker(eventMarker);
799             break;
800
801         case TimelineAgent.EventType.Time:
802         case TimelineAgent.EventType.TimeEnd:
803             // FIXME: <https://webkit.org/b/150690> Web Inspector: Show console.time/timeEnd ranges in Timeline
804             // FIXME: Make use of "message" payload properties.
805             break;
806
807         default:
808             return this._processRecord(recordPayload, parentRecordPayload);
809         }
810
811         return null;
812     }
813
814     _loadNewRecording()
815     {
816         if (this._activeRecording && this._activeRecording.isEmpty())
817             return;
818
819         let instruments = this.enabledTimelineTypes.map((type) => WI.Instrument.createForTimelineType(type));
820         let identifier = this._nextRecordingIdentifier++;
821         let newRecording = new WI.TimelineRecording(identifier, WI.UIString("Timeline Recording %d").format(identifier), instruments);
822
823         this._recordings.push(newRecording);
824         this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingCreated, {recording: newRecording});
825
826         if (this.isCapturing())
827             this.stopCapturing();
828
829         var oldRecording = this._activeRecording;
830         if (oldRecording)
831             oldRecording.unloaded();
832
833         this._activeRecording = newRecording;
834
835         // COMPATIBILITY (iOS 8): When using Legacy timestamps, a navigation will have computed
836         // the main resource's will send request timestamp in terms of the last page's base timestamp.
837         // Now that we have navigated, we should reset the legacy base timestamp and the
838         // will send request timestamp for the new main resource. This way, all new timeline
839         // records will be computed relative to the new navigation.
840         if (this._mainResourceForAutoCapturing && WI.TimelineRecording.isLegacy) {
841             console.assert(this._mainResourceForAutoCapturing.originalRequestWillBeSentTimestamp);
842             this._activeRecording.setLegacyBaseTimestamp(this._mainResourceForAutoCapturing.originalRequestWillBeSentTimestamp);
843             this._mainResourceForAutoCapturing._requestSentTimestamp = 0;
844         }
845
846         this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingLoaded, {oldRecording});
847     }
848
849     _callFramesFromPayload(payload)
850     {
851         if (!payload)
852             return null;
853
854         return payload.map((x) => WI.CallFrame.fromPayload(WI.assumingMainTarget(), x));
855     }
856
857     _addRecord(record)
858     {
859         this._activeRecording.addRecord(record);
860
861         // Only worry about dead time after the load event.
862         if (WI.networkManager.mainFrame && isNaN(WI.networkManager.mainFrame.loadEventTimestamp))
863             this._resetAutoRecordingDeadTimeTimeout();
864     }
865
866     _attemptAutoCapturingForFrame(frame)
867     {
868         if (!this._autoCaptureOnPageLoad)
869             return false;
870
871         if (!frame.isMainFrame())
872             return false;
873
874         // COMPATIBILITY (iOS 9): Timeline.setAutoCaptureEnabled did not exist.
875         // Perform auto capture in the frontend.
876         if (!InspectorBackend.domains.Timeline)
877             return false;
878         if (!InspectorBackend.domains.Timeline.setAutoCaptureEnabled)
879             return this._legacyAttemptStartAutoCapturingForFrame(frame);
880
881         if (!this._shouldSetAutoCapturingMainResource)
882             return false;
883
884         console.assert(this.isCapturing(), "We saw autoCaptureStarted so we should already be capturing");
885
886         let mainResource = frame.provisionalMainResource || frame.mainResource;
887         if (mainResource === this._mainResourceForAutoCapturing)
888             return false;
889
890         let oldMainResource = frame.mainResource || null;
891         this._isCapturingPageReload = oldMainResource !== null && oldMainResource.url === mainResource.url;
892
893         this._mainResourceForAutoCapturing = mainResource;
894
895         this._addRecord(new WI.ResourceTimelineRecord(mainResource));
896
897         this._resetAutoRecordingMaxTimeTimeout();
898
899         this._shouldSetAutoCapturingMainResource = false;
900
901         return true;
902     }
903
904     _legacyAttemptStartAutoCapturingForFrame(frame)
905     {
906         if (this.isCapturing() && !this._mainResourceForAutoCapturing)
907             return false;
908
909         let mainResource = frame.provisionalMainResource || frame.mainResource;
910         if (mainResource === this._mainResourceForAutoCapturing)
911             return false;
912
913         let oldMainResource = frame.mainResource || null;
914         this._isCapturingPageReload = oldMainResource !== null && oldMainResource.url === mainResource.url;
915
916         if (this.isCapturing())
917             this.stopCapturing();
918
919         this._mainResourceForAutoCapturing = mainResource;
920
921         this._loadNewRecording();
922
923         this.startCapturing();
924
925         this._addRecord(new WI.ResourceTimelineRecord(mainResource));
926
927         this._resetAutoRecordingMaxTimeTimeout();
928
929         return true;
930     }
931
932     _stopAutoRecordingSoon()
933     {
934         if (!WI.settings.timelinesAutoStop.value)
935             return;
936
937         // Only auto stop when auto capturing.
938         if (!this.isCapturing() || !this._mainResourceForAutoCapturing)
939             return;
940
941         if (this._stopCapturingTimeout)
942             clearTimeout(this._stopCapturingTimeout);
943         this._stopCapturingTimeout = setTimeout(this._boundStopCapturing, WI.TimelineManager.MaximumAutoRecordDurationAfterLoadEvent);
944     }
945
946     _resetAutoRecordingMaxTimeTimeout()
947     {
948         if (!WI.settings.timelinesAutoStop.value)
949             return;
950
951         if (this._stopCapturingTimeout)
952             clearTimeout(this._stopCapturingTimeout);
953         this._stopCapturingTimeout = setTimeout(this._boundStopCapturing, WI.TimelineManager.MaximumAutoRecordDuration);
954     }
955
956     _resetAutoRecordingDeadTimeTimeout()
957     {
958         if (!WI.settings.timelinesAutoStop.value)
959             return;
960
961         // Only monitor dead time when auto capturing.
962         if (!this.isCapturing() || !this._mainResourceForAutoCapturing)
963             return;
964
965         // Avoid unnecessary churning of timeout identifier by not tickling until 10ms have passed.
966         let now = Date.now();
967         if (now <= this._lastDeadTimeTickle)
968             return;
969         this._lastDeadTimeTickle = now + 10;
970
971         if (this._deadTimeTimeout)
972             clearTimeout(this._deadTimeTimeout);
973         this._deadTimeTimeout = setTimeout(this._boundStopCapturing, WI.TimelineManager.DeadTimeRequiredToStopAutoRecordingEarly);
974     }
975
976     _provisionalLoadStarted(event)
977     {
978         this._attemptAutoCapturingForFrame(event.target);
979     }
980
981     _mainResourceDidChange(event)
982     {
983         // Ignore resource events when there isn't a main frame yet. Those events are triggered by
984         // loading the cached resources when the inspector opens, and they do not have timing information.
985         if (!WI.networkManager.mainFrame)
986             return;
987
988         let frame = event.target;
989
990         // When performing a page transition start a recording once the main resource changes.
991         // We start a legacy capture because the backend wasn't available to automatically
992         // initiate the capture, so the frontend must start the capture.
993         if (this._transitioningPageTarget) {
994             this._transitioningPageTarget = false;
995             if (this._autoCaptureOnPageLoad)
996                 this._legacyAttemptStartAutoCapturingForFrame(frame);
997             return;
998         }
999
1000         if (this._attemptAutoCapturingForFrame(frame))
1001             return;
1002
1003         if (!this.isCapturing())
1004             return;
1005
1006         let mainResource = frame.mainResource;
1007         if (mainResource === this._mainResourceForAutoCapturing)
1008             return;
1009
1010         this._addRecord(new WI.ResourceTimelineRecord(mainResource));
1011     }
1012
1013     _resourceWasAdded(event)
1014     {
1015         // Ignore resource events when there isn't a main frame yet. Those events are triggered by
1016         // loading the cached resources when the inspector opens, and they do not have timing information.
1017         if (!WI.networkManager.mainFrame)
1018             return;
1019
1020         this._addRecord(new WI.ResourceTimelineRecord(event.data.resource));
1021     }
1022
1023     _garbageCollected(event)
1024     {
1025         let {collection} = event.data;
1026         this._addRecord(new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.GarbageCollected, collection.startTime, collection.endTime, null, null, collection));
1027     }
1028
1029     _memoryPressure(event)
1030     {
1031         this._activeRecording.addMemoryPressureEvent(event.data.memoryPressureEvent);
1032     }
1033
1034     _handleTimelinesAutoStopSettingChanged(event)
1035     {
1036         if (WI.settings.timelinesAutoStop.value) {
1037             if (this._mainResourceForAutoCapturing && !isNaN(this._mainResourceForAutoCapturing.parentFrame.loadEventTimestamp))
1038                 this._stopAutoRecordingSoon();
1039             else
1040                 this._resetAutoRecordingMaxTimeTimeout();
1041             this._resetAutoRecordingDeadTimeTimeout();
1042         } else
1043             this.relaxAutoStop();
1044     }
1045
1046     _scriptProfilerTypeToScriptTimelineRecordType(type)
1047     {
1048         switch (type) {
1049         case ScriptProfilerAgent.EventType.API:
1050             return WI.ScriptTimelineRecord.EventType.APIScriptEvaluated;
1051         case ScriptProfilerAgent.EventType.Microtask:
1052             return WI.ScriptTimelineRecord.EventType.MicrotaskDispatched;
1053         case ScriptProfilerAgent.EventType.Other:
1054             return WI.ScriptTimelineRecord.EventType.ScriptEvaluated;
1055         }
1056     }
1057
1058     scriptProfilerTrackingStarted(timestamp)
1059     {
1060         this._scriptProfilerRecords = [];
1061
1062         this.capturingStarted(timestamp);
1063     }
1064
1065     scriptProfilerTrackingUpdated(event)
1066     {
1067         let {startTime, endTime, type} = event;
1068         let scriptRecordType = this._scriptProfilerTypeToScriptTimelineRecordType(type);
1069         let record = new WI.ScriptTimelineRecord(scriptRecordType, startTime, endTime, null, null, null, null);
1070         record.__scriptProfilerType = type;
1071         this._scriptProfilerRecords.push(record);
1072
1073         // "Other" events, generated by Web content, will have wrapping Timeline records
1074         // and need to be merged. Non-Other events, generated purely by the JavaScript
1075         // engine or outside of the page via APIs, will not have wrapping Timeline
1076         // records, so these records can just be added right now.
1077         if (type !== ScriptProfilerAgent.EventType.Other)
1078             this._addRecord(record);
1079     }
1080
1081     scriptProfilerTrackingCompleted(timestamp, samples)
1082     {
1083         console.assert(!this._webTimelineScriptRecordsExpectingScriptProfilerEvents || this._scriptProfilerRecords.length >= this._webTimelineScriptRecordsExpectingScriptProfilerEvents.length);
1084
1085         if (samples) {
1086             let {stackTraces} = samples;
1087             let topDownCallingContextTree = this._activeRecording.topDownCallingContextTree;
1088
1089             // Calculate a per-sample duration.
1090             let timestampIndex = 0;
1091             let timestampCount = stackTraces.length;
1092             let sampleDurations = new Array(timestampCount);
1093             let sampleDurationIndex = 0;
1094             const defaultDuration = 1 / 1000; // 1ms.
1095             for (let i = 0; i < this._scriptProfilerRecords.length; ++i) {
1096                 let record = this._scriptProfilerRecords[i];
1097
1098                 // Use a default duration for timestamps recorded outside of ScriptProfiler events.
1099                 while (timestampIndex < timestampCount && stackTraces[timestampIndex].timestamp < record.startTime) {
1100                     sampleDurations[sampleDurationIndex++] = defaultDuration;
1101                     timestampIndex++;
1102                 }
1103
1104                 // Average the duration per sample across all samples during the record.
1105                 let samplesInRecord = 0;
1106                 while (timestampIndex < timestampCount && stackTraces[timestampIndex].timestamp < record.endTime) {
1107                     timestampIndex++;
1108                     samplesInRecord++;
1109                 }
1110                 if (samplesInRecord) {
1111                     let averageDuration = (record.endTime - record.startTime) / samplesInRecord;
1112                     sampleDurations.fill(averageDuration, sampleDurationIndex, sampleDurationIndex + samplesInRecord);
1113                     sampleDurationIndex += samplesInRecord;
1114                 }
1115             }
1116
1117             // Use a default duration for timestamps recorded outside of ScriptProfiler events.
1118             if (timestampIndex < timestampCount)
1119                 sampleDurations.fill(defaultDuration, sampleDurationIndex);
1120
1121             this._activeRecording.initializeCallingContextTrees(stackTraces, sampleDurations);
1122
1123             // FIXME: This transformation should not be needed after introducing ProfileView.
1124             // Once we eliminate ProfileNodeTreeElements and ProfileNodeDataGridNodes.
1125             // <https://webkit.org/b/154973> Web Inspector: Timelines UI redesign: Remove TimelineSidebarPanel
1126             for (let i = 0; i < this._scriptProfilerRecords.length; ++i) {
1127                 let record = this._scriptProfilerRecords[i];
1128                 record.profilePayload = topDownCallingContextTree.toCPUProfilePayload(record.startTime, record.endTime);
1129             }
1130         }
1131
1132         // Associate the ScriptProfiler created records with Web Timeline records.
1133         // Filter out the already added ScriptProfiler events which should not have been wrapped.
1134         if (WI.sharedApp.debuggableType !== WI.DebuggableType.JavaScript) {
1135             this._scriptProfilerRecords = this._scriptProfilerRecords.filter((x) => x.__scriptProfilerType === ScriptProfilerAgent.EventType.Other);
1136             this._mergeScriptProfileRecords();
1137         }
1138
1139         this._scriptProfilerRecords = null;
1140
1141         let timeline = this._activeRecording.timelineForRecordType(WI.TimelineRecord.Type.Script);
1142         timeline.refresh();
1143
1144         this.capturingStopped(timestamp);
1145     }
1146
1147     _mergeScriptProfileRecords()
1148     {
1149         let nextRecord = function(list) { return list.shift() || null; };
1150         let nextWebTimelineRecord = nextRecord.bind(null, this._webTimelineScriptRecordsExpectingScriptProfilerEvents);
1151         let nextScriptProfilerRecord = nextRecord.bind(null, this._scriptProfilerRecords);
1152         let recordEnclosesRecord = function(record1, record2) {
1153             return record1.startTime <= record2.startTime && record1.endTime >= record2.endTime;
1154         };
1155
1156         let webRecord = nextWebTimelineRecord();
1157         let profilerRecord = nextScriptProfilerRecord();
1158
1159         while (webRecord && profilerRecord) {
1160             // Skip web records with parent web records. For example an EvaluateScript with an EvaluateScript parent.
1161             if (webRecord.parent instanceof WI.ScriptTimelineRecord) {
1162                 console.assert(recordEnclosesRecord(webRecord.parent, webRecord), "Timeline Record incorrectly wrapping another Timeline Record");
1163                 webRecord = nextWebTimelineRecord();
1164                 continue;
1165             }
1166
1167             // Normal case of a Web record wrapping a Script record.
1168             if (recordEnclosesRecord(webRecord, profilerRecord)) {
1169                 webRecord.profilePayload = profilerRecord.profilePayload;
1170                 profilerRecord = nextScriptProfilerRecord();
1171
1172                 // If there are more script profile records in the same time interval, add them
1173                 // as individual script evaluated records with profiles. This can happen with
1174                 // web microtask checkpoints that are technically inside of other web records.
1175                 // FIXME: <https://webkit.org/b/152903> Web Inspector: Timeline Cleanup: Better Timeline Record for Microtask Checkpoints
1176                 while (profilerRecord && recordEnclosesRecord(webRecord, profilerRecord)) {
1177                     this._addRecord(profilerRecord);
1178                     profilerRecord = nextScriptProfilerRecord();
1179                 }
1180
1181                 webRecord = nextWebTimelineRecord();
1182                 continue;
1183             }
1184
1185             // Profiler Record is entirely after the Web Record. This would mean an empty web record.
1186             if (profilerRecord.startTime > webRecord.endTime) {
1187                 console.warn("Unexpected case of a Timeline record not containing a ScriptProfiler event and profile data");
1188                 webRecord = nextWebTimelineRecord();
1189                 continue;
1190             }
1191
1192             // Non-wrapped profiler record.
1193             console.warn("Unexpected case of a ScriptProfiler event not being contained by a Timeline record");
1194             this._addRecord(profilerRecord);
1195             profilerRecord = nextScriptProfilerRecord();
1196         }
1197
1198         // Skipping the remaining ScriptProfiler events to match the current UI for handling Timeline records.
1199         // However, the remaining ScriptProfiler records are valid and could be shown.
1200         // FIXME: <https://webkit.org/b/152904> Web Inspector: Timeline UI should keep up with processing all incoming records
1201     }
1202
1203     _updateAutoCaptureInstruments(targets)
1204     {
1205         let enabledTimelineTypes = this._enabledTimelineTypesSetting.value;
1206
1207         for (let target of targets) {
1208             if (!target.TimelineAgent)
1209                 continue;
1210             if (!target.TimelineAgent.setInstruments)
1211                 continue;
1212
1213             let instrumentSet = new Set;
1214             for (let timelineType of enabledTimelineTypes) {
1215                 switch (timelineType) {
1216                 case WI.TimelineRecord.Type.Script:
1217                     instrumentSet.add(target.TimelineAgent.Instrument.ScriptProfiler);
1218                     break;
1219                 case WI.TimelineRecord.Type.HeapAllocations:
1220                     instrumentSet.add(target.TimelineAgent.Instrument.Heap);
1221                     break;
1222                 case WI.TimelineRecord.Type.Network:
1223                 case WI.TimelineRecord.Type.RenderingFrame:
1224                 case WI.TimelineRecord.Type.Layout:
1225                 case WI.TimelineRecord.Type.Media:
1226                     instrumentSet.add(target.TimelineAgent.Instrument.Timeline);
1227                     break;
1228                 case WI.TimelineRecord.Type.CPU:
1229                     instrumentSet.add(target.TimelineAgent.Instrument.CPU);
1230                     break;
1231                 case WI.TimelineRecord.Type.Memory:
1232                     instrumentSet.add(target.TimelineAgent.Instrument.Memory);
1233                     break;
1234                 }
1235             }
1236
1237             target.TimelineAgent.setInstruments(Array.from(instrumentSet));
1238         }
1239     }
1240
1241     _handleDOMNodeDidFireEvent(event)
1242     {
1243         let {domEvent} = event.data;
1244
1245         this._addRecord(new WI.MediaTimelineRecord(WI.MediaTimelineRecord.EventType.DOMEvent, domEvent.timestamp, {
1246             domNode: event.target,
1247             domEvent,
1248         }));
1249     }
1250
1251     _handleDOMNodeLowPowerChanged(event)
1252     {
1253         let {timestamp, isLowPower} = event.data;
1254
1255         this._addRecord(new WI.MediaTimelineRecord(WI.MediaTimelineRecord.EventType.LowPower, timestamp, {
1256             domNode: event.target,
1257             isLowPower,
1258         }));
1259     }
1260 };
1261
1262 WI.TimelineManager.CapturingState = {
1263     Inactive: "inactive",
1264     Starting: "starting",
1265     Active: "active",
1266     Stopping: "stopping",
1267 };
1268
1269 WI.TimelineManager.Event = {
1270     CapturingStateChanged: "timeline-manager-capturing-started",
1271     RecordingCreated: "timeline-manager-recording-created",
1272     RecordingLoaded: "timeline-manager-recording-loaded",
1273     RecordingImported: "timeline-manager-recording-imported",
1274 };
1275
1276 WI.TimelineManager.MaximumAutoRecordDuration = 90000; // 90 seconds
1277 WI.TimelineManager.MaximumAutoRecordDurationAfterLoadEvent = 10000; // 10 seconds
1278 WI.TimelineManager.DeadTimeRequiredToStopAutoRecordingEarly = 2000; // 2 seconds