1 /*
2 * Copyright (C) 2010 Google 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 are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "config.h"
32 #include "PerformanceTiming.h"
33
34 #if ENABLE(WEB_TIMING)
35
36 #include "DocumentLoadTiming.h"
37 #include "DocumentLoader.h"
38 #include "DocumentTiming.h"
39 #include "Frame.h"
40 #include "ResourceLoadTiming.h"
41 #include "ResourceResponse.h"
42 #include <wtf/CurrentTime.h>
43
44 namespace WebCore {
45
toIntegerMilliseconds(double seconds)46 static unsigned long long toIntegerMilliseconds(double seconds)
47 {
48 ASSERT(seconds >= 0);
49 return static_cast<unsigned long long>(seconds * 1000.0);
50 }
51
getPossiblySkewedTimeInKnownRange(double skewedTime,double lowerBound,double upperBound)52 static double getPossiblySkewedTimeInKnownRange(double skewedTime, double lowerBound, double upperBound)
53 {
54 #if PLATFORM(CHROMIUM)
55 // The chromium port's currentTime() implementation only syncs with the
56 // system clock every 60 seconds. So it is possible for timing marks
57 // collected in different threads or processes to have a small skew.
58 // FIXME: It may be possible to add a currentTimeFromSystemTime() method
59 // that eliminates the skew.
60 if (skewedTime <= lowerBound)
61 return lowerBound;
62
63 if (upperBound <= 0.0)
64 upperBound = currentTime();
65
66 if (skewedTime >= upperBound)
67 return upperBound;
68 #else
69 ASSERT_UNUSED(lowerBound, skewedTime >= lowerBound);
70 ASSERT_UNUSED(upperBound, skewedTime <= upperBound);
71 #endif
72
73 return skewedTime;
74 }
75
PerformanceTiming(Frame * frame)76 PerformanceTiming::PerformanceTiming(Frame* frame)
77 : m_frame(frame)
78 {
79 }
80
frame() const81 Frame* PerformanceTiming::frame() const
82 {
83 return m_frame;
84 }
85
disconnectFrame()86 void PerformanceTiming::disconnectFrame()
87 {
88 m_frame = 0;
89 }
90
navigationStart() const91 unsigned long long PerformanceTiming::navigationStart() const
92 {
93 DocumentLoadTiming* timing = documentLoadTiming();
94 if (!timing)
95 return 0;
96
97 return toIntegerMilliseconds(timing->navigationStart);
98 }
99
unloadEventStart() const100 unsigned long long PerformanceTiming::unloadEventStart() const
101 {
102 DocumentLoadTiming* timing = documentLoadTiming();
103 if (!timing)
104 return 0;
105
106 if (timing->hasCrossOriginRedirect || !timing->hasSameOriginAsPreviousDocument)
107 return 0;
108
109 return toIntegerMilliseconds(timing->unloadEventStart);
110 }
111
unloadEventEnd() const112 unsigned long long PerformanceTiming::unloadEventEnd() const
113 {
114 DocumentLoadTiming* timing = documentLoadTiming();
115 if (!timing)
116 return 0;
117
118 if (timing->hasCrossOriginRedirect || !timing->hasSameOriginAsPreviousDocument)
119 return 0;
120
121 return toIntegerMilliseconds(timing->unloadEventEnd);
122 }
123
redirectStart() const124 unsigned long long PerformanceTiming::redirectStart() const
125 {
126 DocumentLoadTiming* timing = documentLoadTiming();
127 if (!timing)
128 return 0;
129
130 if (timing->hasCrossOriginRedirect)
131 return 0;
132
133 return toIntegerMilliseconds(timing->redirectStart);
134 }
135
redirectEnd() const136 unsigned long long PerformanceTiming::redirectEnd() const
137 {
138 DocumentLoadTiming* timing = documentLoadTiming();
139 if (!timing)
140 return 0;
141
142 if (timing->hasCrossOriginRedirect)
143 return 0;
144
145 return toIntegerMilliseconds(timing->redirectEnd);
146 }
147
fetchStart() const148 unsigned long long PerformanceTiming::fetchStart() const
149 {
150 DocumentLoadTiming* timing = documentLoadTiming();
151 if (!timing)
152 return 0;
153
154 return toIntegerMilliseconds(timing->fetchStart);
155 }
156
domainLookupStart() const157 unsigned long long PerformanceTiming::domainLookupStart() const
158 {
159 ResourceLoadTiming* timing = resourceLoadTiming();
160 if (!timing)
161 return fetchStart();
162
163 // This will be -1 when a DNS request is not performed.
164 // Rather than exposing a special value that indicates no DNS, we "backfill" with fetchStart.
165 int dnsStart = timing->dnsStart;
166 if (dnsStart < 0)
167 return fetchStart();
168
169 return resourceLoadTimeRelativeToAbsolute(dnsStart);
170 }
171
domainLookupEnd() const172 unsigned long long PerformanceTiming::domainLookupEnd() const
173 {
174 ResourceLoadTiming* timing = resourceLoadTiming();
175 if (!timing)
176 return domainLookupStart();
177
178 // This will be -1 when a DNS request is not performed.
179 // Rather than exposing a special value that indicates no DNS, we "backfill" with domainLookupStart.
180 int dnsEnd = timing->dnsEnd;
181 if (dnsEnd < 0)
182 return domainLookupStart();
183
184 return resourceLoadTimeRelativeToAbsolute(dnsEnd);
185 }
186
connectStart() const187 unsigned long long PerformanceTiming::connectStart() const
188 {
189 DocumentLoader* loader = documentLoader();
190 if (!loader)
191 return domainLookupEnd();
192
193 ResourceLoadTiming* timing = loader->response().resourceLoadTiming();
194 if (!timing)
195 return domainLookupEnd();
196
197 // connectStart will be -1 when a network request is not made.
198 // Rather than exposing a special value that indicates no new connection, we "backfill" with domainLookupEnd.
199 int connectStart = timing->connectStart;
200 if (connectStart < 0 || loader->response().connectionReused())
201 return domainLookupEnd();
202
203 // ResourceLoadTiming's connect phase includes DNS, however Navigation Timing's
204 // connect phase should not. So if there is DNS time, trim it from the start.
205 if (timing->dnsEnd >= 0 && timing->dnsEnd > connectStart)
206 connectStart = timing->dnsEnd;
207
208 return resourceLoadTimeRelativeToAbsolute(connectStart);
209 }
210
connectEnd() const211 unsigned long long PerformanceTiming::connectEnd() const
212 {
213 DocumentLoader* loader = documentLoader();
214 if (!loader)
215 return connectStart();
216
217 ResourceLoadTiming* timing = loader->response().resourceLoadTiming();
218 if (!timing)
219 return connectStart();
220
221 // connectEnd will be -1 when a network request is not made.
222 // Rather than exposing a special value that indicates no new connection, we "backfill" with connectStart.
223 int connectEnd = timing->connectEnd;
224 if (connectEnd < 0 || loader->response().connectionReused())
225 return connectStart();
226
227 return resourceLoadTimeRelativeToAbsolute(connectEnd);
228 }
229
secureConnectionStart() const230 unsigned long long PerformanceTiming::secureConnectionStart() const
231 {
232 DocumentLoader* loader = documentLoader();
233 if (!loader)
234 return 0;
235
236 ResourceLoadTiming* timing = loader->response().resourceLoadTiming();
237 if (!timing)
238 return 0;
239
240 int sslStart = timing->sslStart;
241 if (sslStart < 0)
242 return 0;
243
244 return resourceLoadTimeRelativeToAbsolute(sslStart);
245 }
246
requestStart() const247 unsigned long long PerformanceTiming::requestStart() const
248 {
249 ResourceLoadTiming* timing = resourceLoadTiming();
250 if (!timing)
251 return connectEnd();
252
253 ASSERT(timing->sendStart >= 0);
254 return resourceLoadTimeRelativeToAbsolute(timing->sendStart);
255 }
256
responseStart() const257 unsigned long long PerformanceTiming::responseStart() const
258 {
259 ResourceLoadTiming* timing = resourceLoadTiming();
260 if (!timing)
261 return requestStart();
262
263 // FIXME: Response start needs to be the time of the first received byte.
264 // However, the ResourceLoadTiming API currently only supports the time
265 // the last header byte was received. For many responses with reasonable
266 // sized cookies, the HTTP headers fit into a single packet so this time
267 // is basically equivalent. But for some responses, particularly those with
268 // headers larger than a single packet, this time will be too late.
269 ASSERT(timing->receiveHeadersEnd >= 0);
270 return resourceLoadTimeRelativeToAbsolute(timing->receiveHeadersEnd);
271 }
272
responseEnd() const273 unsigned long long PerformanceTiming::responseEnd() const
274 {
275 DocumentLoadTiming* timing = documentLoadTiming();
276 if (!timing)
277 return 0;
278
279 return toIntegerMilliseconds(timing->responseEnd);
280 }
281
domLoading() const282 unsigned long long PerformanceTiming::domLoading() const
283 {
284 const DocumentTiming* timing = documentTiming();
285 if (!timing)
286 return fetchStart();
287
288 return toIntegerMilliseconds(timing->domLoading);
289 }
290
domInteractive() const291 unsigned long long PerformanceTiming::domInteractive() const
292 {
293 const DocumentTiming* timing = documentTiming();
294 if (!timing)
295 return 0;
296
297 return toIntegerMilliseconds(timing->domInteractive);
298 }
299
domContentLoadedEventStart() const300 unsigned long long PerformanceTiming::domContentLoadedEventStart() const
301 {
302 const DocumentTiming* timing = documentTiming();
303 if (!timing)
304 return 0;
305
306 return toIntegerMilliseconds(timing->domContentLoadedEventStart);
307 }
308
domContentLoadedEventEnd() const309 unsigned long long PerformanceTiming::domContentLoadedEventEnd() const
310 {
311 const DocumentTiming* timing = documentTiming();
312 if (!timing)
313 return 0;
314
315 return toIntegerMilliseconds(timing->domContentLoadedEventEnd);
316 }
317
domComplete() const318 unsigned long long PerformanceTiming::domComplete() const
319 {
320 const DocumentTiming* timing = documentTiming();
321 if (!timing)
322 return 0;
323
324 return toIntegerMilliseconds(timing->domComplete);
325 }
326
loadEventStart() const327 unsigned long long PerformanceTiming::loadEventStart() const
328 {
329 DocumentLoadTiming* timing = documentLoadTiming();
330 if (!timing)
331 return 0;
332
333 return toIntegerMilliseconds(timing->loadEventStart);
334 }
335
loadEventEnd() const336 unsigned long long PerformanceTiming::loadEventEnd() const
337 {
338 DocumentLoadTiming* timing = documentLoadTiming();
339 if (!timing)
340 return 0;
341
342 return toIntegerMilliseconds(timing->loadEventEnd);
343 }
344
documentLoader() const345 DocumentLoader* PerformanceTiming::documentLoader() const
346 {
347 if (!m_frame)
348 return 0;
349
350 return m_frame->loader()->documentLoader();
351 }
352
documentTiming() const353 const DocumentTiming* PerformanceTiming::documentTiming() const
354 {
355 if (!m_frame)
356 return 0;
357
358 Document* document = m_frame->document();
359 if (!document)
360 return 0;
361
362 return document->timing();
363 }
364
documentLoadTiming() const365 DocumentLoadTiming* PerformanceTiming::documentLoadTiming() const
366 {
367 DocumentLoader* loader = documentLoader();
368 if (!loader)
369 return 0;
370
371 return loader->timing();
372 }
373
resourceLoadTiming() const374 ResourceLoadTiming* PerformanceTiming::resourceLoadTiming() const
375 {
376 DocumentLoader* loader = documentLoader();
377 if (!loader)
378 return 0;
379
380 return loader->response().resourceLoadTiming();
381 }
382
resourceLoadTimeRelativeToAbsolute(int relativeSeconds) const383 unsigned long long PerformanceTiming::resourceLoadTimeRelativeToAbsolute(int relativeSeconds) const
384 {
385 ASSERT(relativeSeconds >= 0);
386 ResourceLoadTiming* resourceTiming = resourceLoadTiming();
387 ASSERT(resourceTiming);
388 DocumentLoadTiming* documentTiming = documentLoadTiming();
389 ASSERT(documentTiming);
390
391 // The ResourceLoadTiming API's requestTime is the base time to which all
392 // other marks are relative. So to get an absolute time, we must add it to
393 // the relative marks.
394 //
395 // Since ResourceLoadTimings came from the network platform layer, we must
396 // check them for skew because they may be from another thread/process.
397 double baseTime = getPossiblySkewedTimeInKnownRange(resourceTiming->requestTime, documentTiming->fetchStart, documentTiming->responseEnd);
398 return toIntegerMilliseconds(baseTime) + relativeSeconds;
399 }
400
401 } // namespace WebCore
402
403 #endif // ENABLE(WEB_TIMING)
404