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
44namespace WebCore {
45
46static unsigned long long toIntegerMilliseconds(double seconds)
47{
48    ASSERT(seconds >= 0);
49    return static_cast<unsigned long long>(seconds * 1000.0);
50}
51
52static 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
76PerformanceTiming::PerformanceTiming(Frame* frame)
77    : m_frame(frame)
78{
79}
80
81Frame* PerformanceTiming::frame() const
82{
83    return m_frame;
84}
85
86void PerformanceTiming::disconnectFrame()
87{
88    m_frame = 0;
89}
90
91unsigned long long PerformanceTiming::navigationStart() const
92{
93    DocumentLoadTiming* timing = documentLoadTiming();
94    if (!timing)
95        return 0;
96
97    return toIntegerMilliseconds(timing->navigationStart);
98}
99
100unsigned 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
112unsigned 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
124unsigned 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
136unsigned 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
148unsigned long long PerformanceTiming::fetchStart() const
149{
150    DocumentLoadTiming* timing = documentLoadTiming();
151    if (!timing)
152        return 0;
153
154    return toIntegerMilliseconds(timing->fetchStart);
155}
156
157unsigned 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
172unsigned 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
187unsigned 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
211unsigned 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
230unsigned 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
247unsigned 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
257unsigned 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
273unsigned long long PerformanceTiming::responseEnd() const
274{
275    DocumentLoadTiming* timing = documentLoadTiming();
276    if (!timing)
277        return 0;
278
279    return toIntegerMilliseconds(timing->responseEnd);
280}
281
282unsigned 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
291unsigned 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
300unsigned 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
309unsigned 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
318unsigned 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
327unsigned long long PerformanceTiming::loadEventStart() const
328{
329    DocumentLoadTiming* timing = documentLoadTiming();
330    if (!timing)
331        return 0;
332
333    return toIntegerMilliseconds(timing->loadEventStart);
334}
335
336unsigned long long PerformanceTiming::loadEventEnd() const
337{
338    DocumentLoadTiming* timing = documentLoadTiming();
339    if (!timing)
340        return 0;
341
342    return toIntegerMilliseconds(timing->loadEventEnd);
343}
344
345DocumentLoader* PerformanceTiming::documentLoader() const
346{
347    if (!m_frame)
348        return 0;
349
350    return m_frame->loader()->documentLoader();
351}
352
353const 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
365DocumentLoadTiming* PerformanceTiming::documentLoadTiming() const
366{
367    DocumentLoader* loader = documentLoader();
368    if (!loader)
369        return 0;
370
371    return loader->timing();
372}
373
374ResourceLoadTiming* PerformanceTiming::resourceLoadTiming() const
375{
376    DocumentLoader* loader = documentLoader();
377    if (!loader)
378        return 0;
379
380    return loader->response().resourceLoadTiming();
381}
382
383unsigned 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