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