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