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