/* * Copyright (C) 2010 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "PerformanceTiming.h" #if ENABLE(WEB_TIMING) #include "DocumentLoadTiming.h" #include "DocumentLoader.h" #include "DocumentTiming.h" #include "Frame.h" #include "ResourceLoadTiming.h" #include "ResourceResponse.h" #include namespace WebCore { static unsigned long long toIntegerMilliseconds(double seconds) { ASSERT(seconds >= 0); return static_cast(seconds * 1000.0); } static double getPossiblySkewedTimeInKnownRange(double skewedTime, double lowerBound, double upperBound) { #if PLATFORM(CHROMIUM) // The chromium port's currentTime() implementation only syncs with the // system clock every 60 seconds. So it is possible for timing marks // collected in different threads or processes to have a small skew. // FIXME: It may be possible to add a currentTimeFromSystemTime() method // that eliminates the skew. if (skewedTime <= lowerBound) return lowerBound; if (upperBound <= 0.0) upperBound = currentTime(); if (skewedTime >= upperBound) return upperBound; #else ASSERT_UNUSED(lowerBound, skewedTime >= lowerBound); ASSERT_UNUSED(upperBound, skewedTime <= upperBound); #endif return skewedTime; } PerformanceTiming::PerformanceTiming(Frame* frame) : m_frame(frame) { } Frame* PerformanceTiming::frame() const { return m_frame; } void PerformanceTiming::disconnectFrame() { m_frame = 0; } unsigned long long PerformanceTiming::navigationStart() const { DocumentLoadTiming* timing = documentLoadTiming(); if (!timing) return 0; return toIntegerMilliseconds(timing->navigationStart); } unsigned long long PerformanceTiming::unloadEventStart() const { DocumentLoadTiming* timing = documentLoadTiming(); if (!timing) return 0; if (timing->hasCrossOriginRedirect || !timing->hasSameOriginAsPreviousDocument) return 0; return toIntegerMilliseconds(timing->unloadEventStart); } unsigned long long PerformanceTiming::unloadEventEnd() const { DocumentLoadTiming* timing = documentLoadTiming(); if (!timing) return 0; if (timing->hasCrossOriginRedirect || !timing->hasSameOriginAsPreviousDocument) return 0; return toIntegerMilliseconds(timing->unloadEventEnd); } unsigned long long PerformanceTiming::redirectStart() const { DocumentLoadTiming* timing = documentLoadTiming(); if (!timing) return 0; if (timing->hasCrossOriginRedirect) return 0; return toIntegerMilliseconds(timing->redirectStart); } unsigned long long PerformanceTiming::redirectEnd() const { DocumentLoadTiming* timing = documentLoadTiming(); if (!timing) return 0; if (timing->hasCrossOriginRedirect) return 0; return toIntegerMilliseconds(timing->redirectEnd); } unsigned long long PerformanceTiming::fetchStart() const { DocumentLoadTiming* timing = documentLoadTiming(); if (!timing) return 0; return toIntegerMilliseconds(timing->fetchStart); } unsigned long long PerformanceTiming::domainLookupStart() const { ResourceLoadTiming* timing = resourceLoadTiming(); if (!timing) return fetchStart(); // This will be -1 when a DNS request is not performed. // Rather than exposing a special value that indicates no DNS, we "backfill" with fetchStart. int dnsStart = timing->dnsStart; if (dnsStart < 0) return fetchStart(); return resourceLoadTimeRelativeToAbsolute(dnsStart); } unsigned long long PerformanceTiming::domainLookupEnd() const { ResourceLoadTiming* timing = resourceLoadTiming(); if (!timing) return domainLookupStart(); // This will be -1 when a DNS request is not performed. // Rather than exposing a special value that indicates no DNS, we "backfill" with domainLookupStart. int dnsEnd = timing->dnsEnd; if (dnsEnd < 0) return domainLookupStart(); return resourceLoadTimeRelativeToAbsolute(dnsEnd); } unsigned long long PerformanceTiming::connectStart() const { DocumentLoader* loader = documentLoader(); if (!loader) return domainLookupEnd(); ResourceLoadTiming* timing = loader->response().resourceLoadTiming(); if (!timing) return domainLookupEnd(); // connectStart will be -1 when a network request is not made. // Rather than exposing a special value that indicates no new connection, we "backfill" with domainLookupEnd. int connectStart = timing->connectStart; if (connectStart < 0 || loader->response().connectionReused()) return domainLookupEnd(); // ResourceLoadTiming's connect phase includes DNS, however Navigation Timing's // connect phase should not. So if there is DNS time, trim it from the start. if (timing->dnsEnd >= 0 && timing->dnsEnd > connectStart) connectStart = timing->dnsEnd; return resourceLoadTimeRelativeToAbsolute(connectStart); } unsigned long long PerformanceTiming::connectEnd() const { DocumentLoader* loader = documentLoader(); if (!loader) return connectStart(); ResourceLoadTiming* timing = loader->response().resourceLoadTiming(); if (!timing) return connectStart(); // connectEnd will be -1 when a network request is not made. // Rather than exposing a special value that indicates no new connection, we "backfill" with connectStart. int connectEnd = timing->connectEnd; if (connectEnd < 0 || loader->response().connectionReused()) return connectStart(); return resourceLoadTimeRelativeToAbsolute(connectEnd); } unsigned long long PerformanceTiming::secureConnectionStart() const { DocumentLoader* loader = documentLoader(); if (!loader) return 0; ResourceLoadTiming* timing = loader->response().resourceLoadTiming(); if (!timing) return 0; int sslStart = timing->sslStart; if (sslStart < 0) return 0; return resourceLoadTimeRelativeToAbsolute(sslStart); } unsigned long long PerformanceTiming::requestStart() const { ResourceLoadTiming* timing = resourceLoadTiming(); if (!timing) return connectEnd(); ASSERT(timing->sendStart >= 0); return resourceLoadTimeRelativeToAbsolute(timing->sendStart); } unsigned long long PerformanceTiming::responseStart() const { ResourceLoadTiming* timing = resourceLoadTiming(); if (!timing) return requestStart(); // FIXME: Response start needs to be the time of the first received byte. // However, the ResourceLoadTiming API currently only supports the time // the last header byte was received. For many responses with reasonable // sized cookies, the HTTP headers fit into a single packet so this time // is basically equivalent. But for some responses, particularly those with // headers larger than a single packet, this time will be too late. ASSERT(timing->receiveHeadersEnd >= 0); return resourceLoadTimeRelativeToAbsolute(timing->receiveHeadersEnd); } unsigned long long PerformanceTiming::responseEnd() const { DocumentLoadTiming* timing = documentLoadTiming(); if (!timing) return 0; return toIntegerMilliseconds(timing->responseEnd); } unsigned long long PerformanceTiming::domLoading() const { const DocumentTiming* timing = documentTiming(); if (!timing) return fetchStart(); return toIntegerMilliseconds(timing->domLoading); } unsigned long long PerformanceTiming::domInteractive() const { const DocumentTiming* timing = documentTiming(); if (!timing) return 0; return toIntegerMilliseconds(timing->domInteractive); } unsigned long long PerformanceTiming::domContentLoadedEventStart() const { const DocumentTiming* timing = documentTiming(); if (!timing) return 0; return toIntegerMilliseconds(timing->domContentLoadedEventStart); } unsigned long long PerformanceTiming::domContentLoadedEventEnd() const { const DocumentTiming* timing = documentTiming(); if (!timing) return 0; return toIntegerMilliseconds(timing->domContentLoadedEventEnd); } unsigned long long PerformanceTiming::domComplete() const { const DocumentTiming* timing = documentTiming(); if (!timing) return 0; return toIntegerMilliseconds(timing->domComplete); } unsigned long long PerformanceTiming::loadEventStart() const { DocumentLoadTiming* timing = documentLoadTiming(); if (!timing) return 0; return toIntegerMilliseconds(timing->loadEventStart); } unsigned long long PerformanceTiming::loadEventEnd() const { DocumentLoadTiming* timing = documentLoadTiming(); if (!timing) return 0; return toIntegerMilliseconds(timing->loadEventEnd); } DocumentLoader* PerformanceTiming::documentLoader() const { if (!m_frame) return 0; return m_frame->loader()->documentLoader(); } const DocumentTiming* PerformanceTiming::documentTiming() const { if (!m_frame) return 0; Document* document = m_frame->document(); if (!document) return 0; return document->timing(); } DocumentLoadTiming* PerformanceTiming::documentLoadTiming() const { DocumentLoader* loader = documentLoader(); if (!loader) return 0; return loader->timing(); } ResourceLoadTiming* PerformanceTiming::resourceLoadTiming() const { DocumentLoader* loader = documentLoader(); if (!loader) return 0; return loader->response().resourceLoadTiming(); } unsigned long long PerformanceTiming::resourceLoadTimeRelativeToAbsolute(int relativeSeconds) const { ASSERT(relativeSeconds >= 0); ResourceLoadTiming* resourceTiming = resourceLoadTiming(); ASSERT(resourceTiming); DocumentLoadTiming* documentTiming = documentLoadTiming(); ASSERT(documentTiming); // The ResourceLoadTiming API's requestTime is the base time to which all // other marks are relative. So to get an absolute time, we must add it to // the relative marks. // // Since ResourceLoadTimings came from the network platform layer, we must // check them for skew because they may be from another thread/process. double baseTime = getPossiblySkewedTimeInKnownRange(resourceTiming->requestTime, documentTiming->fetchStart, documentTiming->responseEnd); return toIntegerMilliseconds(baseTime) + relativeSeconds; } } // namespace WebCore #endif // ENABLE(WEB_TIMING)