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 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 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 76 PerformanceTiming::PerformanceTiming(Frame* frame) 77 : m_frame(frame) 78 { 79 } 80 81 Frame* PerformanceTiming::frame() const 82 { 83 return m_frame; 84 } 85 86 void PerformanceTiming::disconnectFrame() 87 { 88 m_frame = 0; 89 } 90 91 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 100 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 112 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 124 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 136 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 148 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 157 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 172 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 187 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 211 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 230 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 247 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 257 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 273 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 282 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 291 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 300 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 309 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 318 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 327 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 336 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 345 DocumentLoader* PerformanceTiming::documentLoader() const 346 { 347 if (!m_frame) 348 return 0; 349 350 return m_frame->loader()->documentLoader(); 351 } 352 353 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 365 DocumentLoadTiming* PerformanceTiming::documentLoadTiming() const 366 { 367 DocumentLoader* loader = documentLoader(); 368 if (!loader) 369 return 0; 370 371 return loader->timing(); 372 } 373 374 ResourceLoadTiming* PerformanceTiming::resourceLoadTiming() const 375 { 376 DocumentLoader* loader = documentLoader(); 377 if (!loader) 378 return 0; 379 380 return loader->response().resourceLoadTiming(); 381 } 382 383 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