Web Inspector: Timelines: can't reliably stop/start a recording
[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     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         console.assert(this.isCapturing());
586         if (!this.isCapturing())
587             return;
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         console.assert(this.isCapturing());
864
865         this._activeRecording.addRecord(record);
866
867         // Only worry about dead time after the load event.
868         if (WI.networkManager.mainFrame && isNaN(WI.networkManager.mainFrame.loadEventTimestamp))
869             this._resetAutoRecordingDeadTimeTimeout();
870     }
871
872     _attemptAutoCapturingForFrame(frame)
873     {
874         if (!this._autoCaptureOnPageLoad)
875             return false;
876
877         if (!frame.isMainFrame())
878             return false;
879
880         // COMPATIBILITY (iOS 9): Timeline.setAutoCaptureEnabled did not exist.
881         // Perform auto capture in the frontend.
882         if (!InspectorBackend.domains.Timeline)
883             return false;
884         if (!InspectorBackend.domains.Timeline.setAutoCaptureEnabled)
885             return this._legacyAttemptStartAutoCapturingForFrame(frame);
886
887         if (!this._shouldSetAutoCapturingMainResource)
888             return false;
889
890         console.assert(this.isCapturing(), "We saw autoCaptureStarted so we should already be capturing");
891
892         let mainResource = frame.provisionalMainResource || frame.mainResource;
893         if (mainResource === this._mainResourceForAutoCapturing)
894             return false;
895
896         let oldMainResource = frame.mainResource || null;
897         this._isCapturingPageReload = oldMainResource !== null && oldMainResource.url === mainResource.url;
898
899         this._mainResourceForAutoCapturing = mainResource;
900
901         this._addRecord(new WI.ResourceTimelineRecord(mainResource));
902
903         this._resetAutoRecordingMaxTimeTimeout();
904
905         this._shouldSetAutoCapturingMainResource = false;
906
907         return true;
908     }
909
910     _legacyAttemptStartAutoCapturingForFrame(frame)
911     {
912         if (this.isCapturing() && !this._mainResourceForAutoCapturing)
913             return false;
914
915         let mainResource = frame.provisionalMainResource || frame.mainResource;
916         if (mainResource === this._mainResourceForAutoCapturing)
917             return false;
918
919         let oldMainResource = frame.mainResource || null;
920         this._isCapturingPageReload = oldMainResource !== null && oldMainResource.url === mainResource.url;
921
922         if (this.isCapturing())
923             this.stopCapturing();
924
925         this._mainResourceForAutoCapturing = mainResource;
926
927         this._loadNewRecording();
928
929         this.startCapturing();
930
931         this._addRecord(new WI.ResourceTimelineRecord(mainResource));
932
933         this._resetAutoRecordingMaxTimeTimeout();
934
935         return true;
936     }
937
938     _stopAutoRecordingSoon()
939     {
940         if (!WI.settings.timelinesAutoStop.value)
941             return;
942
943         // Only auto stop when auto capturing.
944         if (!this.isCapturing() || !this._mainResourceForAutoCapturing)
945             return;
946
947         if (this._stopCapturingTimeout)
948             clearTimeout(this._stopCapturingTimeout);
949         this._stopCapturingTimeout = setTimeout(this._boundStopCapturing, WI.TimelineManager.MaximumAutoRecordDurationAfterLoadEvent);
950     }
951
952     _resetAutoRecordingMaxTimeTimeout()
953     {
954         if (!WI.settings.timelinesAutoStop.value)
955             return;
956
957         if (this._stopCapturingTimeout)
958             clearTimeout(this._stopCapturingTimeout);
959         this._stopCapturingTimeout = setTimeout(this._boundStopCapturing, WI.TimelineManager.MaximumAutoRecordDuration);
960     }
961
962     _resetAutoRecordingDeadTimeTimeout()
963     {
964         if (!WI.settings.timelinesAutoStop.value)
965             return;
966
967         // Only monitor dead time when auto capturing.
968         if (!this.isCapturing() || !this._mainResourceForAutoCapturing)
969             return;
970
971         // Avoid unnecessary churning of timeout identifier by not tickling until 10ms have passed.
972         let now = Date.now();
973         if (now <= this._lastDeadTimeTickle)
974             return;
975         this._lastDeadTimeTickle = now + 10;
976
977         if (this._deadTimeTimeout)
978             clearTimeout(this._deadTimeTimeout);
979         this._deadTimeTimeout = setTimeout(this._boundStopCapturing, WI.TimelineManager.DeadTimeRequiredToStopAutoRecordingEarly);
980     }
981
982     _provisionalLoadStarted(event)
983     {
984         this._attemptAutoCapturingForFrame(event.target);
985     }
986
987     _mainResourceDidChange(event)
988     {
989         // Ignore resource events when there isn't a main frame yet. Those events are triggered by
990         // loading the cached resources when the inspector opens, and they do not have timing information.
991         if (!WI.networkManager.mainFrame)
992             return;
993
994         let frame = event.target;
995
996         // When performing a page transition start a recording once the main resource changes.
997         // We start a legacy capture because the backend wasn't available to automatically
998         // initiate the capture, so the frontend must start the capture.
999         if (this._transitioningPageTarget) {
1000             this._transitioningPageTarget = false;
1001             if (this._autoCaptureOnPageLoad)
1002                 this._legacyAttemptStartAutoCapturingForFrame(frame);
1003             return;
1004         }
1005
1006         if (this._attemptAutoCapturingForFrame(frame))
1007             return;
1008
1009         if (!this.isCapturing())
1010             return;
1011
1012         let mainResource = frame.mainResource;
1013         if (mainResource === this._mainResourceForAutoCapturing)
1014             return;
1015
1016         this._addRecord(new WI.ResourceTimelineRecord(mainResource));
1017     }
1018
1019     _resourceWasAdded(event)
1020     {
1021         // Ignore resource events when there isn't a main frame yet. Those events are triggered by
1022         // loading the cached resources when the inspector opens, and they do not have timing information.
1023         if (!WI.networkManager.mainFrame)
1024             return;
1025
1026         this._addRecord(new WI.ResourceTimelineRecord(event.data.resource));
1027     }
1028
1029     _garbageCollected(event)
1030     {
1031         let {collection} = event.data;
1032         this._addRecord(new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.GarbageCollected, collection.startTime, collection.endTime, null, null, collection));
1033     }
1034
1035     _memoryPressure(event)
1036     {
1037         this._activeRecording.addMemoryPressureEvent(event.data.memoryPressureEvent);
1038     }
1039
1040     _handleTimelinesAutoStopSettingChanged(event)
1041     {
1042         if (WI.settings.timelinesAutoStop.value) {
1043             if (this._mainResourceForAutoCapturing && !isNaN(this._mainResourceForAutoCapturing.parentFrame.loadEventTimestamp))
1044                 this._stopAutoRecordingSoon();
1045             else
1046                 this._resetAutoRecordingMaxTimeTimeout();
1047             this._resetAutoRecordingDeadTimeTimeout();
1048         } else
1049             this.relaxAutoStop();
1050     }
1051
1052     _scriptProfilerTypeToScriptTimelineRecordType(type)
1053     {
1054         switch (type) {
1055         case ScriptProfilerAgent.EventType.API:
1056             return WI.ScriptTimelineRecord.EventType.APIScriptEvaluated;
1057         case ScriptProfilerAgent.EventType.Microtask:
1058             return WI.ScriptTimelineRecord.EventType.MicrotaskDispatched;
1059         case ScriptProfilerAgent.EventType.Other:
1060             return WI.ScriptTimelineRecord.EventType.ScriptEvaluated;
1061         }
1062     }
1063
1064     scriptProfilerTrackingStarted(timestamp)
1065     {
1066         this._scriptProfilerRecords = [];
1067
1068         this.capturingStarted(timestamp);
1069     }
1070
1071     scriptProfilerTrackingUpdated(event)
1072     {
1073         let {startTime, endTime, type} = event;
1074         let scriptRecordType = this._scriptProfilerTypeToScriptTimelineRecordType(type);
1075         let record = new WI.ScriptTimelineRecord(scriptRecordType, startTime, endTime, null, null, null, null);
1076         record.__scriptProfilerType = type;
1077         this._scriptProfilerRecords.push(record);
1078
1079         // "Other" events, generated by Web content, will have wrapping Timeline records
1080         // and need to be merged. Non-Other events, generated purely by the JavaScript
1081         // engine or outside of the page via APIs, will not have wrapping Timeline
1082         // records, so these records can just be added right now.
1083         if (type !== ScriptProfilerAgent.EventType.Other)
1084             this._addRecord(record);
1085     }
1086
1087     scriptProfilerTrackingCompleted(timestamp, samples)
1088     {
1089         console.assert(!this._webTimelineScriptRecordsExpectingScriptProfilerEvents || this._scriptProfilerRecords.length >= this._webTimelineScriptRecordsExpectingScriptProfilerEvents.length);
1090
1091         if (samples) {
1092             let {stackTraces} = samples;
1093             let topDownCallingContextTree = this._activeRecording.topDownCallingContextTree;
1094
1095             // Calculate a per-sample duration.
1096             let timestampIndex = 0;
1097             let timestampCount = stackTraces.length;
1098             let sampleDurations = new Array(timestampCount);
1099             let sampleDurationIndex = 0;
1100             const defaultDuration = 1 / 1000; // 1ms.
1101             for (let i = 0; i < this._scriptProfilerRecords.length; ++i) {
1102                 let record = this._scriptProfilerRecords[i];
1103
1104                 // Use a default duration for timestamps recorded outside of ScriptProfiler events.
1105                 while (timestampIndex < timestampCount && stackTraces[timestampIndex].timestamp < record.startTime) {
1106                     sampleDurations[sampleDurationIndex++] = defaultDuration;
1107                     timestampIndex++;
1108                 }
1109
1110                 // Average the duration per sample across all samples during the record.
1111                 let samplesInRecord = 0;
1112                 while (timestampIndex < timestampCount && stackTraces[timestampIndex].timestamp < record.endTime) {
1113                     timestampIndex++;
1114                     samplesInRecord++;
1115                 }
1116                 if (samplesInRecord) {
1117                     let averageDuration = (record.endTime - record.startTime) / samplesInRecord;
1118                     sampleDurations.fill(averageDuration, sampleDurationIndex, sampleDurationIndex + samplesInRecord);
1119                     sampleDurationIndex += samplesInRecord;
1120                 }
1121             }
1122
1123             // Use a default duration for timestamps recorded outside of ScriptProfiler events.
1124             if (timestampIndex < timestampCount)
1125                 sampleDurations.fill(defaultDuration, sampleDurationIndex);
1126
1127             this._activeRecording.initializeCallingContextTrees(stackTraces, sampleDurations);
1128
1129             // FIXME: This transformation should not be needed after introducing ProfileView.
1130             // Once we eliminate ProfileNodeTreeElements and ProfileNodeDataGridNodes.
1131             // <https://webkit.org/b/154973> Web Inspector: Timelines UI redesign: Remove TimelineSidebarPanel
1132             for (let i = 0; i < this._scriptProfilerRecords.length; ++i) {
1133                 let record = this._scriptProfilerRecords[i];
1134                 record.profilePayload = topDownCallingContextTree.toCPUProfilePayload(record.startTime, record.endTime);
1135             }
1136         }
1137
1138         // Associate the ScriptProfiler created records with Web Timeline records.
1139         // Filter out the already added ScriptProfiler events which should not have been wrapped.
1140         if (WI.sharedApp.debuggableType !== WI.DebuggableType.JavaScript) {
1141             this._scriptProfilerRecords = this._scriptProfilerRecords.filter((x) => x.__scriptProfilerType === ScriptProfilerAgent.EventType.Other);
1142             this._mergeScriptProfileRecords();
1143         }
1144
1145         this._scriptProfilerRecords = null;
1146
1147         let timeline = this._activeRecording.timelineForRecordType(WI.TimelineRecord.Type.Script);
1148         timeline.refresh();
1149
1150         this.capturingStopped(timestamp);
1151     }
1152
1153     _mergeScriptProfileRecords()
1154     {
1155         let nextRecord = function(list) { return list.shift() || null; };
1156         let nextWebTimelineRecord = nextRecord.bind(null, this._webTimelineScriptRecordsExpectingScriptProfilerEvents);
1157         let nextScriptProfilerRecord = nextRecord.bind(null, this._scriptProfilerRecords);
1158         let recordEnclosesRecord = function(record1, record2) {
1159             return record1.startTime <= record2.startTime && record1.endTime >= record2.endTime;
1160         };
1161
1162         let webRecord = nextWebTimelineRecord();
1163         let profilerRecord = nextScriptProfilerRecord();
1164
1165         while (webRecord && profilerRecord) {
1166             // Skip web records with parent web records. For example an EvaluateScript with an EvaluateScript parent.
1167             if (webRecord.parent instanceof WI.ScriptTimelineRecord) {
1168                 console.assert(recordEnclosesRecord(webRecord.parent, webRecord), "Timeline Record incorrectly wrapping another Timeline Record");
1169                 webRecord = nextWebTimelineRecord();
1170                 continue;
1171             }
1172
1173             // Normal case of a Web record wrapping a Script record.
1174             if (recordEnclosesRecord(webRecord, profilerRecord)) {
1175                 webRecord.profilePayload = profilerRecord.profilePayload;
1176                 profilerRecord = nextScriptProfilerRecord();
1177
1178                 // If there are more script profile records in the same time interval, add them
1179                 // as individual script evaluated records with profiles. This can happen with
1180                 // web microtask checkpoints that are technically inside of other web records.
1181                 // FIXME: <https://webkit.org/b/152903> Web Inspector: Timeline Cleanup: Better Timeline Record for Microtask Checkpoints
1182                 while (profilerRecord && recordEnclosesRecord(webRecord, profilerRecord)) {
1183                     this._addRecord(profilerRecord);
1184                     profilerRecord = nextScriptProfilerRecord();
1185                 }
1186
1187                 webRecord = nextWebTimelineRecord();
1188                 continue;
1189             }
1190
1191             // Profiler Record is entirely after the Web Record. This would mean an empty web record.
1192             if (profilerRecord.startTime > webRecord.endTime) {
1193                 console.warn("Unexpected case of a Timeline record not containing a ScriptProfiler event and profile data");
1194                 webRecord = nextWebTimelineRecord();
1195                 continue;
1196             }
1197
1198             // Non-wrapped profiler record.
1199             console.warn("Unexpected case of a ScriptProfiler event not being contained by a Timeline record");
1200             this._addRecord(profilerRecord);
1201             profilerRecord = nextScriptProfilerRecord();
1202         }
1203
1204         // Skipping the remaining ScriptProfiler events to match the current UI for handling Timeline records.
1205         // However, the remaining ScriptProfiler records are valid and could be shown.
1206         // FIXME: <https://webkit.org/b/152904> Web Inspector: Timeline UI should keep up with processing all incoming records
1207     }
1208
1209     _updateAutoCaptureInstruments(targets)
1210     {
1211         let enabledTimelineTypes = this._enabledTimelineTypesSetting.value;
1212
1213         for (let target of targets) {
1214             if (!target.TimelineAgent)
1215                 continue;
1216             if (!target.TimelineAgent.setInstruments)
1217                 continue;
1218
1219             let instrumentSet = new Set;
1220             for (let timelineType of enabledTimelineTypes) {
1221                 switch (timelineType) {
1222                 case WI.TimelineRecord.Type.Script:
1223                     instrumentSet.add(target.TimelineAgent.Instrument.ScriptProfiler);
1224                     break;
1225                 case WI.TimelineRecord.Type.HeapAllocations:
1226                     instrumentSet.add(target.TimelineAgent.Instrument.Heap);
1227                     break;
1228                 case WI.TimelineRecord.Type.Network:
1229                 case WI.TimelineRecord.Type.RenderingFrame:
1230                 case WI.TimelineRecord.Type.Layout:
1231                 case WI.TimelineRecord.Type.Media:
1232                     instrumentSet.add(target.TimelineAgent.Instrument.Timeline);
1233                     break;
1234                 case WI.TimelineRecord.Type.CPU:
1235                     instrumentSet.add(target.TimelineAgent.Instrument.CPU);
1236                     break;
1237                 case WI.TimelineRecord.Type.Memory:
1238                     instrumentSet.add(target.TimelineAgent.Instrument.Memory);
1239                     break;
1240                 }
1241             }
1242
1243             target.TimelineAgent.setInstruments(Array.from(instrumentSet));
1244         }
1245     }
1246
1247     _handleDOMNodeDidFireEvent(event)
1248     {
1249         let {domEvent} = event.data;
1250
1251         this._addRecord(new WI.MediaTimelineRecord(WI.MediaTimelineRecord.EventType.DOMEvent, domEvent.timestamp, {
1252             domNode: event.target,
1253             domEvent,
1254         }));
1255     }
1256
1257     _handleDOMNodeLowPowerChanged(event)
1258     {
1259         let {timestamp, isLowPower} = event.data;
1260
1261         this._addRecord(new WI.MediaTimelineRecord(WI.MediaTimelineRecord.EventType.LowPower, timestamp, {
1262             domNode: event.target,
1263             isLowPower,
1264         }));
1265     }
1266 };
1267
1268 WI.TimelineManager.CapturingState = {
1269     Inactive: "inactive",
1270     Starting: "starting",
1271     Active: "active",
1272     Stopping: "stopping",
1273 };
1274
1275 WI.TimelineManager.Event = {
1276     CapturingStateChanged: "timeline-manager-capturing-started",
1277     RecordingCreated: "timeline-manager-recording-created",
1278     RecordingLoaded: "timeline-manager-recording-loaded",
1279     RecordingImported: "timeline-manager-recording-imported",
1280 };
1281
1282 WI.TimelineManager.MaximumAutoRecordDuration = 90000; // 90 seconds
1283 WI.TimelineManager.MaximumAutoRecordDurationAfterLoadEvent = 10000; // 10 seconds
1284 WI.TimelineManager.DeadTimeRequiredToStopAutoRecordingEarly = 2000; // 2 seconds