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