70ebd1c4a6b1dc8a61cc661da6b504b21403c802
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Controllers / TimelineManager.js
1 /*
2  * Copyright (C) 2013 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 WebInspector.TimelineManager = class TimelineManager extends WebInspector.Object
27 {
28     constructor()
29     {
30         super();
31
32         WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ProvisionalLoadStarted, this._startAutoCapturing, this);
33         WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
34         WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this);
35
36         WebInspector.heapManager.addEventListener(WebInspector.HeapManager.Event.GarbageCollected, this._garbageCollected, this);
37
38         this._persistentNetworkTimeline = new WebInspector.NetworkTimeline;
39
40         this._isCapturing = false;
41         this._isCapturingPageReload = false;
42         this._autoCapturingMainResource = null;
43         this._boundStopCapturing = this.stopCapturing.bind(this);
44
45         this.reset();
46     }
47
48     // Public
49
50     reset()
51     {
52         if (this._isCapturing)
53             this.stopCapturing();
54
55         this._recordings = [];
56         this._activeRecording = null;
57         this._nextRecordingIdentifier = 1;
58
59         this._loadNewRecording();
60     }
61
62     // The current recording that new timeline records will be appended to, if any.
63     get activeRecording()
64     {
65         console.assert(this._activeRecording || !this._isCapturing);
66         return this._activeRecording;
67     }
68
69     get persistentNetworkTimeline()
70     {
71         return this._persistentNetworkTimeline;
72     }
73
74     get recordings()
75     {
76         return this._recordings.slice();
77     }
78
79     get autoCaptureOnPageLoad()
80     {
81         return this._autoCaptureOnPageLoad;
82     }
83
84     set autoCaptureOnPageLoad(autoCapture)
85     {
86         this._autoCaptureOnPageLoad = autoCapture;
87     }
88
89     isCapturing()
90     {
91         return this._isCapturing;
92     }
93
94     isCapturingPageReload()
95     {
96         return this._isCapturingPageReload;
97     }
98
99     startCapturing(shouldCreateRecording)
100     {
101         console.assert(!this._isCapturing, "TimelineManager is already capturing.");
102
103         if (!this._activeRecording || shouldCreateRecording)
104             this._loadNewRecording();
105
106         var result = TimelineAgent.start();
107
108         // COMPATIBILITY (iOS 7): recordingStarted event did not exist yet. Start explicitly.
109         if (!TimelineAgent.hasEvent("recordingStarted")) {
110             result.then(function() {
111                 WebInspector.timelineManager.capturingStarted();
112             });
113         }
114     }
115
116     stopCapturing()
117     {
118         console.assert(this._isCapturing, "TimelineManager is not capturing.");
119
120         TimelineAgent.stop();
121
122         // NOTE: Always stop immediately instead of waiting for a Timeline.recordingStopped event.
123         // This way the UI feels as responsive to a stop as possible.
124         this.capturingStopped();
125     }
126
127     unloadRecording()
128     {
129         if (!this._activeRecording)
130             return;
131
132         if (this._isCapturing)
133             this.stopCapturing();
134
135         this._activeRecording.unloaded();
136         this._activeRecording = null;
137     }
138
139     computeElapsedTime(timestamp)
140     {
141         if (!this._activeRecording)
142             return 0;
143
144         return this._activeRecording.computeElapsedTime(timestamp);
145     }
146
147     // Protected
148
149     capturingStarted(startTime)
150     {
151         if (this._isCapturing)
152             return;
153
154         this._isCapturing = true;
155
156         this.dispatchEventToListeners(WebInspector.TimelineManager.Event.CapturingStarted, {startTime});
157     }
158
159     capturingStopped(endTime)
160     {
161         if (!this._isCapturing)
162             return;
163
164         if (this._stopCapturingTimeout) {
165             clearTimeout(this._stopCapturingTimeout);
166             this._stopCapturingTimeout = undefined;
167         }
168
169         if (this._deadTimeTimeout) {
170             clearTimeout(this._deadTimeTimeout);
171             this._deadTimeTimeout = undefined;
172         }
173
174         this._isCapturing = false;
175         this._isCapturingPageReload = false;
176         this._autoCapturingMainResource = null;
177
178         this.dispatchEventToListeners(WebInspector.TimelineManager.Event.CapturingStopped, {endTime});
179     }
180
181     eventRecorded(recordPayload)
182     {
183         // Called from WebInspector.TimelineObserver.
184
185         if (!this._isCapturing)
186             return;
187
188         var records = [];
189
190         // Iterate over the records tree using a stack. Doing this recursively has
191         // been known to cause a call stack overflow. https://webkit.org/b/79106
192         var stack = [{array: [recordPayload], parent: null, parentRecord: null, index: 0}];
193         while (stack.length) {
194             var entry = stack.lastValue;
195             var recordPayloads = entry.array;
196
197             if (entry.index < recordPayloads.length) {
198                 var recordPayload = recordPayloads[entry.index];
199                 var record = this._processEvent(recordPayload, entry.parent);
200                 if (record) {
201                     record.parent = entry.parentRecord;
202                     records.push(record);
203                     if (entry.parentRecord)
204                         entry.parentRecord.children.push(record);
205                 }
206
207                 if (recordPayload.children && recordPayload.children.length)
208                     stack.push({array: recordPayload.children, parent: recordPayload, parentRecord: record || entry.parentRecord, index: 0});
209                 ++entry.index;
210             } else
211                 stack.pop();
212         }
213
214         for (var record of records) {
215             if (record.type === WebInspector.TimelineRecord.Type.RenderingFrame) {
216                 if (!record.children.length)
217                     continue;
218                 record.setupFrameIndex();
219             }
220
221             this._addRecord(record);
222         }
223     }
224
225     // Protected
226
227     pageDidLoad(timestamp)
228     {
229         // Called from WebInspector.PageObserver.
230
231         if (isNaN(WebInspector.frameResourceManager.mainFrame.loadEventTimestamp))
232             WebInspector.frameResourceManager.mainFrame.markLoadEvent(this.activeRecording.computeElapsedTime(timestamp));
233     }
234
235     // Private
236
237     _processRecord(recordPayload, parentRecordPayload)
238     {
239         var startTime = this.activeRecording.computeElapsedTime(recordPayload.startTime);
240         var endTime = this.activeRecording.computeElapsedTime(recordPayload.endTime);
241         var callFrames = this._callFramesFromPayload(recordPayload.stackTrace);
242
243         var significantCallFrame = null;
244         if (callFrames) {
245             for (var i = 0; i < callFrames.length; ++i) {
246                 if (callFrames[i].nativeCode)
247                     continue;
248                 significantCallFrame = callFrames[i];
249                 break;
250             }
251         }
252
253         var sourceCodeLocation = significantCallFrame && significantCallFrame.sourceCodeLocation;
254
255         switch (recordPayload.type) {
256         case TimelineAgent.EventType.ScheduleStyleRecalculation:
257             console.assert(isNaN(endTime));
258
259             // Pass the startTime as the endTime since this record type has no duration.
260             return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.InvalidateStyles, startTime, startTime, callFrames, sourceCodeLocation);
261
262         case TimelineAgent.EventType.RecalculateStyles:
263             return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.RecalculateStyles, startTime, endTime, callFrames, sourceCodeLocation);
264
265         case TimelineAgent.EventType.InvalidateLayout:
266             console.assert(isNaN(endTime));
267
268             // Pass the startTime as the endTime since this record type has no duration.
269             return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.InvalidateLayout, startTime, startTime, callFrames, sourceCodeLocation);
270
271         case TimelineAgent.EventType.Layout:
272             var layoutRecordType = sourceCodeLocation ? WebInspector.LayoutTimelineRecord.EventType.ForcedLayout : WebInspector.LayoutTimelineRecord.EventType.Layout;
273             var quad = new WebInspector.Quad(recordPayload.data.root);
274             return new WebInspector.LayoutTimelineRecord(layoutRecordType, startTime, endTime, callFrames, sourceCodeLocation, quad);
275
276         case TimelineAgent.EventType.Paint:
277             var quad = new WebInspector.Quad(recordPayload.data.clip);
278             return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.Paint, startTime, endTime, callFrames, sourceCodeLocation, quad);
279
280         case TimelineAgent.EventType.Composite:
281             return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.Composite, startTime, endTime, callFrames, sourceCodeLocation);
282
283         case TimelineAgent.EventType.RenderingFrame:
284             if (!recordPayload.children || !recordPayload.children.length)
285                 return null;
286
287             return new WebInspector.RenderingFrameTimelineRecord(startTime, endTime);
288
289         case TimelineAgent.EventType.EvaluateScript:
290             if (!sourceCodeLocation) {
291                 var mainFrame = WebInspector.frameResourceManager.mainFrame;
292                 var scriptResource = mainFrame.url === recordPayload.data.url ? mainFrame.mainResource : mainFrame.resourceForURL(recordPayload.data.url, true);
293                 if (scriptResource) {
294                     // The lineNumber is 1-based, but we expect 0-based.
295                     var lineNumber = recordPayload.data.lineNumber - 1;
296
297                     // FIXME: No column number is provided.
298                     sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, 0);
299                 }
300             }
301
302             var profileData = recordPayload.data.profile;
303
304             switch (parentRecordPayload && parentRecordPayload.type) {
305             case TimelineAgent.EventType.TimerFire:
306                 return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profileData);
307             default:
308                 return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, null, profileData);
309             }
310
311             break;
312
313         case TimelineAgent.EventType.ConsoleProfile:
314             var profileData = recordPayload.data.profile;
315             console.assert(profileData);
316             return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ConsoleProfileRecorded, startTime, endTime, callFrames, sourceCodeLocation, recordPayload.data.title, profileData);
317
318         case TimelineAgent.EventType.FunctionCall:
319             // FunctionCall always happens as a child of another record, and since the FunctionCall record
320             // has useful info we just make the timeline record here (combining the data from both records).
321             if (!parentRecordPayload)
322                 break;
323
324             if (!sourceCodeLocation) {
325                 var mainFrame = WebInspector.frameResourceManager.mainFrame;
326                 var scriptResource = mainFrame.url === recordPayload.data.scriptName ? mainFrame.mainResource : mainFrame.resourceForURL(recordPayload.data.scriptName, true);
327                 if (scriptResource) {
328                     // The lineNumber is 1-based, but we expect 0-based.
329                     var lineNumber = recordPayload.data.scriptLine - 1;
330
331                     // FIXME: No column number is provided.
332                     sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, 0);
333                 }
334             }
335
336             var profileData = recordPayload.data.profile;
337
338             switch (parentRecordPayload.type) {
339             case TimelineAgent.EventType.TimerFire:
340                 return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profileData);
341             case TimelineAgent.EventType.EventDispatch:
342                 return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.type, profileData);
343             case TimelineAgent.EventType.XHRLoad:
344                 return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, "load", profileData);
345             case TimelineAgent.EventType.XHRReadyStateChange:
346                 return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, "readystatechange", profileData);
347             case TimelineAgent.EventType.FireAnimationFrame:
348                 return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
349             }
350
351             break;
352
353         case TimelineAgent.EventType.ProbeSample:
354             // Pass the startTime as the endTime since this record type has no duration.
355             sourceCodeLocation = WebInspector.probeManager.probeForIdentifier(recordPayload.data.probeId).breakpoint.sourceCodeLocation;
356             return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ProbeSampleRecorded, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.probeId);
357
358         case TimelineAgent.EventType.TimerInstall:
359             console.assert(isNaN(endTime));
360
361             // Pass the startTime as the endTime since this record type has no duration.
362             return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerInstalled, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId);
363
364         case TimelineAgent.EventType.TimerRemove:
365             console.assert(isNaN(endTime));
366
367             // Pass the startTime as the endTime since this record type has no duration.
368             return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerRemoved, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId);
369
370         case TimelineAgent.EventType.RequestAnimationFrame:
371             console.assert(isNaN(endTime));
372
373             // Pass the startTime as the endTime since this record type has no duration.
374             return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameRequested, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId);
375
376         case TimelineAgent.EventType.CancelAnimationFrame:
377             console.assert(isNaN(endTime));
378
379             // Pass the startTime as the endTime since this record type has no duration.
380             return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameCanceled, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId);
381         }
382
383         return null;
384     }
385
386     _processEvent(recordPayload, parentRecordPayload)
387     {
388         var startTime = this.activeRecording.computeElapsedTime(recordPayload.startTime);
389         var endTime = this.activeRecording.computeElapsedTime(recordPayload.endTime);
390
391         switch (recordPayload.type) {
392         case TimelineAgent.EventType.MarkLoad:
393             console.assert(isNaN(endTime));
394
395             var frame = WebInspector.frameResourceManager.frameForIdentifier(recordPayload.frameId);
396             console.assert(frame);
397             if (!frame)
398                 break;
399
400             frame.markLoadEvent(startTime);
401
402             if (!frame.isMainFrame())
403                 break;
404
405             var eventMarker = new WebInspector.TimelineMarker(startTime, WebInspector.TimelineMarker.Type.LoadEvent);
406             this._activeRecording.addEventMarker(eventMarker);
407
408             this._stopAutoRecordingSoon();
409             break;
410
411         case TimelineAgent.EventType.MarkDOMContent:
412             console.assert(isNaN(endTime));
413
414             var frame = WebInspector.frameResourceManager.frameForIdentifier(recordPayload.frameId);
415             console.assert(frame);
416             if (!frame)
417                 break;
418
419             frame.markDOMContentReadyEvent(startTime);
420
421             if (!frame.isMainFrame())
422                 break;
423
424             var eventMarker = new WebInspector.TimelineMarker(startTime, WebInspector.TimelineMarker.Type.DOMContentEvent);
425             this._activeRecording.addEventMarker(eventMarker);
426             break;
427
428         case TimelineAgent.EventType.TimeStamp:
429             var eventMarker = new WebInspector.TimelineMarker(startTime, WebInspector.TimelineMarker.Type.TimeStamp);
430             this._activeRecording.addEventMarker(eventMarker);
431             break;
432
433         default:
434             return this._processRecord(recordPayload, parentRecordPayload);
435         }
436
437         return null;
438     }
439
440     _loadNewRecording()
441     {
442         if (this._activeRecording && this._activeRecording.isEmpty())
443             return;
444
445         var identifier = this._nextRecordingIdentifier++;
446         var newRecording = new WebInspector.TimelineRecording(identifier, WebInspector.UIString("Timeline Recording %d").format(identifier));
447         newRecording.addTimeline(WebInspector.Timeline.create(WebInspector.TimelineRecord.Type.Network, newRecording));
448         newRecording.addTimeline(WebInspector.Timeline.create(WebInspector.TimelineRecord.Type.Layout, newRecording));
449         newRecording.addTimeline(WebInspector.Timeline.create(WebInspector.TimelineRecord.Type.Script, newRecording));
450
451         // COMPATIBILITY (iOS 8): TimelineAgent.EventType.RenderingFrame did not exist.
452         if (window.TimelineAgent && TimelineAgent.EventType.RenderingFrame)
453             newRecording.addTimeline(WebInspector.Timeline.create(WebInspector.TimelineRecord.Type.RenderingFrame, newRecording));
454
455         this._recordings.push(newRecording);
456         this.dispatchEventToListeners(WebInspector.TimelineManager.Event.RecordingCreated, {recording: newRecording});
457
458         console.assert(newRecording.isWritable());
459
460         if (this._isCapturing)
461             this.stopCapturing();
462
463         var oldRecording = this._activeRecording;
464         if (oldRecording)
465             oldRecording.unloaded();
466
467         this._activeRecording = newRecording;
468
469         // COMPATIBILITY (iOS 8): When using Legacy timestamps, a navigation will have computed
470         // the main resource's will send request timestamp in terms of the last page's base timestamp.
471         // Now that we have navigated, we should reset the legacy base timestamp and the
472         // will send request timestamp for the new main resource. This way, all new timeline
473         // records will be computed relative to the new navigation.
474         if (this._autoCapturingMainResource && WebInspector.TimelineRecording.isLegacy) {
475             console.assert(this._autoCapturingMainResource.originalRequestWillBeSentTimestamp);
476             this._activeRecording.setLegacyBaseTimestamp(this._autoCapturingMainResource.originalRequestWillBeSentTimestamp);
477             this._autoCapturingMainResource._requestSentTimestamp = 0;
478         }
479
480         this.dispatchEventToListeners(WebInspector.TimelineManager.Event.RecordingLoaded, {oldRecording});
481     }
482
483     _callFramesFromPayload(payload)
484     {
485         if (!payload)
486             return null;
487
488         return payload.map(WebInspector.CallFrame.fromPayload);
489     }
490
491     _addRecord(record)
492     {
493         this._activeRecording.addRecord(record);
494
495         // Only worry about dead time after the load event.
496         if (!isNaN(WebInspector.frameResourceManager.mainFrame.loadEventTimestamp))
497             this._resetAutoRecordingDeadTimeTimeout();
498     }
499
500     _startAutoCapturing(event)
501     {
502         if (!this._autoCaptureOnPageLoad)
503             return false;
504
505         if (!event.target.isMainFrame() || (this._isCapturing && !this._autoCapturingMainResource))
506             return false;
507
508         var mainResource = event.target.provisionalMainResource || event.target.mainResource;
509         if (mainResource === this._autoCapturingMainResource)
510             return false;
511
512         var oldMainResource = event.target.mainResource || null;
513         this._isCapturingPageReload = oldMainResource !== null && oldMainResource.url === mainResource.url;
514
515         if (this._isCapturing)
516             this.stopCapturing();
517
518         this._autoCapturingMainResource = mainResource;
519
520         this._loadNewRecording();
521
522         this.startCapturing();
523
524         this._addRecord(new WebInspector.ResourceTimelineRecord(mainResource));
525
526         if (this._stopCapturingTimeout)
527             clearTimeout(this._stopCapturingTimeout);
528         this._stopCapturingTimeout = setTimeout(this._boundStopCapturing, WebInspector.TimelineManager.MaximumAutoRecordDuration);
529
530         return true;
531     }
532
533     _stopAutoRecordingSoon()
534     {
535         // Only auto stop when auto capturing.
536         if (!this._isCapturing || !this._autoCapturingMainResource)
537             return;
538
539         if (this._stopCapturingTimeout)
540             clearTimeout(this._stopCapturingTimeout);
541         this._stopCapturingTimeout = setTimeout(this._boundStopCapturing, WebInspector.TimelineManager.MaximumAutoRecordDurationAfterLoadEvent);
542     }
543
544     _resetAutoRecordingDeadTimeTimeout()
545     {
546         // Only monitor dead time when auto capturing.
547         if (!this._isCapturing || !this._autoCapturingMainResource)
548             return;
549
550         if (this._deadTimeTimeout)
551             clearTimeout(this._deadTimeTimeout);
552         this._deadTimeTimeout = setTimeout(this._boundStopCapturing, WebInspector.TimelineManager.DeadTimeRequiredToStopAutoRecordingEarly);
553     }
554
555     _mainResourceDidChange(event)
556     {
557         if (event.target.isMainFrame())
558             this._persistentNetworkTimeline.reset();
559
560         var mainResource = event.target.mainResource;
561         var record = new WebInspector.ResourceTimelineRecord(mainResource);
562         if (!isNaN(record.startTime))
563             this._persistentNetworkTimeline.addRecord(record);
564
565         // Ignore resource events when there isn't a main frame yet. Those events are triggered by
566         // loading the cached resources when the inspector opens, and they do not have timing information.
567         if (!WebInspector.frameResourceManager.mainFrame)
568             return;
569
570         if (this._startAutoCapturing(event))
571             return;
572
573         if (!this._isCapturing)
574             return;
575
576         if (mainResource === this._autoCapturingMainResource)
577             return;
578
579         this._addRecord(record);
580     }
581
582     _resourceWasAdded(event)
583     {
584         var record = new WebInspector.ResourceTimelineRecord(event.data.resource);
585         if (!isNaN(record.startTime))
586             this._persistentNetworkTimeline.addRecord(record);
587
588         // Ignore resource events when there isn't a main frame yet. Those events are triggered by
589         // loading the cached resources when the inspector opens, and they do not have timing information.
590         if (!WebInspector.frameResourceManager.mainFrame)
591             return;
592
593         if (!this._isCapturing)
594             return;
595
596         this._addRecord(record);
597     }
598
599     _garbageCollected(event)
600     {
601         if (!this._isCapturing)
602             return;
603
604         let collection = event.data.collection;
605         this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.GarbageCollected, collection.startTime, collection.endTime, null, null, collection));
606     }
607 };
608
609 WebInspector.TimelineManager.Event = {
610     RecordingCreated: "timeline-manager-recording-created",
611     RecordingLoaded: "timeline-manager-recording-loaded",
612     CapturingStarted: "timeline-manager-capturing-started",
613     CapturingStopped: "timeline-manager-capturing-stopped"
614 };
615
616 WebInspector.TimelineManager.MaximumAutoRecordDuration = 90000; // 90 seconds
617 WebInspector.TimelineManager.MaximumAutoRecordDurationAfterLoadEvent = 10000; // 10 seconds
618 WebInspector.TimelineManager.DeadTimeRequiredToStopAutoRecordingEarly = 2000; // 2 seconds