5ba3b315290aec0499bc871072991201b5c6ff13
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Models / TimelineRecording.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.TimelineRecording = class TimelineRecording extends WebInspector.Object
27 {
28     constructor(identifier, displayName)
29     {
30         super();
31
32         this._identifier = identifier;
33         this._timelines = new Map;
34         this._displayName = displayName;
35         this._isWritable = true;
36
37         // For legacy backends, we compute the elapsed time of records relative to this timestamp.
38         this._legacyFirstRecordedTimestamp = NaN;
39
40         this.reset(true);
41     }
42
43     // Public
44
45     get displayName()
46     {
47         return this._displayName;
48     }
49
50     get identifier()
51     {
52         return this._identifier;
53     }
54
55     get timelines()
56     {
57         return this._timelines;
58     }
59
60     get startTime()
61     {
62         return this._startTime;
63     }
64
65     get endTime()
66     {
67         return this._endTime;
68     }
69
70     saveIdentityToCookie()
71     {
72         // Do nothing. Timeline recordings are not persisted when the inspector is
73         // re-opened, so do not attempt to restore by identifier or display name.
74     }
75
76     isWritable()
77     {
78         return this._isWritable;
79     }
80
81     isEmpty()
82     {
83         for (var timeline of this._timelines.values()) {
84             if (timeline.records.length)
85                 return false;
86         }
87
88         return true;
89     }
90
91     unloaded()
92     {
93         console.assert(!this.isEmpty(), "Shouldn't unload an empty recording; it should be reused instead.");
94
95         this._isWritable = false;
96
97         this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.Unloaded);
98     }
99
100     reset(suppressEvents)
101     {
102         console.assert(this._isWritable, "Can't reset a read-only recording.");
103
104         this._sourceCodeTimelinesMap = new Map;
105         this._eventMarkers = [];
106         this._startTime = NaN;
107         this._endTime = NaN;
108
109         for (var timeline of this._timelines.values())
110             timeline.reset(suppressEvents);
111
112         WebInspector.RenderingFrameTimelineRecord.resetFrameIndex();
113
114         if (!suppressEvents) {
115             this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.Reset);
116             this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.TimesUpdated);
117         }
118     }
119
120     sourceCodeTimelinesForSourceCode(sourceCode)
121     {
122         var timelines = this._sourceCodeTimelinesMap.get(sourceCode);
123         if (!timelines)
124             return [];
125         return [...timelines.values()];
126     }
127
128     addTimeline(timeline)
129     {
130         console.assert(timeline instanceof WebInspector.Timeline, timeline);
131         console.assert(!this._timelines.has(timeline), this._timelines, timeline);
132
133         this._timelines.set(timeline.type, timeline);
134
135         timeline.addEventListener(WebInspector.Timeline.Event.TimesUpdated, this._timelineTimesUpdated, this);
136         this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.TimelineAdded, {timeline});
137     }
138
139     removeTimeline(timeline)
140     {
141         console.assert(timeline instanceof WebInspector.Timeline, timeline);
142         console.assert(this._timelines.has(timeline.type), this._timelines, timeline);
143         console.assert(this._timelines.get(timeline.type) === timeline, this._timelines, timeline);
144
145         this._timelines.delete(timeline.type);
146
147         timeline.removeEventListener(WebInspector.Timeline.Event.TimesUpdated, this._timelineTimesUpdated, this);
148         this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.TimelineRemoved, {timeline});
149     }
150
151     addEventMarker(eventMarker)
152     {
153         this._eventMarkers.push(eventMarker);
154     }
155
156     addRecord(record)
157     {
158         var timeline = this._timelines.get(record.type);
159         console.assert(timeline, record, this._timelines);
160         if (!timeline)
161             return;
162
163         // Add the record to the global timeline by type.
164         timeline.addRecord(record);
165
166         // Network and RenderingFrame records don't have source code timelines.
167         if (record.type === WebInspector.TimelineRecord.Type.Network || record.type === WebInspector.TimelineRecord.Type.RenderingFrame)
168             return;
169
170         // Add the record to the source code timelines.
171         var activeMainResource = WebInspector.frameResourceManager.mainFrame.provisionalMainResource || WebInspector.frameResourceManager.mainFrame.mainResource;
172         var sourceCode = record.sourceCodeLocation ? record.sourceCodeLocation.sourceCode : activeMainResource;
173
174         var sourceCodeTimelines = this._sourceCodeTimelinesMap.get(sourceCode);
175         if (!sourceCodeTimelines) {
176             sourceCodeTimelines = new Map;
177             this._sourceCodeTimelinesMap.set(sourceCode, sourceCodeTimelines);
178         }
179
180         var newTimeline = false;
181         var key = this._keyForRecord(record);
182         var sourceCodeTimeline = sourceCodeTimelines.get(key);
183         if (!sourceCodeTimeline) {
184             sourceCodeTimeline = new WebInspector.SourceCodeTimeline(sourceCode, record.sourceCodeLocation, record.type, record.eventType);
185             sourceCodeTimelines.set(key, sourceCodeTimeline);
186             newTimeline = true;
187         }
188
189         sourceCodeTimeline.addRecord(record);
190
191         if (newTimeline)
192             this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.SourceCodeTimelineAdded, {sourceCodeTimeline});
193     }
194
195     computeElapsedTime(timestamp)
196     {
197         if (!timestamp || isNaN(timestamp))
198             return NaN;
199
200         // COMPATIBILITY (iOS 8): old backends send timestamps (seconds or milliseconds since the epoch),
201         // rather than seconds elapsed since timeline capturing started. We approximate the latter by
202         // subtracting the start timestamp, as old versions did not use monotonic times.
203         if (WebInspector.TimelineRecording.isLegacy === undefined)
204             WebInspector.TimelineRecording.isLegacy = timestamp > WebInspector.TimelineRecording.TimestampThresholdForLegacyRecordConversion;
205
206         if (!WebInspector.TimelineRecording.isLegacy)
207             return timestamp;
208
209         // If the record's start time is large, but not really large, then it is seconds since epoch
210         // not millseconds since epoch, so convert it to milliseconds.
211         if (timestamp < WebInspector.TimelineRecording.TimestampThresholdForLegacyAssumedMilliseconds)
212             timestamp *= 1000;
213
214         if (isNaN(this._legacyFirstRecordedTimestamp))
215             this._legacyFirstRecordedTimestamp = timestamp;
216
217         // Return seconds since the first recorded value.
218         return (timestamp - this._legacyFirstRecordedTimestamp) / 1000.0;
219     }
220
221     setLegacyBaseTimestamp(timestamp)
222     {
223         console.assert(isNaN(this._legacyFirstRecordedTimestamp));
224
225         if (timestamp < WebInspector.TimelineRecording.TimestampThresholdForLegacyAssumedMilliseconds)
226             timestamp *= 1000;
227
228         this._legacyFirstRecordedTimestamp = timestamp;
229     }
230
231     // Private
232
233     _keyForRecord(record)
234     {
235         var key = record.type;
236         if (record instanceof WebInspector.ScriptTimelineRecord || record instanceof WebInspector.LayoutTimelineRecord)
237             key += ":" + record.eventType;
238         if (record instanceof WebInspector.ScriptTimelineRecord && record.eventType === WebInspector.ScriptTimelineRecord.EventType.EventDispatched)
239             key += ":" + record.details;
240         if (record.sourceCodeLocation)
241             key += ":" + record.sourceCodeLocation.lineNumber + ":" + record.sourceCodeLocation.columnNumber;
242         return key;
243     }
244
245     _timelineTimesUpdated(event)
246     {
247         var timeline = event.target;
248         var changed = false;
249
250         if (isNaN(this._startTime) || timeline.startTime < this._startTime) {
251             this._startTime = timeline.startTime;
252             changed = true;
253         }
254
255         if (isNaN(this._endTime) || this._endTime < timeline.endTime) {
256             this._endTime = timeline.endTime;
257             changed = true;
258         }
259
260         if (changed)
261             this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.TimesUpdated);
262     }
263 };
264
265 WebInspector.TimelineRecording.Event = {
266     Reset: "timeline-recording-reset",
267     Unloaded: "timeline-recording-unloaded",
268     SourceCodeTimelineAdded: "timeline-recording-source-code-timeline-added",
269     TimelineAdded: "timeline-recording-timeline-added",
270     TimelineRemoved: "timeline-recording-timeline-removed",
271     TimesUpdated: "timeline-recording-times-updated"
272 };
273
274 WebInspector.TimelineRecording.isLegacy = undefined;
275 WebInspector.TimelineRecording.TimestampThresholdForLegacyRecordConversion = 10000000; // Some value not near zero.
276 WebInspector.TimelineRecording.TimestampThresholdForLegacyAssumedMilliseconds = 1420099200000; // Date.parse("Jan 1, 2015"). Milliseconds since epoch.