Web Inspector: Timelines: can't reliably stop/start a recording
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Models / DefaultDashboard.js
1 /*
2  * Copyright (C) 2013, 2014 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS
14  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
15  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
16  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
17  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WI.DefaultDashboard = class DefaultDashboard extends WI.Object
27 {
28     constructor()
29     {
30         super();
31
32         this._waitingForFirstMainResourceToStartTrackingSize = true;
33
34         // Necessary event required to track page load time and resource sizes.
35         WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
36         WI.timelineManager.addEventListener(WI.TimelineManager.Event.CapturingStateChanged, this._handleTimelineCapturingStateChanged, this);
37
38         // Necessary events required to track load of resources.
39         WI.Frame.addEventListener(WI.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this);
40         WI.Target.addEventListener(WI.Target.Event.ResourceAdded, this._resourceWasAdded, this);
41         WI.networkManager.addEventListener(WI.NetworkManager.Event.FrameWasAdded, this._frameWasAdded, this);
42
43         // Necessary events required to track console messages.
44         WI.consoleManager.addEventListener(WI.ConsoleManager.Event.Cleared, this._consoleWasCleared, this);
45         WI.consoleManager.addEventListener(WI.ConsoleManager.Event.MessageAdded, this._consoleMessageAdded, this);
46         WI.consoleManager.addEventListener(WI.ConsoleManager.Event.PreviousMessageRepeatCountUpdated, this._consoleMessageWasRepeated, this);
47
48         // FIXME: This is working around the order of events. Normal page navigation
49         // triggers a MainResource change and then a MainFrame change. Page Transition
50         // triggers a MainFrame change then a MainResource change.
51         this._transitioningPageTarget = false;
52
53         WI.notifications.addEventListener(WI.Notification.TransitionPageTarget, this._transitionPageTarget, this);
54
55         this._resourcesCount = 0;
56         this._resourcesSize = 0;
57         this._time = 0;
58         this._logs = 0;
59         this._errors = 0;
60         this._issues = 0;
61     }
62
63     // Public
64
65     get resourcesCount()
66     {
67         return this._resourcesCount;
68     }
69
70     set resourcesCount(value)
71     {
72         this._resourcesCount = value;
73         this._dataDidChange();
74     }
75
76     get resourcesSize()
77     {
78         return this._resourcesSize;
79     }
80
81     set resourcesSize(value)
82     {
83         this._resourcesSize = value;
84         this._dataDidChange();
85     }
86
87     get time()
88     {
89         return this._time;
90     }
91
92     set time(value)
93     {
94         this._time = value;
95         this._dataDidChange();
96     }
97
98     get logs()
99     {
100         return this._logs;
101     }
102
103     set logs(value)
104     {
105         this._logs = value;
106         this._dataDidChange();
107     }
108
109     get errors()
110     {
111         return this._errors;
112     }
113
114     set errors(value)
115     {
116         this._errors = value;
117         this._dataDidChange();
118     }
119
120     get issues()
121     {
122         return this._issues;
123     }
124
125     set issues(value)
126     {
127         this._issues = value;
128         this._dataDidChange();
129     }
130
131     // Private
132
133     _dataDidChange()
134     {
135         this.dispatchEventToListeners(WI.DefaultDashboard.Event.DataDidChange);
136     }
137
138     _mainResourceDidChange(event)
139     {
140         console.assert(event.target instanceof WI.Frame);
141
142         if (!event.target.isMainFrame())
143             return;
144
145         if (!this._transitioningPageTarget) {
146             this._time = 0;
147             this._resourcesCount = 1;
148             this._resourcesSize = WI.networkManager.mainFrame.mainResource.size || 0;
149         }
150
151         // We should only track resource sizes on fresh loads.
152         if (this._waitingForFirstMainResourceToStartTrackingSize) {
153             this._waitingForFirstMainResourceToStartTrackingSize = false;
154             WI.Resource.addEventListener(WI.Resource.Event.SizeDidChange, this._resourceSizeDidChange, this);
155         }
156
157         this._dataDidChange();
158
159         if (!this._transitioningPageTarget)
160             this._startUpdatingTime();
161
162         if (this._transitioningPageTarget)
163             this._transitioningPageTarget = false;
164     }
165
166     _handleTimelineCapturingStateChanged(event)
167     {
168         if (WI.timelineManager.isCapturing())
169             return;
170
171         // If recording stops, we should stop the timer if it hasn't stopped already.
172         this._stopUpdatingTime();
173     }
174
175     _resourceWasAdded(event)
176     {
177         ++this.resourcesCount;
178     }
179
180     _frameWasAdded(event)
181     {
182         ++this.resourcesCount;
183     }
184
185     _resourceSizeDidChange(event)
186     {
187         if (event.target.urlComponents.scheme === "data")
188             return;
189
190         let delta = event.target.size - event.data.previousSize;
191         console.assert(!isNaN(delta), "Resource size change should never be NaN.");
192         this.resourcesSize += delta;
193     }
194
195     _startUpdatingTime()
196     {
197         this._stopUpdatingTime();
198
199         this.time = 0;
200
201         this._timelineBaseTime = Date.now();
202         this._timeIntervalDelay = 50;
203         this._timeIntervalIdentifier = setInterval(this._updateTime.bind(this), this._timeIntervalDelay);
204     }
205
206     _stopUpdatingTime()
207     {
208         if (!this._timeIntervalIdentifier)
209             return;
210
211         clearInterval(this._timeIntervalIdentifier);
212         this._timeIntervalIdentifier = undefined;
213     }
214
215     _updateTime()
216     {
217         var duration = Date.now() - this._timelineBaseTime;
218
219         var timeIntervalDelay = this._timeIntervalDelay;
220         if (duration >= 1000) // 1 second
221             timeIntervalDelay = 100;
222         else if (duration >= 60000) // 60 seconds
223             timeIntervalDelay = 1000;
224         else if (duration >= 3600000) // 1 minute
225             timeIntervalDelay = 10000;
226
227         if (timeIntervalDelay !== this._timeIntervalDelay) {
228             this._timeIntervalDelay = timeIntervalDelay;
229
230             clearInterval(this._timeIntervalIdentifier);
231             this._timeIntervalIdentifier = setInterval(this._updateTime.bind(this), this._timeIntervalDelay);
232         }
233
234         var mainFrame = WI.networkManager.mainFrame;
235         var mainFrameStartTime = mainFrame.mainResource.firstTimestamp;
236         var mainFrameLoadEventTime = mainFrame.loadEventTimestamp;
237
238         if (isNaN(mainFrameStartTime) || isNaN(mainFrameLoadEventTime)) {
239             this.time = duration / 1000;
240             return;
241         }
242
243         this.time = mainFrameLoadEventTime - mainFrameStartTime;
244
245         this._stopUpdatingTime();
246     }
247
248     _consoleMessageAdded(event)
249     {
250         var message = event.data.message;
251         this._lastConsoleMessageType = message.level;
252         this._incrementConsoleMessageType(message.level, message.repeatCount);
253     }
254
255     _consoleMessageWasRepeated(event)
256     {
257         this._incrementConsoleMessageType(this._lastConsoleMessageType, 1);
258     }
259
260     _incrementConsoleMessageType(type, increment)
261     {
262         switch (type) {
263         case WI.ConsoleMessage.MessageLevel.Log:
264         case WI.ConsoleMessage.MessageLevel.Info:
265         case WI.ConsoleMessage.MessageLevel.Debug:
266             this.logs += increment;
267             break;
268         case WI.ConsoleMessage.MessageLevel.Warning:
269             this.issues += increment;
270             break;
271         case WI.ConsoleMessage.MessageLevel.Error:
272             this.errors += increment;
273             break;
274         }
275     }
276
277     _consoleWasCleared(event)
278     {
279         this._logs = 0;
280         this._issues = 0;
281         this._errors = 0;
282         this._dataDidChange();
283     }
284
285     _transitionPageTarget()
286     {
287         this._transitioningPageTarget = true;
288
289         this._time = 0;
290         this._resourcesCount = 0;
291         this._resourcesSize = 0;
292
293         this._dataDidChange();
294     }
295 };
296
297 WI.DefaultDashboard.Event = {
298     DataDidChange: "default-dashboard-data-did-change"
299 };