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 "Document.h"
37 #include "DocumentLoadTiming.h"
38 #include "DocumentLoader.h"
39 #include "DocumentTiming.h"
40 #include "Frame.h"
41 #include "ResourceLoadTiming.h"
42 #include "ResourceResponse.h"
43 #include <wtf/CurrentTime.h>
44 
45 namespace WebCore {
46 
toIntegerMilliseconds(double seconds)47 static unsigned long long toIntegerMilliseconds(double seconds)
48 {
49     ASSERT(seconds >= 0);
50     return static_cast<unsigned long long>(seconds * 1000.0);
51 }
52 
getPossiblySkewedTimeInKnownRange(double skewedTime,double lowerBound,double upperBound)53 static double getPossiblySkewedTimeInKnownRange(double skewedTime, double lowerBound, double upperBound)
54 {
55 #if PLATFORM(CHROMIUM)
56     // The chromium port's currentTime() implementation only syncs with the
57     // system clock every 60 seconds. So it is possible for timing marks
58     // collected in different threads or processes to have a small skew.
59     // FIXME: It may be possible to add a currentTimeFromSystemTime() method
60     // that eliminates the skew.
61     if (skewedTime <= lowerBound)
62         return lowerBound;
63 
64     if (upperBound <= 0.0)
65         upperBound = currentTime();
66 
67     if (skewedTime >= upperBound)
68         return upperBound;
69 #else
70     ASSERT_UNUSED(lowerBound, skewedTime >= lowerBound);
71     ASSERT_UNUSED(upperBound, skewedTime <= upperBound);
72 #endif
73 
74     return skewedTime;
75 }
76 
PerformanceTiming(Frame * frame)77 PerformanceTiming::PerformanceTiming(Frame* frame)
78     : m_frame(frame)
79 {
80 }
81 
frame() const82 Frame* PerformanceTiming::frame() const
83 {
84     return m_frame;
85 }
86 
disconnectFrame()87 void PerformanceTiming::disconnectFrame()
88 {
89     m_frame = 0;
90 }
91 
navigationStart() const92 unsigned long long PerformanceTiming::navigationStart() const
93 {
94     DocumentLoadTiming* timing = documentLoadTiming();
95     if (!timing)
96         return 0;
97 
98     if (timing->hasCrossOriginRedirect)
99         return 0;
100 
101     return toIntegerMilliseconds(timing->navigationStart);
102 }
103 
unloadEventStart() const104 unsigned long long PerformanceTiming::unloadEventStart() const
105 {
106     DocumentLoadTiming* timing = documentLoadTiming();
107     if (!timing)
108         return 0;
109 
110     if (timing->hasCrossOriginRedirect || !timing->hasSameOriginAsPreviousDocument)
111         return 0;
112 
113     return toIntegerMilliseconds(timing->unloadEventStart);
114 }
115 
unloadEventEnd() const116 unsigned long long PerformanceTiming::unloadEventEnd() const
117 {
118     DocumentLoadTiming* timing = documentLoadTiming();
119     if (!timing)
120         return 0;
121 
122     if (timing->hasCrossOriginRedirect || !timing->hasSameOriginAsPreviousDocument)
123         return 0;
124 
125     return toIntegerMilliseconds(timing->unloadEventEnd);
126 }
127 
redirectStart() const128 unsigned long long PerformanceTiming::redirectStart() const
129 {
130     DocumentLoadTiming* timing = documentLoadTiming();
131     if (!timing)
132         return 0;
133 
134     if (timing->hasCrossOriginRedirect)
135         return 0;
136 
137     return toIntegerMilliseconds(timing->redirectStart);
138 }
139 
redirectEnd() const140 unsigned long long PerformanceTiming::redirectEnd() const
141 {
142     DocumentLoadTiming* timing = documentLoadTiming();
143     if (!timing)
144         return 0;
145 
146     if (timing->hasCrossOriginRedirect)
147         return 0;
148 
149     return toIntegerMilliseconds(timing->redirectEnd);
150 }
151 
fetchStart() const152 unsigned long long PerformanceTiming::fetchStart() const
153 {
154     DocumentLoadTiming* timing = documentLoadTiming();
155     if (!timing)
156         return 0;
157 
158     return toIntegerMilliseconds(timing->fetchStart);
159 }
160 
domainLookupStart() const161 unsigned long long PerformanceTiming::domainLookupStart() const
162 {
163     ResourceLoadTiming* timing = resourceLoadTiming();
164     if (!timing)
165         return fetchStart();
166 
167     // This will be -1 when a DNS request is not performed.
168     // Rather than exposing a special value that indicates no DNS, we "backfill" with fetchStart.
169     int dnsStart = timing->dnsStart;
170     if (dnsStart < 0)
171         return fetchStart();
172 
173     return resourceLoadTimeRelativeToAbsolute(dnsStart);
174 }
175 
domainLookupEnd() const176 unsigned long long PerformanceTiming::domainLookupEnd() const
177 {
178     ResourceLoadTiming* timing = resourceLoadTiming();
179     if (!timing)
180         return domainLookupStart();
181 
182     // This will be -1 when a DNS request is not performed.
183     // Rather than exposing a special value that indicates no DNS, we "backfill" with domainLookupStart.
184     int dnsEnd = timing->dnsEnd;
185     if (dnsEnd < 0)
186         return domainLookupStart();
187 
188     return resourceLoadTimeRelativeToAbsolute(dnsEnd);
189 }
190 
connectStart() const191 unsigned long long PerformanceTiming::connectStart() const
192 {
193     DocumentLoader* loader = documentLoader();
194     if (!loader)
195         return domainLookupEnd();
196 
197     ResourceLoadTiming* timing = loader->response().resourceLoadTiming();
198     if (!timing)
199         return domainLookupEnd();
200 
201     // connectStart will be -1 when a network request is not made.
202     // Rather than exposing a special value that indicates no new connection, we "backfill" with domainLookupEnd.
203     int connectStart = timing->connectStart;
204     if (connectStart < 0 || loader->response().connectionReused())
205         return domainLookupEnd();
206 
207     // ResourceLoadTiming's connect phase includes DNS, however Navigation Timing's
208     // connect phase should not. So if there is DNS time, trim it from the start.
209     if (timing->dnsEnd >= 0 && timing->dnsEnd > connectStart)
210         connectStart = timing->dnsEnd;
211 
212     return resourceLoadTimeRelativeToAbsolute(connectStart);
213 }
214 
connectEnd() const215 unsigned long long PerformanceTiming::connectEnd() const
216 {
217     DocumentLoader* loader = documentLoader();
218     if (!loader)
219         return connectStart();
220 
221     ResourceLoadTiming* timing = loader->response().resourceLoadTiming();
222     if (!timing)
223         return connectStart();
224 
225     // connectEnd will be -1 when a network request is not made.
226     // Rather than exposing a special value that indicates no new connection, we "backfill" with connectStart.
227     int connectEnd = timing->connectEnd;
228     if (connectEnd < 0 || loader->response().connectionReused())
229         return connectStart();
230 
231     return resourceLoadTimeRelativeToAbsolute(connectEnd);
232 }
233 
secureConnectionStart() const234 unsigned long long PerformanceTiming::secureConnectionStart() const
235 {
236     DocumentLoader* loader = documentLoader();
237     if (!loader)
238         return 0;
239 
240     ResourceLoadTiming* timing = loader->response().resourceLoadTiming();
241     if (!timing)
242         return 0;
243 
244     int sslStart = timing->sslStart;
245     if (sslStart < 0)
246         return 0;
247 
248     return resourceLoadTimeRelativeToAbsolute(sslStart);
249 }
250 
requestStart() const251 unsigned long long PerformanceTiming::requestStart() const
252 {
253     ResourceLoadTiming* timing = resourceLoadTiming();
254     if (!timing)
255         return connectEnd();
256 
257     ASSERT(timing->sendStart >= 0);
258     return resourceLoadTimeRelativeToAbsolute(timing->sendStart);
259 }
260 
responseStart() const261 unsigned long long PerformanceTiming::responseStart() const
262 {
263     ResourceLoadTiming* timing = resourceLoadTiming();
264     if (!timing)
265         return requestStart();
266 
267     // FIXME: Response start needs to be the time of the first received byte.
268     // However, the ResourceLoadTiming API currently only supports the time
269     // the last header byte was received. For many responses with reasonable
270     // sized cookies, the HTTP headers fit into a single packet so this time
271     // is basically equivalent. But for some responses, particularly those with
272     // headers larger than a single packet, this time will be too late.
273     ASSERT(timing->receiveHeadersEnd >= 0);
274     return resourceLoadTimeRelativeToAbsolute(timing->receiveHeadersEnd);
275 }
276 
responseEnd() const277 unsigned long long PerformanceTiming::responseEnd() const
278 {
279     DocumentLoadTiming* timing = documentLoadTiming();
280     if (!timing)
281         return 0;
282 
283     return toIntegerMilliseconds(timing->responseEnd);
284 }
285 
domLoading() const286 unsigned long long PerformanceTiming::domLoading() const
287 {
288     const DocumentTiming* timing = documentTiming();
289     if (!timing)
290         return fetchStart();
291 
292     return toIntegerMilliseconds(timing->domLoading);
293 }
294 
domInteractive() const295 unsigned long long PerformanceTiming::domInteractive() const
296 {
297     const DocumentTiming* timing = documentTiming();
298     if (!timing)
299         return 0;
300 
301     return toIntegerMilliseconds(timing->domInteractive);
302 }
303 
domContentLoadedEventStart() const304 unsigned long long PerformanceTiming::domContentLoadedEventStart() const
305 {
306     const DocumentTiming* timing = documentTiming();
307     if (!timing)
308         return 0;
309 
310     return toIntegerMilliseconds(timing->domContentLoadedEventStart);
311 }
312 
domContentLoadedEventEnd() const313 unsigned long long PerformanceTiming::domContentLoadedEventEnd() const
314 {
315     const DocumentTiming* timing = documentTiming();
316     if (!timing)
317         return 0;
318 
319     return toIntegerMilliseconds(timing->domContentLoadedEventEnd);
320 }
321 
domComplete() const322 unsigned long long PerformanceTiming::domComplete() const
323 {
324     const DocumentTiming* timing = documentTiming();
325     if (!timing)
326         return 0;
327 
328     return toIntegerMilliseconds(timing->domComplete);
329 }
330 
loadEventStart() const331 unsigned long long PerformanceTiming::loadEventStart() const
332 {
333     DocumentLoadTiming* timing = documentLoadTiming();
334     if (!timing)
335         return 0;
336 
337     return toIntegerMilliseconds(timing->loadEventStart);
338 }
339 
loadEventEnd() const340 unsigned long long PerformanceTiming::loadEventEnd() const
341 {
342     DocumentLoadTiming* timing = documentLoadTiming();
343     if (!timing)
344         return 0;
345 
346     return toIntegerMilliseconds(timing->loadEventEnd);
347 }
348 
documentLoader() const349 DocumentLoader* PerformanceTiming::documentLoader() const
350 {
351     if (!m_frame)
352         return 0;
353 
354     return m_frame->loader()->documentLoader();
355 }
356 
documentTiming() const357 const DocumentTiming* PerformanceTiming::documentTiming() const
358 {
359     if (!m_frame)
360         return 0;
361 
362     Document* document = m_frame->document();
363     if (!document)
364         return 0;
365 
366     return document->timing();
367 }
368 
documentLoadTiming() const369 DocumentLoadTiming* PerformanceTiming::documentLoadTiming() const
370 {
371     DocumentLoader* loader = documentLoader();
372     if (!loader)
373         return 0;
374 
375     return loader->timing();
376 }
377 
resourceLoadTiming() const378 ResourceLoadTiming* PerformanceTiming::resourceLoadTiming() const
379 {
380     DocumentLoader* loader = documentLoader();
381     if (!loader)
382         return 0;
383 
384     return loader->response().resourceLoadTiming();
385 }
386 
resourceLoadTimeRelativeToAbsolute(int relativeSeconds) const387 unsigned long long PerformanceTiming::resourceLoadTimeRelativeToAbsolute(int relativeSeconds) const
388 {
389     ASSERT(relativeSeconds >= 0);
390     ResourceLoadTiming* resourceTiming = resourceLoadTiming();
391     ASSERT(resourceTiming);
392     DocumentLoadTiming* documentTiming = documentLoadTiming();
393     ASSERT(documentTiming);
394 
395     // The ResourceLoadTiming API's requestTime is the base time to which all
396     // other marks are relative. So to get an absolute time, we must add it to
397     // the relative marks.
398     //
399     // Since ResourceLoadTimings came from the network platform layer, we must
400     // check them for skew because they may be from another thread/process.
401     double baseTime = getPossiblySkewedTimeInKnownRange(resourceTiming->requestTime, documentTiming->fetchStart, documentTiming->responseEnd);
402     return toIntegerMilliseconds(baseTime) + relativeSeconds;
403 }
404 
405 } // namespace WebCore
406 
407 #endif // ENABLE(WEB_TIMING)
408