michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsPerformance.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsITimedChannel.h" michael@0: #include "nsDOMNavigationTiming.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsIURI.h" michael@0: #include "PerformanceEntry.h" michael@0: #include "PerformanceResourceTiming.h" michael@0: #include "mozilla/dom/PerformanceBinding.h" michael@0: #include "mozilla/dom/PerformanceTimingBinding.h" michael@0: #include "mozilla/dom/PerformanceNavigationBinding.h" michael@0: #include "mozilla/TimeStamp.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(nsPerformanceTiming, mPerformance) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsPerformanceTiming, AddRef) michael@0: NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsPerformanceTiming, Release) michael@0: michael@0: nsPerformanceTiming::nsPerformanceTiming(nsPerformance* aPerformance, michael@0: nsITimedChannel* aChannel, michael@0: nsIHttpChannel* aHttpChannel, michael@0: DOMHighResTimeStamp aZeroTime) michael@0: : mPerformance(aPerformance), michael@0: mChannel(aChannel), michael@0: mFetchStart(0.0), michael@0: mZeroTime(aZeroTime), michael@0: mReportCrossOriginResources(true) michael@0: { michael@0: MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); michael@0: SetIsDOMBinding(); michael@0: michael@0: if (!nsContentUtils::IsPerformanceTimingEnabled()) { michael@0: mZeroTime = 0; michael@0: } michael@0: michael@0: // The aHttpChannel argument is null if this nsPerformanceTiming object michael@0: // is being used for the navigation timing (document) and has a non-null michael@0: // value for the resource timing (any resources within the page). michael@0: if (aHttpChannel) { michael@0: CheckRedirectCrossOrigin(aHttpChannel); michael@0: } michael@0: } michael@0: michael@0: nsPerformanceTiming::~nsPerformanceTiming() michael@0: { michael@0: } michael@0: michael@0: DOMHighResTimeStamp michael@0: nsPerformanceTiming::FetchStartHighRes() michael@0: { michael@0: if (!mFetchStart) { michael@0: if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { michael@0: return mZeroTime; michael@0: } michael@0: TimeStamp stamp; michael@0: mChannel->GetAsyncOpen(&stamp); michael@0: MOZ_ASSERT(!stamp.IsNull(), "The fetch start time stamp should always be " michael@0: "valid if the performance timing is enabled"); michael@0: mFetchStart = (!stamp.IsNull()) michael@0: ? TimeStampToDOMHighRes(stamp) michael@0: : 0.0; michael@0: } michael@0: return mFetchStart; michael@0: } michael@0: michael@0: DOMTimeMilliSec michael@0: nsPerformanceTiming::FetchStart() michael@0: { michael@0: return static_cast(FetchStartHighRes()); michael@0: } michael@0: michael@0: // This method will implement the timing allow check algorithm michael@0: // http://w3c-test.org/webperf/specs/ResourceTiming/#timing-allow-check michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=936814 michael@0: void michael@0: nsPerformanceTiming::CheckRedirectCrossOrigin(nsIHttpChannel* aResourceChannel) michael@0: { michael@0: if (!IsInitialized()) { michael@0: return; michael@0: } michael@0: uint16_t redirectCount; michael@0: mChannel->GetRedirectCount(&redirectCount); michael@0: if (redirectCount == 0) { michael@0: return; michael@0: } michael@0: nsCOMPtr resourceURI, referrerURI; michael@0: aResourceChannel->GetReferrer(getter_AddRefs(referrerURI)); michael@0: aResourceChannel->GetURI(getter_AddRefs(resourceURI)); michael@0: nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); michael@0: nsresult rv = ssm->CheckSameOriginURI(resourceURI, referrerURI, false); michael@0: if (!NS_SUCCEEDED(rv)) { michael@0: mReportCrossOriginResources = false; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsPerformanceTiming::IsSameOriginAsReferral() const michael@0: { michael@0: return mReportCrossOriginResources; michael@0: } michael@0: michael@0: uint16_t michael@0: nsPerformanceTiming::GetRedirectCount() const michael@0: { michael@0: if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { michael@0: return 0; michael@0: } michael@0: bool sameOrigin; michael@0: mChannel->GetAllRedirectsSameOrigin(&sameOrigin); michael@0: if (!sameOrigin) { michael@0: return 0; michael@0: } michael@0: uint16_t redirectCount; michael@0: mChannel->GetRedirectCount(&redirectCount); michael@0: return redirectCount; michael@0: } michael@0: michael@0: /** michael@0: * RedirectStartHighRes() is used by both the navigation timing and the michael@0: * resource timing. Since, navigation timing and resource timing check and michael@0: * interpret cross-domain redirects in a different manner, michael@0: * RedirectStartHighRes() will make no checks for cross-domain redirect. michael@0: * It's up to the consumers of this method (nsPerformanceTiming::RedirectStart() michael@0: * and PerformanceResourceTiming::RedirectStart() to make such verifications. michael@0: * michael@0: * @return a valid timing if the Performance Timing is enabled michael@0: */ michael@0: DOMHighResTimeStamp michael@0: nsPerformanceTiming::RedirectStartHighRes() michael@0: { michael@0: if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { michael@0: return mZeroTime; michael@0: } michael@0: mozilla::TimeStamp stamp; michael@0: mChannel->GetRedirectStart(&stamp); michael@0: return TimeStampToDOMHighResOrFetchStart(stamp); michael@0: } michael@0: michael@0: DOMTimeMilliSec michael@0: nsPerformanceTiming::RedirectStart() michael@0: { michael@0: if (!IsInitialized()) { michael@0: return mZeroTime; michael@0: } michael@0: // We have to check if all the redirect URIs had the same origin (since there michael@0: // is no check in RedirectStartHighRes()) michael@0: bool sameOrigin; michael@0: mChannel->GetAllRedirectsSameOrigin(&sameOrigin); michael@0: if (sameOrigin) { michael@0: return static_cast(RedirectStartHighRes()); michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: /** michael@0: * RedirectEndHighRes() is used by both the navigation timing and the resource michael@0: * timing. Since, navigation timing and resource timing check and interpret michael@0: * cross-domain redirects in a different manner, RedirectEndHighRes() will make michael@0: * no checks for cross-domain redirect. It's up to the consumers of this method michael@0: * (nsPerformanceTiming::RedirectEnd() and michael@0: * PerformanceResourceTiming::RedirectEnd() to make such verifications. michael@0: * michael@0: * @return a valid timing if the Performance Timing is enabled michael@0: */ michael@0: DOMHighResTimeStamp michael@0: nsPerformanceTiming::RedirectEndHighRes() michael@0: { michael@0: if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { michael@0: return mZeroTime; michael@0: } michael@0: mozilla::TimeStamp stamp; michael@0: mChannel->GetRedirectEnd(&stamp); michael@0: return TimeStampToDOMHighResOrFetchStart(stamp); michael@0: } michael@0: michael@0: DOMTimeMilliSec michael@0: nsPerformanceTiming::RedirectEnd() michael@0: { michael@0: if (!IsInitialized()) { michael@0: return mZeroTime; michael@0: } michael@0: // We have to check if all the redirect URIs had the same origin (since there michael@0: // is no check in RedirectEndHighRes()) michael@0: bool sameOrigin; michael@0: mChannel->GetAllRedirectsSameOrigin(&sameOrigin); michael@0: if (sameOrigin) { michael@0: return static_cast(RedirectEndHighRes()); michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: DOMHighResTimeStamp michael@0: nsPerformanceTiming::DomainLookupStartHighRes() michael@0: { michael@0: if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { michael@0: return mZeroTime; michael@0: } michael@0: mozilla::TimeStamp stamp; michael@0: mChannel->GetDomainLookupStart(&stamp); michael@0: return TimeStampToDOMHighResOrFetchStart(stamp); michael@0: } michael@0: michael@0: DOMTimeMilliSec michael@0: nsPerformanceTiming::DomainLookupStart() michael@0: { michael@0: return static_cast(DomainLookupStartHighRes()); michael@0: } michael@0: michael@0: DOMHighResTimeStamp michael@0: nsPerformanceTiming::DomainLookupEndHighRes() michael@0: { michael@0: if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { michael@0: return mZeroTime; michael@0: } michael@0: mozilla::TimeStamp stamp; michael@0: mChannel->GetDomainLookupEnd(&stamp); michael@0: return TimeStampToDOMHighResOrFetchStart(stamp); michael@0: } michael@0: michael@0: DOMTimeMilliSec michael@0: nsPerformanceTiming::DomainLookupEnd() michael@0: { michael@0: return static_cast(DomainLookupEndHighRes()); michael@0: } michael@0: michael@0: DOMHighResTimeStamp michael@0: nsPerformanceTiming::ConnectStartHighRes() michael@0: { michael@0: if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { michael@0: return mZeroTime; michael@0: } michael@0: mozilla::TimeStamp stamp; michael@0: mChannel->GetConnectStart(&stamp); michael@0: return TimeStampToDOMHighResOrFetchStart(stamp); michael@0: } michael@0: michael@0: DOMTimeMilliSec michael@0: nsPerformanceTiming::ConnectStart() michael@0: { michael@0: return static_cast(ConnectStartHighRes()); michael@0: } michael@0: michael@0: DOMHighResTimeStamp michael@0: nsPerformanceTiming::ConnectEndHighRes() michael@0: { michael@0: if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { michael@0: return mZeroTime; michael@0: } michael@0: mozilla::TimeStamp stamp; michael@0: mChannel->GetConnectEnd(&stamp); michael@0: return TimeStampToDOMHighResOrFetchStart(stamp); michael@0: } michael@0: michael@0: DOMTimeMilliSec michael@0: nsPerformanceTiming::ConnectEnd() michael@0: { michael@0: return static_cast(ConnectEndHighRes()); michael@0: } michael@0: michael@0: DOMHighResTimeStamp michael@0: nsPerformanceTiming::RequestStartHighRes() michael@0: { michael@0: if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { michael@0: return mZeroTime; michael@0: } michael@0: mozilla::TimeStamp stamp; michael@0: mChannel->GetRequestStart(&stamp); michael@0: return TimeStampToDOMHighResOrFetchStart(stamp); michael@0: } michael@0: michael@0: DOMTimeMilliSec michael@0: nsPerformanceTiming::RequestStart() michael@0: { michael@0: return static_cast(RequestStartHighRes()); michael@0: } michael@0: michael@0: DOMHighResTimeStamp michael@0: nsPerformanceTiming::ResponseStartHighRes() michael@0: { michael@0: if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { michael@0: return mZeroTime; michael@0: } michael@0: mozilla::TimeStamp stamp; michael@0: mChannel->GetResponseStart(&stamp); michael@0: mozilla::TimeStamp cacheStamp; michael@0: mChannel->GetCacheReadStart(&cacheStamp); michael@0: if (stamp.IsNull() || (!cacheStamp.IsNull() && cacheStamp < stamp)) { michael@0: stamp = cacheStamp; michael@0: } michael@0: return TimeStampToDOMHighResOrFetchStart(stamp); michael@0: } michael@0: michael@0: DOMTimeMilliSec michael@0: nsPerformanceTiming::ResponseStart() michael@0: { michael@0: return static_cast(ResponseStartHighRes()); michael@0: } michael@0: michael@0: DOMHighResTimeStamp michael@0: nsPerformanceTiming::ResponseEndHighRes() michael@0: { michael@0: if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { michael@0: return mZeroTime; michael@0: } michael@0: mozilla::TimeStamp stamp; michael@0: mChannel->GetResponseEnd(&stamp); michael@0: mozilla::TimeStamp cacheStamp; michael@0: mChannel->GetCacheReadEnd(&cacheStamp); michael@0: if (stamp.IsNull() || (!cacheStamp.IsNull() && cacheStamp < stamp)) { michael@0: stamp = cacheStamp; michael@0: } michael@0: return TimeStampToDOMHighResOrFetchStart(stamp); michael@0: } michael@0: michael@0: DOMTimeMilliSec michael@0: nsPerformanceTiming::ResponseEnd() michael@0: { michael@0: return static_cast(ResponseEndHighRes()); michael@0: } michael@0: michael@0: bool michael@0: nsPerformanceTiming::IsInitialized() const michael@0: { michael@0: return !!mChannel; michael@0: } michael@0: michael@0: JSObject* michael@0: nsPerformanceTiming::WrapObject(JSContext *cx) michael@0: { michael@0: return dom::PerformanceTimingBinding::Wrap(cx, this); michael@0: } michael@0: michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(nsPerformanceNavigation, mPerformance) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsPerformanceNavigation, AddRef) michael@0: NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsPerformanceNavigation, Release) michael@0: michael@0: nsPerformanceNavigation::nsPerformanceNavigation(nsPerformance* aPerformance) michael@0: : mPerformance(aPerformance) michael@0: { michael@0: MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: nsPerformanceNavigation::~nsPerformanceNavigation() michael@0: { michael@0: } michael@0: michael@0: JSObject* michael@0: nsPerformanceNavigation::WrapObject(JSContext *cx) michael@0: { michael@0: return dom::PerformanceNavigationBinding::Wrap(cx, this); michael@0: } michael@0: michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_5(nsPerformance, michael@0: mWindow, mTiming, michael@0: mNavigation, mEntries, michael@0: mParentPerformance) michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPerformance) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPerformance) michael@0: michael@0: nsPerformance::nsPerformance(nsIDOMWindow* aWindow, michael@0: nsDOMNavigationTiming* aDOMTiming, michael@0: nsITimedChannel* aChannel, michael@0: nsPerformance* aParentPerformance) michael@0: : mWindow(aWindow), michael@0: mDOMTiming(aDOMTiming), michael@0: mChannel(aChannel), michael@0: mParentPerformance(aParentPerformance), michael@0: mBufferSizeSet(kDefaultBufferSize), michael@0: mPrimaryBufferSize(kDefaultBufferSize) michael@0: { michael@0: MOZ_ASSERT(aWindow, "Parent window object should be provided"); michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: nsPerformance::~nsPerformance() michael@0: { michael@0: } michael@0: michael@0: // QueryInterface implementation for nsPerformance michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPerformance) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: michael@0: nsPerformanceTiming* michael@0: nsPerformance::Timing() michael@0: { michael@0: if (!mTiming) { michael@0: // For navigation timing, the third argument (an nsIHtttpChannel) is null michael@0: // since the cross-domain redirect were already checked. michael@0: // The last argument (zero time) for performance.timing is the navigation michael@0: // start value. michael@0: mTiming = new nsPerformanceTiming(this, mChannel, nullptr, michael@0: mDOMTiming->GetNavigationStart()); michael@0: } michael@0: return mTiming; michael@0: } michael@0: michael@0: nsPerformanceNavigation* michael@0: nsPerformance::Navigation() michael@0: { michael@0: if (!mNavigation) { michael@0: mNavigation = new nsPerformanceNavigation(this); michael@0: } michael@0: return mNavigation; michael@0: } michael@0: michael@0: DOMHighResTimeStamp michael@0: nsPerformance::Now() michael@0: { michael@0: return GetDOMTiming()->TimeStampToDOMHighRes(mozilla::TimeStamp::Now()); michael@0: } michael@0: michael@0: JSObject* michael@0: nsPerformance::WrapObject(JSContext *cx) michael@0: { michael@0: return dom::PerformanceBinding::Wrap(cx, this); michael@0: } michael@0: michael@0: void michael@0: nsPerformance::GetEntries(nsTArray >& retval) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: retval.Clear(); michael@0: uint32_t count = mEntries.Length(); michael@0: if (count > mPrimaryBufferSize) { michael@0: count = mPrimaryBufferSize; michael@0: } michael@0: retval.AppendElements(mEntries.Elements(), count); michael@0: } michael@0: michael@0: void michael@0: nsPerformance::GetEntriesByType(const nsAString& entryType, michael@0: nsTArray >& retval) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: retval.Clear(); michael@0: uint32_t count = mEntries.Length(); michael@0: for (uint32_t i = 0 ; i < count && i < mPrimaryBufferSize ; i++) { michael@0: if (mEntries[i]->GetEntryType().Equals(entryType)) { michael@0: retval.AppendElement(mEntries[i]); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsPerformance::GetEntriesByName(const nsAString& name, michael@0: const mozilla::dom::Optional& entryType, michael@0: nsTArray >& retval) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: retval.Clear(); michael@0: uint32_t count = mEntries.Length(); michael@0: for (uint32_t i = 0 ; i < count && i < mPrimaryBufferSize ; i++) { michael@0: if (mEntries[i]->GetName().Equals(name) && michael@0: (!entryType.WasPassed() || michael@0: mEntries[i]->GetEntryType().Equals(entryType.Value()))) { michael@0: retval.AppendElement(mEntries[i]); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsPerformance::ClearResourceTimings() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mPrimaryBufferSize = mBufferSizeSet; michael@0: mEntries.Clear(); michael@0: } michael@0: michael@0: void michael@0: nsPerformance::SetResourceTimingBufferSize(uint64_t maxSize) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mBufferSizeSet = maxSize; michael@0: if (mBufferSizeSet < mEntries.Length()) { michael@0: // call onresourcetimingbufferfull michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=936813 michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * An entry should be added only after the resource is loaded. michael@0: * This method is not thread safe and can only be called on the main thread. michael@0: */ michael@0: void michael@0: nsPerformance::AddEntry(nsIHttpChannel* channel, michael@0: nsITimedChannel* timedChannel) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: // Check if resource timing is prefed off. michael@0: if (!nsContentUtils::IsResourceTimingEnabled()) { michael@0: return; michael@0: } michael@0: if (channel && timedChannel) { michael@0: nsAutoCString name; michael@0: nsAutoString initiatorType; michael@0: nsCOMPtr originalURI; michael@0: michael@0: timedChannel->GetInitiatorType(initiatorType); michael@0: michael@0: // According to the spec, "The name attribute must return the resolved URL michael@0: // of the requested resource. This attribute must not change even if the michael@0: // fetch redirected to a different URL." michael@0: channel->GetOriginalURI(getter_AddRefs(originalURI)); michael@0: originalURI->GetSpec(name); michael@0: NS_ConvertUTF8toUTF16 entryName(name); michael@0: michael@0: // The nsITimedChannel argument will be used to gather all the timings. michael@0: // The nsIHttpChannel argument will be used to check if any cross-origin michael@0: // redirects occurred. michael@0: // The last argument is the "zero time" (offset). Since we don't want michael@0: // any offset for the resource timing, this will be set to "0" - the michael@0: // resource timing returns a relative timing (no offset). michael@0: nsRefPtr performanceTiming = michael@0: new nsPerformanceTiming(this, timedChannel, channel, michael@0: 0); michael@0: michael@0: // The PerformanceResourceTiming object will use the nsPerformanceTiming michael@0: // object to get all the required timings. michael@0: nsRefPtr performanceEntry = michael@0: new dom::PerformanceResourceTiming(performanceTiming, this); michael@0: michael@0: performanceEntry->SetName(entryName); michael@0: performanceEntry->SetEntryType(NS_LITERAL_STRING("resource")); michael@0: // If the initiator type had no valid value, then set it to the default michael@0: // ("other") value. michael@0: if (initiatorType.IsEmpty()) { michael@0: initiatorType = NS_LITERAL_STRING("other"); michael@0: } michael@0: performanceEntry->SetInitiatorType(initiatorType); michael@0: michael@0: mEntries.InsertElementSorted(performanceEntry, michael@0: PerformanceEntryComparator()); michael@0: if (mEntries.Length() > mPrimaryBufferSize) { michael@0: // call onresourcetimingbufferfull michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=936813 michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsPerformance::PerformanceEntryComparator::Equals( michael@0: const PerformanceEntry* aElem1, michael@0: const PerformanceEntry* aElem2) const michael@0: { michael@0: NS_ABORT_IF_FALSE(aElem1 && aElem2, michael@0: "Trying to compare null performance entries"); michael@0: return aElem1->StartTime() == aElem2->StartTime(); michael@0: } michael@0: michael@0: bool michael@0: nsPerformance::PerformanceEntryComparator::LessThan( michael@0: const PerformanceEntry* aElem1, michael@0: const PerformanceEntry* aElem2) const michael@0: { michael@0: NS_ABORT_IF_FALSE(aElem1 && aElem2, michael@0: "Trying to compare null performance entries"); michael@0: return aElem1->StartTime() < aElem2->StartTime(); michael@0: }