From 864ffca9e75ba51242ef70a53661acb07f737c79 Mon Sep 17 00:00:00 2001 From: Adrian Lungu Date: Tue, 15 Oct 2013 18:35:44 -0700 Subject: [PATCH] Bug 822480 - Add in the Resource Timing API. r=honzab, r=jst --- content/base/public/nsContentUtils.h | 10 + content/base/src/nsContentUtils.cpp | 6 + content/base/src/nsDocument.cpp | 1 + content/base/src/nsImageLoadingContent.cpp | 4 + content/base/src/nsObjectLoadingContent.cpp | 6 + content/base/src/nsScriptLoader.cpp | 7 + content/base/src/nsXMLHttpRequest.cpp | 7 + docshell/base/nsDocShell.cpp | 11 +- dom/base/PerformanceEntry.cpp | 37 ++ dom/base/PerformanceEntry.h | 82 ++++ dom/base/PerformanceResourceTiming.cpp | 48 ++ dom/base/PerformanceResourceTiming.h | 133 ++++++ dom/base/moz.build | 4 + dom/base/nsDOMNavigationTiming.cpp | 92 +--- dom/base/nsDOMNavigationTiming.h | 31 +- dom/base/nsGlobalWindow.cpp | 20 +- dom/base/nsPerformance.cpp | 435 ++++++++++++++++-- dom/base/nsPerformance.h | 192 ++++++-- dom/tests/mochitest/general/mochitest.ini | 3 + .../general/resource_timing_iframe.html | 48 ++ .../general/resource_timing_main_test.html | 258 +++++++++++ dom/tests/mochitest/general/test-data.json | 1 + dom/tests/mochitest/general/test-data2.json | 1 + .../mochitest/general/test_interfaces.html | 4 + .../general/test_resource_timing.html | 35 ++ dom/webidl/Performance.webidl | 15 + dom/webidl/PerformanceEntry.webidl | 19 + dom/webidl/PerformanceResourceTiming.webidl | 33 ++ dom/webidl/moz.build | 2 + dom/xbl/nsXBLResourceLoader.cpp | 2 +- .../browser/webBrowser/nsContextMenuInfo.cpp | 2 +- image/src/imgLoader.cpp | 8 + image/src/imgLoader.h | 1 + image/src/imgRequestProxy.h | 1 + layout/generic/nsImageFrame.cpp | 1 + layout/style/ImageLoader.cpp | 1 + layout/style/Loader.cpp | 10 + layout/xul/nsImageBoxFrame.cpp | 2 +- layout/xul/tree/nsTreeBodyFrame.cpp | 1 + modules/libpref/src/init/all.js | 3 + netwerk/base/public/nsITimedChannel.idl | 18 +- netwerk/protocol/http/HttpBaseChannel.cpp | 45 +- netwerk/protocol/http/HttpBaseChannel.h | 19 + netwerk/protocol/http/nsHttpChannel.cpp | 124 +++++ netwerk/protocol/http/nsHttpChannel.h | 2 + widget/cocoa/OSXNotificationCenter.mm | 3 +- widget/cocoa/nsMenuItemIconX.mm | 2 +- 47 files changed, 1581 insertions(+), 209 deletions(-) create mode 100644 dom/base/PerformanceEntry.cpp create mode 100644 dom/base/PerformanceEntry.h create mode 100644 dom/base/PerformanceResourceTiming.cpp create mode 100644 dom/base/PerformanceResourceTiming.h create mode 100644 dom/tests/mochitest/general/resource_timing_iframe.html create mode 100644 dom/tests/mochitest/general/resource_timing_main_test.html create mode 100644 dom/tests/mochitest/general/test-data.json create mode 100644 dom/tests/mochitest/general/test-data2.json create mode 100644 dom/tests/mochitest/general/test_resource_timing.html create mode 100644 dom/webidl/PerformanceEntry.webidl create mode 100644 dom/webidl/PerformanceResourceTiming.webidl diff --git a/content/base/public/nsContentUtils.h b/content/base/public/nsContentUtils.h index 4998b6619866..accb84763ba0 100644 --- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -654,6 +654,7 @@ public: nsIURI* aReferrer, imgINotificationObserver* aObserver, int32_t aLoadFlags, + const nsAString& initiatorType, imgRequestProxy** aRequest); /** @@ -1847,6 +1848,14 @@ public: return sIsPerformanceTimingEnabled; } + /* + * Returns true if the performance timing APIs are enabled. + */ + static bool IsResourceTimingEnabled() + { + return sIsResourceTimingEnabled; + } + /** * Returns true if the doc tree branch which contains aDoc contains any * plugins which we don't control event dispatch for, i.e. do any plugins @@ -2221,6 +2230,7 @@ private: static uint32_t sHandlingInputTimeout; static bool sIsIdleObserverAPIEnabled; static bool sIsPerformanceTimingEnabled; + static bool sIsResourceTimingEnabled; static nsHtml5StringParser* sHTMLFragmentParser; static nsIParser* sXMLFragmentParser; diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index d480a1d3089f..3e9a02d0f1f9 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -238,6 +238,7 @@ bool nsContentUtils::sTrustedFullScreenOnly = true; bool nsContentUtils::sFullscreenApiIsContentOnly = false; bool nsContentUtils::sIsIdleObserverAPIEnabled = false; bool nsContentUtils::sIsPerformanceTimingEnabled = false; +bool nsContentUtils::sIsResourceTimingEnabled = false; uint32_t nsContentUtils::sHandlingInputTimeout = 1000; @@ -439,6 +440,9 @@ nsContentUtils::Init() Preferences::AddBoolVarCache(&sIsPerformanceTimingEnabled, "dom.enable_performance", true); + Preferences::AddBoolVarCache(&sIsResourceTimingEnabled, + "dom.enable_resource_timing", true); + Preferences::AddUintVarCache(&sHandlingInputTimeout, "dom.event.handling-user-input-time-limit", 1000); @@ -2711,6 +2715,7 @@ nsresult nsContentUtils::LoadImage(nsIURI* aURI, nsIDocument* aLoadingDocument, nsIPrincipal* aLoadingPrincipal, nsIURI* aReferrer, imgINotificationObserver* aObserver, int32_t aLoadFlags, + const nsAString& initiatorType, imgRequestProxy** aRequest) { NS_PRECONDITION(aURI, "Must have a URI"); @@ -2760,6 +2765,7 @@ nsContentUtils::LoadImage(nsIURI* aURI, nsIDocument* aLoadingDocument, aLoadFlags, /* load flags */ nullptr, /* cache key */ channelPolicy, /* CSP info */ + initiatorType, /* the load initiator */ aRequest); } diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index f849f90764a4..31e457a6e662 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -9274,6 +9274,7 @@ nsDocument::MaybePreLoadImage(nsIURI* uri, const nsAString &aCrossOriginAttr) mDocumentURI, // uri of document used as referrer nullptr, // no observer loadFlags, + NS_LITERAL_STRING("img"), getter_AddRefs(request)); // Pin image-reference to avoid evicting it from the img-cache before diff --git a/content/base/src/nsImageLoadingContent.cpp b/content/base/src/nsImageLoadingContent.cpp index cc326245599c..b13f31a2e1ce 100644 --- a/content/base/src/nsImageLoadingContent.cpp +++ b/content/base/src/nsImageLoadingContent.cpp @@ -822,12 +822,16 @@ nsImageLoadingContent::LoadImage(nsIURI* aNewURI, // Not blocked. Do the load. nsRefPtr& req = PrepareNextRequest(); + nsCOMPtr content = + do_QueryInterface(static_cast(this)); nsresult rv; rv = nsContentUtils::LoadImage(aNewURI, aDocument, aDocument->NodePrincipal(), aDocument->GetDocumentURI(), this, loadFlags, + content->LocalName(), getter_AddRefs(req)); + if (NS_SUCCEEDED(rv)) { TrackImage(req); ResetAnimationIfNeeded(); diff --git a/content/base/src/nsObjectLoadingContent.cpp b/content/base/src/nsObjectLoadingContent.cpp index d43cc2c000fb..7cfa1ede4e52 100644 --- a/content/base/src/nsObjectLoadingContent.cpp +++ b/content/base/src/nsObjectLoadingContent.cpp @@ -2323,6 +2323,12 @@ nsObjectLoadingContent::OpenChannel() nsCOMPtr httpChan(do_QueryInterface(chan)); if (httpChan) { httpChan->SetReferrer(doc->GetDocumentURI()); + + // Set the initiator type + nsCOMPtr timedChannel(do_QueryInterface(httpChan)); + if (timedChannel) { + timedChannel->SetInitiatorType(thisContent->LocalName()); + } } // Set up the channel's principal and such, like nsDocShell::DoURILoad does. diff --git a/content/base/src/nsScriptLoader.cpp b/content/base/src/nsScriptLoader.cpp index 694c0c5c8584..2356a7eda4a0 100644 --- a/content/base/src/nsScriptLoader.cpp +++ b/content/base/src/nsScriptLoader.cpp @@ -28,6 +28,7 @@ #include "nsContentPolicyUtils.h" #include "nsIHttpChannel.h" #include "nsIHttpChannelInternal.h" +#include "nsITimedChannel.h" #include "nsIScriptElement.h" #include "nsIDOMHTMLScriptElement.h" #include "nsIDocShell.h" @@ -338,6 +339,12 @@ nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType, mozilla::net::SeerLearn(aRequest->mURI, mDocument->GetDocumentURI(), nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, loadContext); + // Set the initiator type + nsCOMPtr timedChannel(do_QueryInterface(httpChannel)); + if (timedChannel) { + timedChannel->SetInitiatorType(NS_LITERAL_STRING("script")); + } + nsCOMPtr loader; rv = NS_NewStreamLoader(getter_AddRefs(loader), this); NS_ENSURE_SUCCESS(rv, rv); diff --git a/content/base/src/nsXMLHttpRequest.cpp b/content/base/src/nsXMLHttpRequest.cpp index 93440f8b379d..627462fb38a0 100644 --- a/content/base/src/nsXMLHttpRequest.cpp +++ b/content/base/src/nsXMLHttpRequest.cpp @@ -68,6 +68,7 @@ #include "nsFormData.h" #include "nsStreamListenerWrapper.h" #include "xpcjsid.h" +#include "nsITimedChannel.h" #include "nsWrapperCacheInlines.h" @@ -1703,6 +1704,12 @@ nsXMLHttpRequest::Open(const nsACString& inMethod, const nsACString& url, if (httpChannel) { rv = httpChannel->SetRequestMethod(method); NS_ENSURE_SUCCESS(rv, rv); + + // Set the initiator type + nsCOMPtr timedChannel(do_QueryInterface(httpChannel)); + if (timedChannel) { + timedChannel->SetInitiatorType(NS_LITERAL_STRING("xmlhttprequest")); + } } ChangeState(XML_HTTP_REQUEST_OPENED); diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 29d434b94806..f4f311649c53 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -6818,14 +6818,6 @@ nsDocShell::OnRedirectStateChange(nsIChannel* aOldChannel, } } - // On session restore we get a redirect from page to itself. Don't count it. - bool equals = false; - if (mTiming && - !(mLoadType == LOAD_HISTORY && - NS_SUCCEEDED(newURI->Equals(oldURI, &equals)) && equals)) { - mTiming->NotifyRedirect(oldURI, newURI); - } - // Below a URI visit is saved (see AddURIVisit method doc). // The visit chain looks something like: // ... @@ -10033,6 +10025,9 @@ nsDocShell::DoURILoad(nsIURI * aURI, nsCOMPtr timedChannel(do_QueryInterface(channel)); if (timedChannel) { timedChannel->SetTimingEnabled(true); + if (IsFrame()) { + timedChannel->SetInitiatorType(NS_LITERAL_STRING("subdocument")); + } } rv = DoChannelLoad(channel, uriLoader, aBypassClassifier); diff --git a/dom/base/PerformanceEntry.cpp b/dom/base/PerformanceEntry.cpp new file mode 100644 index 000000000000..6c3688da5522 --- /dev/null +++ b/dom/base/PerformanceEntry.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PerformanceEntry.h" +#include "nsIURI.h" +#include "mozilla/dom/PerformanceEntryBinding.h" + +using namespace mozilla::dom; + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(PerformanceEntry, mPerformance) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(PerformanceEntry) +NS_IMPL_CYCLE_COLLECTING_RELEASE(PerformanceEntry) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceEntry) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +PerformanceEntry::PerformanceEntry(nsPerformance* aPerformance) +: mPerformance(aPerformance) +{ + MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); + SetIsDOMBinding(); +} + +PerformanceEntry::~PerformanceEntry() +{ +} + +JSObject* +PerformanceEntry::WrapObject(JSContext* aCx) +{ + return mozilla::dom::PerformanceEntryBinding::Wrap(aCx, this); +} diff --git a/dom/base/PerformanceEntry.h b/dom/base/PerformanceEntry.h new file mode 100644 index 000000000000..32e29dd27093 --- /dev/null +++ b/dom/base/PerformanceEntry.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_PerformanceEntry_h___ +#define mozilla_dom_PerformanceEntry_h___ + +#include "nsPerformance.h" +#include "nsDOMNavigationTiming.h" + +namespace mozilla { +namespace dom { + +// http://www.w3.org/TR/performance-timeline/#performanceentry +class PerformanceEntry : public nsISupports, + public nsWrapperCache +{ +public: + PerformanceEntry(nsPerformance* aPerformance); + virtual ~PerformanceEntry(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PerformanceEntry) + + virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE; + + nsPerformance* GetParentObject() const + { + return mPerformance; + } + + void GetName(nsAString& aName) const + { + aName = mName; + } + + const nsAString& GetName() const + { + return mName; + } + + void SetName(const nsAString& aName) + { + mName = aName; + } + + void GetEntryType(nsAString& aEntryType) const + { + aEntryType = mEntryType; + } + + const nsAString& GetEntryType() + { + return mEntryType; + } + + void SetEntryType(const nsAString& aEntryType) + { + mEntryType = aEntryType; + } + + virtual DOMHighResTimeStamp StartTime() const + { + return 0; + } + + virtual DOMHighResTimeStamp Duration() const + { + return 0; + } + +protected: + nsRefPtr mPerformance; + nsString mName; + nsString mEntryType; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_PerformanceEntry_h___ */ diff --git a/dom/base/PerformanceResourceTiming.cpp b/dom/base/PerformanceResourceTiming.cpp new file mode 100644 index 000000000000..76f63482e754 --- /dev/null +++ b/dom/base/PerformanceResourceTiming.cpp @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PerformanceResourceTiming.h" +#include "mozilla/dom/PerformanceResourceTimingBinding.h" + +using namespace mozilla::dom; + +NS_IMPL_CYCLE_COLLECTION_INHERITED_1(PerformanceResourceTiming, + PerformanceEntry, + mTiming) + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceResourceTiming, + PerformanceEntry) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PerformanceResourceTiming) +NS_INTERFACE_MAP_END_INHERITING(PerformanceEntry) + +NS_IMPL_ADDREF_INHERITED(PerformanceResourceTiming, PerformanceEntry) +NS_IMPL_RELEASE_INHERITED(PerformanceResourceTiming, PerformanceEntry) + +PerformanceResourceTiming::PerformanceResourceTiming(nsPerformanceTiming* aPerformanceTiming, + nsPerformance* aPerformance) +: PerformanceEntry(aPerformance), + mTiming(aPerformanceTiming) +{ + MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); +} + +PerformanceResourceTiming::~PerformanceResourceTiming() +{ +} + +DOMHighResTimeStamp +PerformanceResourceTiming::StartTime() const +{ + DOMHighResTimeStamp startTime = mTiming->RedirectStartHighRes(); + return startTime ? startTime : mTiming->FetchStartHighRes(); +} + +JSObject* +PerformanceResourceTiming::WrapObject(JSContext* aCx) +{ + return PerformanceResourceTimingBinding::Wrap(aCx, this); +} diff --git a/dom/base/PerformanceResourceTiming.h b/dom/base/PerformanceResourceTiming.h new file mode 100644 index 000000000000..b169b4a2862d --- /dev/null +++ b/dom/base/PerformanceResourceTiming.h @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_PerformanceResourceTiming_h___ +#define mozilla_dom_PerformanceResourceTiming_h___ + +#include "nsCOMPtr.h" +#include "nsPerformance.h" +#include "nsIChannel.h" +#include "nsITimedChannel.h" +#include "nsDOMNavigationTiming.h" +#include "PerformanceEntry.h" + +namespace mozilla { +namespace dom { + +// http://www.w3.org/TR/resource-timing/#performanceresourcetiming +class PerformanceResourceTiming MOZ_FINAL : public PerformanceEntry +{ +public: + typedef mozilla::TimeStamp TimeStamp; + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED( + PerformanceResourceTiming, + PerformanceEntry) + + PerformanceResourceTiming(nsPerformanceTiming* aPerformanceTiming, + nsPerformance* aPerformance); + virtual ~PerformanceResourceTiming(); + + virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE; + + + virtual DOMHighResTimeStamp StartTime() const; + + virtual DOMHighResTimeStamp Duration() const + { + return ResponseEnd() - StartTime(); + } + + void GetInitiatorType(nsAString& aInitiatorType) const + { + aInitiatorType = mInitiatorType; + } + + void SetInitiatorType(const nsAString& aInitiatorType) + { + mInitiatorType = aInitiatorType; + } + + DOMHighResTimeStamp FetchStart() const { + return mTiming + ? mTiming->FetchStartHighRes() + : 0; + } + + DOMHighResTimeStamp RedirectStart() const { + // We have to check if all the redirect URIs had the same origin (since + // there is no check in RedirectEndHighRes()) + return mTiming && mTiming->IsSameOriginAsReferral() + ? mTiming->RedirectStartHighRes() + : 0; + } + + DOMHighResTimeStamp RedirectEnd() const { + // We have to check if all the redirect URIs had the same origin (since + // there is no check in RedirectEndHighRes()) + return mTiming && mTiming->IsSameOriginAsReferral() + ? mTiming->RedirectEndHighRes() + : 0; + } + + DOMHighResTimeStamp DomainLookupStart() const { + return mTiming && mTiming->IsSameOriginAsReferral() + ? mTiming->DomainLookupStartHighRes() + : 0; + } + + DOMHighResTimeStamp DomainLookupEnd() const { + return mTiming && mTiming->IsSameOriginAsReferral() + ? mTiming->DomainLookupEndHighRes() + : 0; + } + + DOMHighResTimeStamp ConnectStart() const { + return mTiming && mTiming->IsSameOriginAsReferral() + ? mTiming->ConnectStartHighRes() + : 0; + } + + DOMHighResTimeStamp ConnectEnd() const { + return mTiming && mTiming->IsSameOriginAsReferral() + ? mTiming->ConnectEndHighRes() + : 0; + } + + DOMHighResTimeStamp RequestStart() const { + return mTiming && mTiming->IsSameOriginAsReferral() + ? mTiming->RequestStartHighRes() + : 0; + } + + DOMHighResTimeStamp ResponseStart() const { + return mTiming && mTiming->IsSameOriginAsReferral() + ? mTiming->ResponseStartHighRes() + : 0; + } + + DOMHighResTimeStamp ResponseEnd() const { + return mTiming + ? mTiming->ResponseEndHighRes() + : 0; + } + + DOMHighResTimeStamp SecureConnectionStart() const + { + // This measurement is not available for Navigation Timing either. + // There is a different bug submitted for it. + return 0; + } + +protected: + nsString mInitiatorType; + nsRefPtr mTiming; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_PerformanceResourceTiming_h___ */ diff --git a/dom/base/moz.build b/dom/base/moz.build index 7b83740616cc..efed5169cf77 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -59,6 +59,8 @@ EXPORTS.mozilla.dom += [ 'MessagePort.h', 'MessagePortList.h', 'Navigator.h', + 'PerformanceEntry.h', + 'PerformanceResourceTiming.h', 'ScreenOrientation.h', 'ScriptSettings.h', 'StructuredCloneTags.h', @@ -99,6 +101,8 @@ UNIFIED_SOURCES += [ 'nsWindowMemoryReporter.cpp', 'nsWindowRoot.cpp', 'nsWrapperCache.cpp', + 'PerformanceEntry.cpp', + 'PerformanceResourceTiming.cpp', 'ScriptSettings.cpp', 'URL.cpp', 'URLSearchParams.cpp', diff --git a/dom/base/nsDOMNavigationTiming.cpp b/dom/base/nsDOMNavigationTiming.cpp index 1958db2ea13a..f381024e31b5 100644 --- a/dom/base/nsDOMNavigationTiming.cpp +++ b/dom/base/nsDOMNavigationTiming.cpp @@ -4,6 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsDOMNavigationTiming.h" +#include "nsPerformance.h" #include "nsCOMPtr.h" #include "nsContentUtils.h" #include "nsIScriptSecurityManager.h" @@ -24,11 +25,7 @@ void nsDOMNavigationTiming::Clear() { mNavigationType = mozilla::dom::PerformanceNavigation::TYPE_RESERVED; - mNavigationStart = 0; - mFetchStart = 0; - mRedirectStart = 0; - mRedirectEnd = 0; - mRedirectCount = 0; + mNavigationStartHighRes = 0; mBeforeUnloadStart = 0; mUnloadStart = 0; mUnloadEnd = 0; @@ -39,7 +36,6 @@ nsDOMNavigationTiming::Clear() mDOMContentLoadedEventStart = 0; mDOMContentLoadedEventEnd = 0; mDOMComplete = 0; - mRedirectCheck = NOT_CHECKED; mLoadEventStartSet = false; mLoadEventEndSet = false; @@ -57,17 +53,7 @@ nsDOMNavigationTiming::TimeStampToDOM(mozilla::TimeStamp aStamp) const return 0; } mozilla::TimeDuration duration = aStamp - mNavigationStartTimeStamp; - return mNavigationStart + static_cast(duration.ToMilliseconds()); -} - -DOMTimeMilliSec -nsDOMNavigationTiming::TimeStampToDOMOrFetchStart(mozilla::TimeStamp aStamp) const -{ - if (!aStamp.IsNull()) { - return TimeStampToDOM(aStamp); - } else { - return GetFetchStart(); - } + return GetNavigationStart() + static_cast(duration.ToMilliseconds()); } DOMTimeMilliSec nsDOMNavigationTiming::DurationFromStart(){ @@ -77,36 +63,19 @@ DOMTimeMilliSec nsDOMNavigationTiming::DurationFromStart(){ void nsDOMNavigationTiming::NotifyNavigationStart() { - mNavigationStart = PR_Now() / PR_USEC_PER_MSEC; + mNavigationStartHighRes = (double)PR_Now() / PR_USEC_PER_MSEC; mNavigationStartTimeStamp = mozilla::TimeStamp::Now(); } void nsDOMNavigationTiming::NotifyFetchStart(nsIURI* aURI, nsDOMPerformanceNavigationType aNavigationType) { - mFetchStart = DurationFromStart(); mNavigationType = aNavigationType; // At the unload event time we don't really know the loading uri. // Need it for later check for unload timing access. mLoadedURI = aURI; } -void -nsDOMNavigationTiming::NotifyRedirect(nsIURI* aOldURI, nsIURI* aNewURI) -{ - if (mRedirects.Count() == 0) { - mRedirectStart = mFetchStart; - } - mFetchStart = DurationFromStart(); - mRedirectEnd = mFetchStart; - - // At the unload event time we don't really know the loading uri. - // Need it for later check for unload timing access. - mLoadedURI = aNewURI; - - mRedirects.AppendObject(aOldURI); -} - void nsDOMNavigationTiming::NotifyBeforeUnload() { @@ -150,32 +119,6 @@ nsDOMNavigationTiming::NotifyLoadEventEnd() } } -bool -nsDOMNavigationTiming::ReportRedirects() -{ - if (mRedirectCheck == NOT_CHECKED) { - mRedirectCount = mRedirects.Count(); - if (mRedirects.Count() == 0) { - mRedirectCheck = NO_REDIRECTS; - } else { - mRedirectCheck = CHECK_PASSED; - nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); - for (int i = mRedirects.Count() - 1; i >= 0; --i) { - nsIURI * curr = mRedirects[i]; - nsresult rv = ssm->CheckSameOriginURI(curr, mLoadedURI, false); - if (!NS_SUCCEEDED(rv)) { - mRedirectCheck = CHECK_FAILED; - mRedirectCount = 0; - break; - } - } - // All we need to know is in mRedirectCheck now. Clear history. - mRedirects.Clear(); - } - } - return mRedirectCheck == CHECK_PASSED; -} - void nsDOMNavigationTiming::SetDOMLoadingTimeStamp(nsIURI* aURI, mozilla::TimeStamp aValue) { @@ -236,33 +179,6 @@ nsDOMNavigationTiming::NotifyDOMContentLoadedEnd(nsIURI* aURI) } } -uint16_t -nsDOMNavigationTiming::GetRedirectCount() -{ - if (ReportRedirects()) { - return mRedirectCount; - } - return 0; -} - -DOMTimeMilliSec -nsDOMNavigationTiming::GetRedirectStart() -{ - if (ReportRedirects()) { - return mRedirectStart; - } - return 0; -} - -DOMTimeMilliSec -nsDOMNavigationTiming::GetRedirectEnd() -{ - if (ReportRedirects()) { - return mRedirectEnd; - } - return 0; -} - DOMTimeMilliSec nsDOMNavigationTiming::GetUnloadEventStart() { diff --git a/dom/base/nsDOMNavigationTiming.h b/dom/base/nsDOMNavigationTiming.h index 0b43d36b6c80..7672bba2e833 100644 --- a/dom/base/nsDOMNavigationTiming.h +++ b/dom/base/nsDOMNavigationTiming.h @@ -37,18 +37,17 @@ public: nsDOMPerformanceNavigationType GetType() const { return mNavigationType; } - uint16_t GetRedirectCount(); - - DOMTimeMilliSec GetRedirectStart(); - DOMTimeMilliSec GetRedirectEnd(); + inline DOMHighResTimeStamp GetNavigationStartHighRes() const { + return mNavigationStartHighRes; + } DOMTimeMilliSec GetNavigationStart() const { - return mNavigationStart; + return static_cast(GetNavigationStartHighRes()); + } + mozilla::TimeStamp GetNavigationStartTimeStamp() const { + return mNavigationStartTimeStamp; } DOMTimeMilliSec GetUnloadEventStart(); DOMTimeMilliSec GetUnloadEventEnd(); - DOMTimeMilliSec GetFetchStart() const { - return mFetchStart; - } DOMTimeMilliSec GetDomLoading() const { return mDOMLoading; } @@ -73,7 +72,6 @@ public: void NotifyNavigationStart(); void NotifyFetchStart(nsIURI* aURI, nsDOMPerformanceNavigationType aNavigationType); - void NotifyRedirect(nsIURI* aOldURI, nsIURI* aNewURI); void NotifyBeforeUnload(); void NotifyUnloadAccepted(nsIURI* aOldURI); void NotifyUnloadEventStart(); @@ -89,7 +87,6 @@ public: void NotifyDOMContentLoadedStart(nsIURI* aURI); void NotifyDOMContentLoadedEnd(nsIURI* aURI); DOMTimeMilliSec TimeStampToDOM(mozilla::TimeStamp aStamp) const; - DOMTimeMilliSec TimeStampToDOMOrFetchStart(mozilla::TimeStamp aStamp) const; inline DOMHighResTimeStamp TimeStampToDOMHighRes(mozilla::TimeStamp aStamp) { @@ -102,27 +99,15 @@ private: ~nsDOMNavigationTiming(); void Clear(); - bool ReportRedirects(); nsCOMPtr mUnloadedURI; nsCOMPtr mLoadedURI; - nsCOMArray mRedirects; - - typedef enum { NOT_CHECKED, - CHECK_PASSED, - NO_REDIRECTS, - CHECK_FAILED} RedirectCheckState; - RedirectCheckState mRedirectCheck; - int16_t mRedirectCount; nsDOMPerformanceNavigationType mNavigationType; - DOMTimeMilliSec mNavigationStart; + DOMHighResTimeStamp mNavigationStartHighRes; mozilla::TimeStamp mNavigationStartTimeStamp; DOMTimeMilliSec DurationFromStart(); - DOMTimeMilliSec mFetchStart; - DOMTimeMilliSec mRedirectStart; - DOMTimeMilliSec mRedirectEnd; DOMTimeMilliSec mBeforeUnloadStart; DOMTimeMilliSec mUnloadStart; DOMTimeMilliSec mUnloadEnd; diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 2bec7ac3e87b..366feb0f0eee 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -2454,7 +2454,8 @@ nsGlobalWindow::SetNewDocument(nsIDocument* aDocument, newInnerWindow->mPerformance = new nsPerformance(newInnerWindow, currentInner->mPerformance->GetDOMTiming(), - currentInner->mPerformance->GetChannel()); + currentInner->mPerformance->GetChannel(), + currentInner->mPerformance->GetParentPerformance()); } } @@ -3567,7 +3568,22 @@ nsPIDOMWindow::CreatePerformanceObjectIfNeeded() timedChannel = nullptr; } if (timing) { - mPerformance = new nsPerformance(this, timing, timedChannel); + // If we are dealing with an iframe, we will need the parent's performance + // object (so we can add the iframe as a resource of that page). + nsPerformance* parentPerformance = nullptr; + nsCOMPtr parentWindow; + GetScriptableParent(getter_AddRefs(parentWindow)); + nsCOMPtr parentPWindow = do_GetInterface(parentWindow); + if (GetOuterWindow() != parentPWindow) { + if (parentPWindow && !parentPWindow->IsInnerWindow()) { + parentPWindow = parentPWindow->GetCurrentInnerWindow(); + } + if (parentPWindow) { + parentPerformance = parentPWindow->GetPerformance(); + } + } + mPerformance = + new nsPerformance(this, timing, timedChannel, parentPerformance); } } diff --git a/dom/base/nsPerformance.cpp b/dom/base/nsPerformance.cpp index ea029be0828e..d41040f3972b 100644 --- a/dom/base/nsPerformance.cpp +++ b/dom/base/nsPerformance.cpp @@ -5,14 +5,20 @@ #include "nsPerformance.h" #include "nsCOMPtr.h" +#include "nsIHttpChannel.h" #include "nsITimedChannel.h" #include "nsDOMNavigationTiming.h" #include "nsContentUtils.h" +#include "nsIScriptSecurityManager.h" #include "nsIDOMWindow.h" +#include "nsIURI.h" +#include "PerformanceEntry.h" +#include "PerformanceResourceTiming.h" #include "mozilla/dom/PerformanceBinding.h" #include "mozilla/dom/PerformanceTimingBinding.h" #include "mozilla/dom/PerformanceNavigationBinding.h" #include "mozilla/TimeStamp.h" +#include "nsThreadUtils.h" using namespace mozilla; @@ -22,97 +28,257 @@ NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsPerformanceTiming, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsPerformanceTiming, Release) nsPerformanceTiming::nsPerformanceTiming(nsPerformance* aPerformance, - nsITimedChannel* aChannel) + nsITimedChannel* aChannel, + nsIHttpChannel* aHttpChannel, + DOMHighResTimeStamp aZeroTime) : mPerformance(aPerformance), - mChannel(aChannel) + mChannel(aChannel), + mFetchStart(0.0), + mZeroTime(aZeroTime), + mReportCrossOriginResources(true) { MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); SetIsDOMBinding(); + // The aHttpChannel argument is null if this nsPerformanceTiming object + // is being used for the navigation timing (document) and has a non-null + // value for the resource timing (any resources within the page). + if (aHttpChannel) { + CheckRedirectCrossOrigin(aHttpChannel); + } } nsPerformanceTiming::~nsPerformanceTiming() { } -DOMTimeMilliSec -nsPerformanceTiming::DomainLookupStart() const +DOMHighResTimeStamp +nsPerformanceTiming::FetchStartHighRes() { - if (!nsContentUtils::IsPerformanceTimingEnabled()) { + if (!mFetchStart) { + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { + return 0; + } + TimeStamp stamp; + mChannel->GetAsyncOpen(&stamp); + MOZ_ASSERT(!stamp.IsNull(), "The fetch start time stamp should always be " + "valid if the performance timing is enabled"); + mFetchStart = (!stamp.IsNull()) + ? TimeStampToDOMHighRes(stamp) + : 0.0; + } + return mFetchStart; +} + +DOMTimeMilliSec +nsPerformanceTiming::FetchStart() +{ + return static_cast(FetchStartHighRes()); +} + +// This method will implement the timing allow check algorithm +// http://w3c-test.org/webperf/specs/ResourceTiming/#timing-allow-check +// https://bugzilla.mozilla.org/show_bug.cgi?id=936814 +void +nsPerformanceTiming::CheckRedirectCrossOrigin(nsIHttpChannel* aResourceChannel) +{ + MOZ_ASSERT(mChannel, "The resource channel should not be null for any" + "valid resource"); + uint16_t redirectCount; + mChannel->GetRedirectCount(&redirectCount); + if (redirectCount == 0) { + return; + } + nsCOMPtr resourceURI, referrerURI; + aResourceChannel->GetReferrer(getter_AddRefs(referrerURI)); + aResourceChannel->GetURI(getter_AddRefs(resourceURI)); + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + nsresult rv = ssm->CheckSameOriginURI(resourceURI, referrerURI, false); + if (!NS_SUCCEEDED(rv)) { + mReportCrossOriginResources = false; + } +} + +bool +nsPerformanceTiming::IsSameOriginAsReferral() const +{ + return mReportCrossOriginResources; +} + +uint16_t +nsPerformanceTiming::GetRedirectCount() const +{ + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { return 0; } - if (!mChannel) { - return FetchStart(); + bool sameOrigin; + mChannel->GetAllRedirectsSameOrigin(&sameOrigin); + if (!sameOrigin) { + return 0; + } + uint16_t redirectCount; + mChannel->GetRedirectCount(&redirectCount); + return redirectCount; +} + +/** + * RedirectStartHighRes() is used by both the navigation timing and the + * resource timing. Since, navigation timing and resource timing check and + * interpret cross-domain redirects in a different manner, + * RedirectStartHighRes() will make no checks for cross-domain redirect. + * It's up to the consumers of this method (nsPerformanceTiming::RedirectStart() + * and PerformanceResourceTiming::RedirectStart() to make such verifications. + * + * @return a valid timing if the Performance Timing is enabled + */ +DOMHighResTimeStamp +nsPerformanceTiming::RedirectStartHighRes() +{ + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { + return 0; + } + mozilla::TimeStamp stamp; + mChannel->GetRedirectStart(&stamp); + return TimeStampToDOMHighResOrFetchStart(stamp); +} + +DOMTimeMilliSec +nsPerformanceTiming::RedirectStart() +{ + // We have to check if all the redirect URIs had the same origin (since there + // is no check in RedirectStartHighRes()) + bool sameOrigin; + mChannel->GetAllRedirectsSameOrigin(&sameOrigin); + if (sameOrigin) { + return static_cast(RedirectStartHighRes()); + } + return 0; +} + +/** + * RedirectEndHighRes() is used by both the navigation timing and the resource + * timing. Since, navigation timing and resource timing check and interpret + * cross-domain redirects in a different manner, RedirectEndHighRes() will make + * no checks for cross-domain redirect. It's up to the consumers of this method + * (nsPerformanceTiming::RedirectEnd() and + * PerformanceResourceTiming::RedirectEnd() to make such verifications. + * + * @return a valid timing if the Performance Timing is enabled + */ +DOMHighResTimeStamp +nsPerformanceTiming::RedirectEndHighRes() +{ + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { + return 0; + } + mozilla::TimeStamp stamp; + mChannel->GetRedirectEnd(&stamp); + return TimeStampToDOMHighResOrFetchStart(stamp); +} + +DOMTimeMilliSec +nsPerformanceTiming::RedirectEnd() +{ + // We have to check if all the redirect URIs had the same origin (since there + // is no check in RedirectEndHighRes()) + bool sameOrigin; + mChannel->GetAllRedirectsSameOrigin(&sameOrigin); + if (sameOrigin) { + return static_cast(RedirectEndHighRes()); + } + return 0; +} + +DOMHighResTimeStamp +nsPerformanceTiming::DomainLookupStartHighRes() +{ + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { + return 0; } mozilla::TimeStamp stamp; mChannel->GetDomainLookupStart(&stamp); - return GetDOMTiming()->TimeStampToDOMOrFetchStart(stamp); + return TimeStampToDOMHighResOrFetchStart(stamp); } DOMTimeMilliSec -nsPerformanceTiming::DomainLookupEnd() const +nsPerformanceTiming::DomainLookupStart() { - if (!nsContentUtils::IsPerformanceTimingEnabled()) { + return static_cast(DomainLookupStartHighRes()); +} + +DOMHighResTimeStamp +nsPerformanceTiming::DomainLookupEndHighRes() +{ + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { return 0; } - if (!mChannel) { - return FetchStart(); - } mozilla::TimeStamp stamp; mChannel->GetDomainLookupEnd(&stamp); - return GetDOMTiming()->TimeStampToDOMOrFetchStart(stamp); + return TimeStampToDOMHighResOrFetchStart(stamp); } DOMTimeMilliSec -nsPerformanceTiming::ConnectStart() const +nsPerformanceTiming::DomainLookupEnd() { - if (!nsContentUtils::IsPerformanceTimingEnabled()) { + return static_cast(DomainLookupEndHighRes()); +} + +DOMHighResTimeStamp +nsPerformanceTiming::ConnectStartHighRes() +{ + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { return 0; } - if (!mChannel) { - return FetchStart(); - } mozilla::TimeStamp stamp; mChannel->GetConnectStart(&stamp); - return GetDOMTiming()->TimeStampToDOMOrFetchStart(stamp); + return TimeStampToDOMHighResOrFetchStart(stamp); } DOMTimeMilliSec -nsPerformanceTiming::ConnectEnd() const +nsPerformanceTiming::ConnectStart() { - if (!nsContentUtils::IsPerformanceTimingEnabled()) { + return static_cast(ConnectStartHighRes()); +} + +DOMHighResTimeStamp +nsPerformanceTiming::ConnectEndHighRes() +{ + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { return 0; } - if (!mChannel) { - return FetchStart(); - } mozilla::TimeStamp stamp; mChannel->GetConnectEnd(&stamp); - return GetDOMTiming()->TimeStampToDOMOrFetchStart(stamp); + return TimeStampToDOMHighResOrFetchStart(stamp); } DOMTimeMilliSec -nsPerformanceTiming::RequestStart() const +nsPerformanceTiming::ConnectEnd() { - if (!nsContentUtils::IsPerformanceTimingEnabled()) { + return static_cast(ConnectEndHighRes()); +} + +DOMHighResTimeStamp +nsPerformanceTiming::RequestStartHighRes() +{ + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { return 0; } - if (!mChannel) { - return FetchStart(); - } mozilla::TimeStamp stamp; mChannel->GetRequestStart(&stamp); - return GetDOMTiming()->TimeStampToDOMOrFetchStart(stamp); + return TimeStampToDOMHighResOrFetchStart(stamp); } DOMTimeMilliSec -nsPerformanceTiming::ResponseStart() const +nsPerformanceTiming::RequestStart() { - if (!nsContentUtils::IsPerformanceTimingEnabled()) { + return static_cast(RequestStartHighRes()); +} + +DOMHighResTimeStamp +nsPerformanceTiming::ResponseStartHighRes() +{ + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { return 0; } - if (!mChannel) { - return FetchStart(); - } mozilla::TimeStamp stamp; mChannel->GetResponseStart(&stamp); mozilla::TimeStamp cacheStamp; @@ -120,18 +286,21 @@ nsPerformanceTiming::ResponseStart() const if (stamp.IsNull() || (!cacheStamp.IsNull() && cacheStamp < stamp)) { stamp = cacheStamp; } - return GetDOMTiming()->TimeStampToDOMOrFetchStart(stamp); + return TimeStampToDOMHighResOrFetchStart(stamp); } DOMTimeMilliSec -nsPerformanceTiming::ResponseEnd() const +nsPerformanceTiming::ResponseStart() { - if (!nsContentUtils::IsPerformanceTimingEnabled()) { + return static_cast(ResponseStartHighRes()); +} + +DOMHighResTimeStamp +nsPerformanceTiming::ResponseEndHighRes() +{ + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { return 0; } - if (!mChannel) { - return FetchStart(); - } mozilla::TimeStamp stamp; mChannel->GetResponseEnd(&stamp); mozilla::TimeStamp cacheStamp; @@ -139,7 +308,21 @@ nsPerformanceTiming::ResponseEnd() const if (stamp.IsNull() || (!cacheStamp.IsNull() && cacheStamp < stamp)) { stamp = cacheStamp; } - return GetDOMTiming()->TimeStampToDOMOrFetchStart(stamp); + return TimeStampToDOMHighResOrFetchStart(stamp); +} + +DOMTimeMilliSec +nsPerformanceTiming::ResponseEnd() +{ + return static_cast(ResponseEndHighRes()); +} + +bool +nsPerformanceTiming::IsInitialized() const +{ + MOZ_ASSERT(mChannel, "The timed channel should not be null for any " + "valid resource"); + return !!mChannel; } JSObject* @@ -172,18 +355,23 @@ nsPerformanceNavigation::WrapObject(JSContext *cx) } -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_3(nsPerformance, +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_5(nsPerformance, mWindow, mTiming, - mNavigation) + mNavigation, mEntries, + mParentPerformance) NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPerformance) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPerformance) nsPerformance::nsPerformance(nsIDOMWindow* aWindow, nsDOMNavigationTiming* aDOMTiming, - nsITimedChannel* aChannel) + nsITimedChannel* aChannel, + nsPerformance* aParentPerformance) : mWindow(aWindow), mDOMTiming(aDOMTiming), - mChannel(aChannel) + mChannel(aChannel), + mParentPerformance(aParentPerformance), + mBufferSizeSet(kDefaultBufferSize), + mPrimaryBufferSize(kDefaultBufferSize) { MOZ_ASSERT(aWindow, "Parent window object should be provided"); SetIsDOMBinding(); @@ -204,7 +392,12 @@ nsPerformanceTiming* nsPerformance::Timing() { if (!mTiming) { - mTiming = new nsPerformanceTiming(this, mChannel); + // For navigation timing, the third argument (an nsIHtttpChannel) is null + // since the cross-domain redirect were already checked. + // The last argument (zero time) for performance.timing is the navigation + // start value. + mTiming = new nsPerformanceTiming(this, mChannel, nullptr, + mDOMTiming->GetNavigationStart()); } return mTiming; } @@ -230,3 +423,147 @@ nsPerformance::WrapObject(JSContext *cx) return dom::PerformanceBinding::Wrap(cx, this); } +void +nsPerformance::GetEntries(nsTArray >& retval) +{ + MOZ_ASSERT(NS_IsMainThread()); + + retval.Clear(); + uint32_t count = mEntries.Length(); + if (count > mPrimaryBufferSize) { + count = mPrimaryBufferSize; + } + retval.AppendElements(mEntries.Elements(), count); +} + +void +nsPerformance::GetEntriesByType(const nsAString& entryType, + nsTArray >& retval) +{ + MOZ_ASSERT(NS_IsMainThread()); + + retval.Clear(); + uint32_t count = mEntries.Length(); + for (uint32_t i = 0 ; i < count && i < mPrimaryBufferSize ; i++) { + if (mEntries[i]->GetEntryType().Equals(entryType)) { + retval.AppendElement(mEntries[i]); + } + } +} + +void +nsPerformance::GetEntriesByName(const nsAString& name, + const mozilla::dom::Optional& entryType, + nsTArray >& retval) +{ + MOZ_ASSERT(NS_IsMainThread()); + + retval.Clear(); + uint32_t count = mEntries.Length(); + for (uint32_t i = 0 ; i < count && i < mPrimaryBufferSize ; i++) { + if (mEntries[i]->GetName().Equals(name) && + (!entryType.WasPassed() || + mEntries[i]->GetEntryType().Equals(entryType.Value()))) { + retval.AppendElement(mEntries[i]); + } + } +} + +void +nsPerformance::ClearResourceTimings() +{ + MOZ_ASSERT(NS_IsMainThread()); + mPrimaryBufferSize = mBufferSizeSet; + mEntries.Clear(); +} + +void +nsPerformance::SetResourceTimingBufferSize(uint64_t maxSize) +{ + MOZ_ASSERT(NS_IsMainThread()); + mBufferSizeSet = maxSize; + if (mBufferSizeSet < mEntries.Length()) { + // call onresourcetimingbufferfull + // https://bugzilla.mozilla.org/show_bug.cgi?id=936813 + } +} + +/** + * An entry should be added only after the resource is loaded. + * This method is not thread safe and can only be called on the main thread. + */ +void +nsPerformance::AddEntry(nsIHttpChannel* channel, + nsITimedChannel* timedChannel) +{ + MOZ_ASSERT(NS_IsMainThread()); + // Check if resource timing is prefed off. + if (!nsContentUtils::IsResourceTimingEnabled()) { + return; + } + if (channel && timedChannel) { + nsAutoCString name; + nsAutoString initiatorType; + nsCOMPtr originalURI; + + timedChannel->GetInitiatorType(initiatorType); + + // According to the spec, "The name attribute must return the resolved URL + // of the requested resource. This attribute must not change even if the + // fetch redirected to a different URL." + channel->GetOriginalURI(getter_AddRefs(originalURI)); + originalURI->GetSpec(name); + NS_ConvertUTF8toUTF16 entryName(name); + + // The nsITimedChannel argument will be used to gather all the timings. + // The nsIHttpChannel argument will be used to check if any cross-origin + // redirects occurred. + // The last argument is the "zero time" (offset). Since we don't want + // any offset for the resource timing, this will be set to "0" - the + // resource timing returns a relative timing (no offset). + nsRefPtr performanceTiming = + new nsPerformanceTiming(this, timedChannel, channel, + 0); + + // The PerformanceResourceTiming object will use the nsPerformanceTiming + // object to get all the required timings. + nsRefPtr performanceEntry = + new dom::PerformanceResourceTiming(performanceTiming, this); + + performanceEntry->SetName(entryName); + performanceEntry->SetEntryType(NS_LITERAL_STRING("resource")); + // If the initiator type had no valid value, then set it to the default + // ("other") value. + if (initiatorType.IsEmpty()) { + initiatorType = NS_LITERAL_STRING("other"); + } + performanceEntry->SetInitiatorType(initiatorType); + + mEntries.InsertElementSorted(performanceEntry, + PerformanceEntryComparator()); + if (mEntries.Length() > mPrimaryBufferSize) { + // call onresourcetimingbufferfull + // https://bugzilla.mozilla.org/show_bug.cgi?id=936813 + } + } +} + +bool +nsPerformance::PerformanceEntryComparator::Equals( + const PerformanceEntry* aElem1, + const PerformanceEntry* aElem2) const +{ + NS_ABORT_IF_FALSE(aElem1 && aElem2, + "Trying to compare null performance entries"); + return aElem1->StartTime() == aElem2->StartTime(); +} + +bool +nsPerformance::PerformanceEntryComparator::LessThan( + const PerformanceEntry* aElem1, + const PerformanceEntry* aElem2) const +{ + NS_ABORT_IF_FALSE(aElem1 && aElem2, + "Trying to compare null performance entries"); + return aElem1->StartTime() < aElem2->StartTime(); +} diff --git a/dom/base/nsPerformance.h b/dom/base/nsPerformance.h index 85ad5c5081d6..9fef9cd82f88 100644 --- a/dom/base/nsPerformance.h +++ b/dom/base/nsPerformance.h @@ -13,16 +13,47 @@ #include "nsContentUtils.h" #include "nsIDOMWindow.h" #include "js/TypeDecls.h" +#include "mozilla/dom/BindingDeclarations.h" class nsITimedChannel; class nsPerformance; +class nsIHttpChannel; + +namespace mozilla { +namespace dom { + class PerformanceEntry; +} +} // Script "performance.timing" object class nsPerformanceTiming MOZ_FINAL : public nsWrapperCache { public: + typedef mozilla::TimeStamp TimeStamp; + +/** + * @param aPerformance + * The performance object (the JS parent). + * This will allow access to "window.performance.timing" attribute for + * the navigation timing (can't be null). + * @param aChannel + * An nsITimedChannel used to gather all the networking timings by both + * the navigation timing and the resource timing (can't be null). + * @param aHttpChannel + * An nsIHttpChannel (the resource's http channel). + * This will be used by the resource timing cross-domain check + * algorithm. + * Argument is null for the navigation timing (navigation timing uses + * another algorithm for the cross-domain redirects). + * @param aZeroTime + * The offset that will be added to the timestamp of each event. This + * argument should be equal to performance.navigationStart for + * navigation timing and "0" for the resource timing. + */ nsPerformanceTiming(nsPerformance* aPerformance, - nsITimedChannel* aChannel); + nsITimedChannel* aChannel, + nsIHttpChannel* aHttpChannel, + DOMHighResTimeStamp aZeroTime); NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsPerformanceTiming) NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(nsPerformanceTiming) @@ -33,6 +64,57 @@ public: return mPerformance; } + /** + * @param aStamp + * The TimeStamp recorded for a specific event. This TimeStamp can + * be null. + * @return the duration of an event with a given TimeStamp, relative to the + * navigationStart TimeStamp (the moment the user landed on the + * page), if the given TimeStamp is valid. Otherwise, it will return + * the FetchStart timing value. + */ + inline DOMHighResTimeStamp TimeStampToDOMHighResOrFetchStart(TimeStamp aStamp) + { + return (!aStamp.IsNull()) + ? TimeStampToDOMHighRes(aStamp) + : FetchStartHighRes(); + } + + /** + * The nsITimedChannel records an absolute timestamp for each event. + * The nsDOMNavigationTiming will record the moment when the user landed on + * the page. This is a window.performance unique timestamp, so it can be used + * for all the events (navigation timing and resource timing events). + * + * The algorithm operates in 2 steps: + * 1. The first step is to subtract the two timestamps: the argument (the + * envet's timesramp) and the navigation start timestamp. This will result in + * a relative timestamp of the event (relative to the navigation start - + * window.performance.timing.navigationStart). + * 2. The second step is to add any required offset (the mZeroTime). For now, + * this offset value is either 0 (for the resource timing), or equal to + * "performance.navigationStart" (for navigation timing). + * For the resource timing, mZeroTime is set to 0, causing the result to be a + * relative time. + * For the navigation timing, mZeroTime is set to "performance.navigationStart" + * causing the result be an absolute time. + * + * @param aStamp + * The TimeStamp recorded for a specific event. This TimeStamp can't + * be null. + * @return number of milliseconds value as one of: + * - relative to the navigation start time, time the user has landed on the + * page + * - an absolute wall clock time since the unix epoch + */ + inline DOMHighResTimeStamp TimeStampToDOMHighRes(TimeStamp aStamp) const + { + MOZ_ASSERT(!aStamp.IsNull()); + mozilla::TimeDuration duration = + aStamp - GetDOMTiming()->GetNavigationStartTimeStamp(); + return duration.ToMilliseconds() + mZeroTime; + } + virtual JSObject* WrapObject(JSContext *cx) MOZ_OVERRIDE; // PerformanceNavigation WebIDL methods @@ -54,32 +136,36 @@ public: } return GetDOMTiming()->GetUnloadEventEnd(); } - DOMTimeMilliSec RedirectStart() { - if (!nsContentUtils::IsPerformanceTimingEnabled()) { - return 0; - } - return GetDOMTiming()->GetRedirectStart(); - } - DOMTimeMilliSec RedirectEnd() { - if (!nsContentUtils::IsPerformanceTimingEnabled()) { - return 0; - } - return GetDOMTiming()->GetRedirectEnd(); - } - DOMTimeMilliSec FetchStart() const { - if (!nsContentUtils::IsPerformanceTimingEnabled()) { - return 0; - } - return GetDOMTiming()->GetFetchStart(); - } - DOMTimeMilliSec DomainLookupStart() const; - DOMTimeMilliSec DomainLookupEnd() const; - DOMTimeMilliSec ConnectStart() const; - DOMTimeMilliSec ConnectEnd() const; - DOMTimeMilliSec RequestStart() const; - DOMTimeMilliSec ResponseStart() const; - DOMTimeMilliSec ResponseEnd() const; - DOMTimeMilliSec DomLoading() const { + + uint16_t GetRedirectCount() const; + bool IsSameOriginAsReferral() const; + void CheckRedirectCrossOrigin(nsIHttpChannel* aResourceChannel); + + // High resolution (used by resource timing) + DOMHighResTimeStamp FetchStartHighRes(); + DOMHighResTimeStamp RedirectStartHighRes(); + DOMHighResTimeStamp RedirectEndHighRes(); + DOMHighResTimeStamp DomainLookupStartHighRes(); + DOMHighResTimeStamp DomainLookupEndHighRes(); + DOMHighResTimeStamp ConnectStartHighRes(); + DOMHighResTimeStamp ConnectEndHighRes(); + DOMHighResTimeStamp RequestStartHighRes(); + DOMHighResTimeStamp ResponseStartHighRes(); + DOMHighResTimeStamp ResponseEndHighRes(); + + // Low resolution (used by navigation timing) + DOMTimeMilliSec FetchStart(); + DOMTimeMilliSec RedirectStart(); + DOMTimeMilliSec RedirectEnd(); + DOMTimeMilliSec DomainLookupStart(); + DOMTimeMilliSec DomainLookupEnd(); + DOMTimeMilliSec ConnectStart(); + DOMTimeMilliSec ConnectEnd(); + DOMTimeMilliSec RequestStart(); + DOMTimeMilliSec ResponseStart(); + DOMTimeMilliSec ResponseEnd(); + + DOMTimeMilliSec DomLoading() { if (!nsContentUtils::IsPerformanceTimingEnabled()) { return 0; } @@ -124,8 +210,16 @@ public: private: ~nsPerformanceTiming(); + bool IsInitialized() const; nsRefPtr mPerformance; nsCOMPtr mChannel; + DOMHighResTimeStamp mFetchStart; + // This is an offset that will be added to each timing ([ms] resolution). + // There are only 2 possible values: (1) logicaly equal to navigationStart + // TimeStamp (results are absolute timstamps - wallclock); (2) "0" (results + // are relative to the navigation start). + DOMHighResTimeStamp mZeroTime; + bool mReportCrossOriginResources; }; // Script "performance.navigation" object @@ -137,6 +231,7 @@ public: NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(nsPerformanceNavigation) nsDOMNavigationTiming* GetDOMTiming() const; + nsPerformanceTiming* GetPerformanceTiming() const; nsPerformance* GetParentObject() const { @@ -150,7 +245,7 @@ public: return GetDOMTiming()->GetType(); } uint16_t RedirectCount() const { - return GetDOMTiming()->GetRedirectCount(); + return GetPerformanceTiming()->GetRedirectCount(); } private: @@ -163,9 +258,11 @@ class nsPerformance MOZ_FINAL : public nsISupports, public nsWrapperCache { public: + typedef mozilla::dom::PerformanceEntry PerformanceEntry; nsPerformance(nsIDOMWindow* aWindow, nsDOMNavigationTiming* aDOMTiming, - nsITimedChannel* aChannel); + nsITimedChannel* aChannel, + nsPerformance* aParentPerformance); NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsPerformance) @@ -180,6 +277,11 @@ public: return mChannel; } + nsPerformance* GetParentPerformance() const + { + return mParentPerformance; + } + nsIDOMWindow* GetParentObject() const { return mWindow.get(); @@ -192,6 +294,17 @@ public: nsPerformanceTiming* Timing(); nsPerformanceNavigation* Navigation(); + void GetEntries(nsTArray >& retval); + void GetEntriesByType(const nsAString& entryType, + nsTArray >& retval); + void GetEntriesByName(const nsAString& name, + const mozilla::dom::Optional< nsAString >& entryType, + nsTArray >& retval); + void AddEntry(nsIHttpChannel* channel, + nsITimedChannel* timedChannel); + void ClearResourceTimings(); + void SetResourceTimingBufferSize(uint64_t maxSize); + private: ~nsPerformance(); @@ -200,6 +313,21 @@ private: nsCOMPtr mChannel; nsRefPtr mTiming; nsRefPtr mNavigation; + nsTArray > mEntries; + nsRefPtr mParentPerformance; + uint64_t mBufferSizeSet; + uint64_t mPrimaryBufferSize; + + static const uint64_t kDefaultBufferSize = 150; + + // Helper classes + class PerformanceEntryComparator { + public: + bool Equals(const PerformanceEntry* aElem1, + const PerformanceEntry* aElem2) const; + bool LessThan(const PerformanceEntry* aElem1, + const PerformanceEntry* aElem2) const; + }; }; inline nsDOMNavigationTiming* @@ -208,6 +336,12 @@ nsPerformanceNavigation::GetDOMTiming() const return mPerformance->GetDOMTiming(); } +inline nsPerformanceTiming* +nsPerformanceNavigation::GetPerformanceTiming() const +{ + return mPerformance->Timing(); +} + inline nsDOMNavigationTiming* nsPerformanceTiming::GetDOMTiming() const { diff --git a/dom/tests/mochitest/general/mochitest.ini b/dom/tests/mochitest/general/mochitest.ini index 19716c6a4507..7f17199ba64c 100644 --- a/dom/tests/mochitest/general/mochitest.ini +++ b/dom/tests/mochitest/general/mochitest.ini @@ -12,6 +12,8 @@ support-files = file_moving_xhr.html file_showModalDialog.html historyframes.html + resource_timing_iframe.html + resource_timing_main_test.html [test_497898.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || toolkit == 'android' #Bug 931116, b2g desktop specific, initial triage @@ -48,6 +50,7 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop spec [test_outerHTML.html] [test_outerHTML.xhtml] [test_paste_selection.html] +[test_resource_timing.html] skip-if = buildapp == 'b2g' # b2g(No clipboard) b2g-debug(No clipboard) b2g-desktop(No clipboard) [test_performance_now.html] [test_showModalDialog.html] diff --git a/dom/tests/mochitest/general/resource_timing_iframe.html b/dom/tests/mochitest/general/resource_timing_iframe.html new file mode 100644 index 000000000000..ef8e439df6da --- /dev/null +++ b/dom/tests/mochitest/general/resource_timing_iframe.html @@ -0,0 +1,48 @@ + + + + + + + + + Test for Bug 822480 - Add in the Resource Timing API + + + + + + + diff --git a/dom/tests/mochitest/general/resource_timing_main_test.html b/dom/tests/mochitest/general/resource_timing_main_test.html new file mode 100644 index 000000000000..5ca0c126eee1 --- /dev/null +++ b/dom/tests/mochitest/general/resource_timing_main_test.html @@ -0,0 +1,258 @@ + + + + + + + + + + + + + Bug #822480 - Add in the Resource Timing API + +

+
+ + + + + + + diff --git a/dom/tests/mochitest/general/test-data.json b/dom/tests/mochitest/general/test-data.json new file mode 100644 index 000000000000..7bd0cdaf3b30 --- /dev/null +++ b/dom/tests/mochitest/general/test-data.json @@ -0,0 +1 @@ +{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ] } diff --git a/dom/tests/mochitest/general/test-data2.json b/dom/tests/mochitest/general/test-data2.json new file mode 100644 index 000000000000..7bd0cdaf3b30 --- /dev/null +++ b/dom/tests/mochitest/general/test-data2.json @@ -0,0 +1 @@ +{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ] } diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 1f7cf6b96149..2a893ce2f95a 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -747,8 +747,12 @@ var interfaceNamesInGlobalScope = { name: "Path2D", pref: "canvas.path.enabled" }, // IMPORTANT: Do not change this list without review from a DOM peer! "Performance", +// IMPORTANT: Do not change this list without review from a DOM peer! + "PerformanceEntry", // IMPORTANT: Do not change this list without review from a DOM peer! "PerformanceNavigation", +// IMPORTANT: Do not change this list without review from a DOM peer! + "PerformanceResourceTiming", // IMPORTANT: Do not change this list without review from a DOM peer! "PerformanceTiming", // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/tests/mochitest/general/test_resource_timing.html b/dom/tests/mochitest/general/test_resource_timing.html new file mode 100644 index 000000000000..16c8ead9e995 --- /dev/null +++ b/dom/tests/mochitest/general/test_resource_timing.html @@ -0,0 +1,35 @@ + + + + + + + + + + + +
+
+
+ + + diff --git a/dom/webidl/Performance.webidl b/dom/webidl/Performance.webidl index a6b89af8e750..6c7c3ca7a9d6 100644 --- a/dom/webidl/Performance.webidl +++ b/dom/webidl/Performance.webidl @@ -11,6 +11,7 @@ */ typedef double DOMHighResTimeStamp; +typedef sequence PerformanceEntryList; interface Performance { DOMHighResTimeStamp now(); @@ -22,3 +23,17 @@ interface Performance { jsonifier; }; + +// http://www.w3c-test.org/webperf/specs/PerformanceTimeline/#sec-window.performance-attribute +partial interface Performance { + PerformanceEntryList getEntries(); + PerformanceEntryList getEntriesByType(DOMString entryType); + PerformanceEntryList getEntriesByName(DOMString name, optional DOMString + entryType); +}; + +// http://w3c-test.org/webperf/specs/ResourceTiming/#extensions-performance-interface +partial interface Performance { + void clearResourceTimings(); + void setResourceTimingBufferSize(unsigned long maxSize); +}; diff --git a/dom/webidl/PerformanceEntry.webidl b/dom/webidl/PerformanceEntry.webidl new file mode 100644 index 000000000000..e5d99883a7c7 --- /dev/null +++ b/dom/webidl/PerformanceEntry.webidl @@ -0,0 +1,19 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * http://www.w3c-test.org/webperf/specs/PerformanceTimeline/#sec-PerformanceEntry-interface + * + * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C + * liability, trademark and document use rules apply. + */ + +interface PerformanceEntry +{ + readonly attribute DOMString name; + readonly attribute DOMString entryType; + readonly attribute DOMHighResTimeStamp startTime; + readonly attribute DOMHighResTimeStamp duration; +}; diff --git a/dom/webidl/PerformanceResourceTiming.webidl b/dom/webidl/PerformanceResourceTiming.webidl new file mode 100644 index 000000000000..777fae904a8f --- /dev/null +++ b/dom/webidl/PerformanceResourceTiming.webidl @@ -0,0 +1,33 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * http://w3c-test.org/webperf/specs/ResourceTiming/#performanceresourcetiming + * + * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C + * liability, trademark and document use rules apply. + */ + +interface PerformanceResourceTiming : PerformanceEntry +{ + // A string with the name of that element that initiated the load. + // If the initiator is a CSS resource, the initiatorType attribute must return + // the string "css". + // If the initiator is an XMLHttpRequest object, the initiatorType attribute + // must return the string "xmlhttprequest". + readonly attribute DOMString initiatorType; + + readonly attribute DOMHighResTimeStamp redirectStart; + readonly attribute DOMHighResTimeStamp redirectEnd; + readonly attribute DOMHighResTimeStamp fetchStart; + readonly attribute DOMHighResTimeStamp domainLookupStart; + readonly attribute DOMHighResTimeStamp domainLookupEnd; + readonly attribute DOMHighResTimeStamp connectStart; + readonly attribute DOMHighResTimeStamp connectEnd; + readonly attribute DOMHighResTimeStamp secureConnectionStart; + readonly attribute DOMHighResTimeStamp requestStart; + readonly attribute DOMHighResTimeStamp responseStart; + readonly attribute DOMHighResTimeStamp responseEnd; +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index f865dbb6c485..308ee48cbba8 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -269,7 +269,9 @@ WEBIDL_FILES = [ 'PannerNode.webidl', 'ParentNode.webidl', 'Performance.webidl', + 'PerformanceEntry.webidl', 'PerformanceNavigation.webidl', + 'PerformanceResourceTiming.webidl', 'PerformanceTiming.webidl', 'PeriodicWave.webidl', 'PermissionSettings.webidl', diff --git a/dom/xbl/nsXBLResourceLoader.cpp b/dom/xbl/nsXBLResourceLoader.cpp index d03b1e77fd25..19b202537a07 100644 --- a/dom/xbl/nsXBLResourceLoader.cpp +++ b/dom/xbl/nsXBLResourceLoader.cpp @@ -119,7 +119,7 @@ nsXBLResourceLoader::LoadResources(bool* aResult) // XXX: initialDocumentURI is nullptr! nsRefPtr req; nsContentUtils::LoadImage(url, doc, docPrincipal, docURL, nullptr, - nsIRequest::LOAD_BACKGROUND, + nsIRequest::LOAD_BACKGROUND, EmptyString(), getter_AddRefs(req)); } else if (curr->mType == nsGkAtoms::stylesheet) { diff --git a/embedding/browser/webBrowser/nsContextMenuInfo.cpp b/embedding/browser/webBrowser/nsContextMenuInfo.cpp index 27f569ce8f7e..7717010ba53c 100644 --- a/embedding/browser/webBrowser/nsContextMenuInfo.cpp +++ b/embedding/browser/webBrowser/nsContextMenuInfo.cpp @@ -310,7 +310,7 @@ nsContextMenuInfo::GetBackgroundImageRequestInternal(nsIDOMNode *aDOMNode, imgRe return il->LoadImage(bgUri, nullptr, nullptr, principal, nullptr, nullptr, nullptr, nsIRequest::LOAD_NORMAL, - nullptr, channelPolicy, aRequest); + nullptr, channelPolicy, EmptyString(), aRequest); } } diff --git a/image/src/imgLoader.cpp b/image/src/imgLoader.cpp index 7ce5ba02d34e..5dd015c8c909 100644 --- a/image/src/imgLoader.cpp +++ b/image/src/imgLoader.cpp @@ -1562,6 +1562,7 @@ NS_IMETHODIMP imgLoader::LoadImageXPCOM(nsIURI *aURI, aLoadFlags, aCacheKey, aPolicy, + EmptyString(), &proxy); *_retval = proxy; return result; @@ -1581,6 +1582,7 @@ nsresult imgLoader::LoadImage(nsIURI *aURI, nsLoadFlags aLoadFlags, nsISupports *aCacheKey, nsIChannelPolicy *aPolicy, + const nsAString& initiatorType, imgRequestProxy **_retval) { VerifyCacheSizes(); @@ -1718,6 +1720,12 @@ nsresult imgLoader::LoadImage(nsIURI *aURI, request->Init(aURI, aURI, channelLoadGroup, newChannel, entry, aCX, aLoadingPrincipal, corsmode); + // Add the initiator type for this image load + nsCOMPtr timedChannel = do_QueryInterface(newChannel); + if (timedChannel) { + timedChannel->SetInitiatorType(initiatorType); + } + // Pass the inner window ID of the loading document, if possible. nsCOMPtr doc = do_QueryInterface(aCX); if (doc) { diff --git a/image/src/imgLoader.h b/image/src/imgLoader.h index d6948f9732b8..928e76d1cc26 100644 --- a/image/src/imgLoader.h +++ b/image/src/imgLoader.h @@ -255,6 +255,7 @@ public: nsLoadFlags aLoadFlags, nsISupports *aCacheKey, nsIChannelPolicy *aPolicy, + const nsAString& initiatorType, imgRequestProxy **_retval); nsresult LoadImageWithChannel(nsIChannel *channel, imgINotificationObserver *aObserver, diff --git a/image/src/imgRequestProxy.h b/image/src/imgRequestProxy.h index 9c55ae8f9146..59b7e960f1e6 100644 --- a/image/src/imgRequestProxy.h +++ b/image/src/imgRequestProxy.h @@ -17,6 +17,7 @@ #include "nsCOMPtr.h" #include "nsAutoPtr.h" #include "nsThreadUtils.h" +#include "mozilla/TimeStamp.h" #include "imgRequest.h" diff --git a/layout/generic/nsImageFrame.cpp b/layout/generic/nsImageFrame.cpp index 435e7f0a3537..82647db8f1dd 100644 --- a/layout/generic/nsImageFrame.cpp +++ b/layout/generic/nsImageFrame.cpp @@ -1842,6 +1842,7 @@ nsImageFrame::LoadIcon(const nsAString& aSpec, loadFlags, nullptr, nullptr, /* channel policy not needed */ + EmptyString(), aRequest); } diff --git a/layout/style/ImageLoader.cpp b/layout/style/ImageLoader.cpp index 77c5fa6c4a56..9249d7178423 100644 --- a/layout/style/ImageLoader.cpp +++ b/layout/style/ImageLoader.cpp @@ -259,6 +259,7 @@ ImageLoader::LoadImage(nsIURI* aURI, nsIPrincipal* aOriginPrincipal, nsRefPtr request; nsContentUtils::LoadImage(aURI, mDocument, aOriginPrincipal, aReferrer, nullptr, nsIRequest::LOAD_NORMAL, + NS_LITERAL_STRING("css"), getter_AddRefs(request)); if (!request) { diff --git a/layout/style/Loader.cpp b/layout/style/Loader.cpp index c8a92774aa66..ffb68dd877fd 100644 --- a/layout/style/Loader.cpp +++ b/layout/style/Loader.cpp @@ -1546,6 +1546,16 @@ Loader::LoadSheet(SheetLoadData* aLoadData, StyleSheetState aSheetState) nsCOMPtr referrerURI = aLoadData->GetReferrerURI(); if (referrerURI) httpChannel->SetReferrer(referrerURI); + + // Set the initiator type + nsCOMPtr timedChannel(do_QueryInterface(httpChannel)); + if (timedChannel) { + if (aLoadData->mParentData) { + timedChannel->SetInitiatorType(NS_LITERAL_STRING("css")); + } else { + timedChannel->SetInitiatorType(NS_LITERAL_STRING("link")); + } + } } // Now tell the channel we expect text/css data back.... We do diff --git a/layout/xul/nsImageBoxFrame.cpp b/layout/xul/nsImageBoxFrame.cpp index 4a3f929750f2..c733ca5efbb5 100644 --- a/layout/xul/nsImageBoxFrame.cpp +++ b/layout/xul/nsImageBoxFrame.cpp @@ -236,7 +236,7 @@ nsImageBoxFrame::UpdateImage() mContent->NodePrincipal())) { nsContentUtils::LoadImage(uri, doc, mContent->NodePrincipal(), doc->GetDocumentURI(), mListener, mLoadFlags, - getter_AddRefs(mImageRequest)); + EmptyString(), getter_AddRefs(mImageRequest)); if (mImageRequest) { nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, diff --git a/layout/xul/tree/nsTreeBodyFrame.cpp b/layout/xul/tree/nsTreeBodyFrame.cpp index fc79219df090..afa2e4745375 100644 --- a/layout/xul/tree/nsTreeBodyFrame.cpp +++ b/layout/xul/tree/nsTreeBodyFrame.cpp @@ -2199,6 +2199,7 @@ nsTreeBodyFrame::GetImage(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContex doc->GetDocumentURI(), imgNotificationObserver, nsIRequest::LOAD_NORMAL, + EmptyString(), getter_AddRefs(imageRequest)); NS_ENSURE_SUCCESS(rv, rv); diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 026e3f222efc..70af6e1edd8f 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -105,6 +105,9 @@ pref("dom.workers.sharedWorkers.enabled", true); // Whether nonzero values can be returned from performance.timing.* pref("dom.enable_performance", true); +// Whether resource timing will be gathered and returned by performance.GetEntries* +pref("dom.enable_resource_timing", false); + // Whether the Gamepad API is enabled pref("dom.gamepad.enabled", true); #ifdef RELEASE_BUILD diff --git a/netwerk/base/public/nsITimedChannel.idl b/netwerk/base/public/nsITimedChannel.idl index dd49b99fddf0..2138faf2015c 100644 --- a/netwerk/base/public/nsITimedChannel.idl +++ b/netwerk/base/public/nsITimedChannel.idl @@ -12,13 +12,16 @@ class TimeStamp; native TimeStamp(mozilla::TimeStamp); // All properties return zero if the value is not available -[scriptable, uuid(c259b593-a9bf-4d08-8149-ef89e1977dc4)] +[scriptable, uuid(E94AB245-B40D-4154-8B7F-B6E0F2461031)] interface nsITimedChannel : nsISupports { // Set this attribute to true to enable collection of timing data. // channelCreationTime will be available even with this attribute set to // false. attribute boolean timingEnabled; + // The number of redirects + attribute uint16_t redirectCount; + [noscript] readonly attribute TimeStamp channelCreation; [noscript] readonly attribute TimeStamp asyncOpen; @@ -32,6 +35,17 @@ interface nsITimedChannel : nsISupports { [noscript] readonly attribute TimeStamp responseStart; [noscript] readonly attribute TimeStamp responseEnd; + // The redirect attributes timings must be writeble, se we can transfer + // the data from one channel to the redirected channel. + [noscript] attribute TimeStamp redirectStart; + [noscript] attribute TimeStamp redirectEnd; + + // The initiator type + [noscript] attribute AString initiatorType; + + // This flag should be set to false only if a cross-domain redirect occurred + [noscript] attribute boolean allRedirectsSameOrigin; + // The following are only set if the document is (partially) read from the // cache [noscript] readonly attribute TimeStamp cacheReadStart; @@ -49,4 +63,6 @@ interface nsITimedChannel : nsISupports { readonly attribute PRTime responseEndTime; readonly attribute PRTime cacheReadStartTime; readonly attribute PRTime cacheReadEndTime; + readonly attribute PRTime redirectStartTime; + readonly attribute PRTime redirectEndTime; }; diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index d55fea0910af..668ac6833fbe 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -26,6 +26,8 @@ #include "nsICookieService.h" #include "nsIStreamConverterService.h" #include "nsCRT.h" +#include "nsContentUtils.h" +#include "nsIScriptSecurityManager.h" #include "nsIObserverService.h" #include @@ -59,10 +61,12 @@ HttpBaseChannel::HttpBaseChannel() , mLoadAsBlocking(false) , mLoadUnblocked(false) , mResponseTimeoutEnabled(true) + , mAllRedirectsSameOrigin(true) , mSuspendCount(0) , mProxyResolveFlags(0) , mContentDispositionHint(UINT32_MAX) , mHttpHandler(gHttpHandler) + , mRedirectCount(0) { LOG(("Creating HttpBaseChannel @%x\n", this)); @@ -1906,14 +1910,47 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, if (bag) mPropertyHash.EnumerateRead(CopyProperties, bag.get()); - // transfer timed channel enabled status - nsCOMPtr timed(do_QueryInterface(newChannel)); - if (timed) - timed->SetTimingEnabled(mTimingEnabled); + // Transfer the timing data (if we are dealing with an nsITimedChannel). + nsCOMPtr newTimedChannel(do_QueryInterface(newChannel)); + nsCOMPtr oldTimedChannel( + do_QueryInterface(static_cast(this))); + if (oldTimedChannel && newTimedChannel) { + newTimedChannel->SetTimingEnabled(mTimingEnabled); + newTimedChannel->SetRedirectCount(mRedirectCount + 1); + + // If the RedirectStart is null, we will use the AsyncOpen value of the + // previous channel (this is the first redirect in the redirects chain). + if (mRedirectStartTimeStamp.IsNull()) { + TimeStamp asyncOpen; + oldTimedChannel->GetAsyncOpen(&asyncOpen); + newTimedChannel->SetRedirectStart(asyncOpen); + } + else { + newTimedChannel->SetRedirectStart(mRedirectStartTimeStamp); + } + + // The RedirectEnd timestamp is equal to the previous channel response end. + TimeStamp prevResponseEnd; + oldTimedChannel->GetResponseEnd(&prevResponseEnd); + newTimedChannel->SetRedirectEnd(prevResponseEnd); + + // Check whether or not this was a cross-domain redirect. + newTimedChannel->SetAllRedirectsSameOrigin( + mAllRedirectsSameOrigin && SameOriginWithOriginalUri(newURI)); + } return NS_OK; } +// Redirect Tracking +bool +HttpBaseChannel::SameOriginWithOriginalUri(nsIURI *aURI) +{ + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + nsresult rv = ssm->CheckSameOriginURI(aURI, mOriginalURI, false); + return (NS_SUCCEEDED(rv)); +} + //------------------------------------------------------------------------------ } // namespace net diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h index e47563b5c2fe..275535314dc8 100644 --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -243,6 +243,10 @@ protected: getter_AddRefs(aResult)); } + // Redirect tracking + // Checks whether or not aURI and mOriginalURI share the same domain. + bool SameOriginWithOriginalUri(nsIURI *aURI); + friend class PrivateBrowsingChannel; nsCOMPtr mURI; @@ -306,6 +310,8 @@ protected: uint32_t mLoadAsBlocking : 1; uint32_t mLoadUnblocked : 1; uint32_t mResponseTimeoutEnabled : 1; + // A flag that should be false only if a cross-domain redirect occurred + uint32_t mAllRedirectsSameOrigin : 1; // Current suspension depth for this channel object uint32_t mSuspendCount; @@ -320,6 +326,19 @@ protected: nsAutoPtr mContentDispositionFilename; nsRefPtr mHttpHandler; // keep gHttpHandler alive + + // Performance tracking + // The initiator type (for this resource) - how was the resource referenced in + // the HTML file. + nsString mInitiatorType; + // Number of redirects that has occurred. + int16_t mRedirectCount; + // A time value equal to the starting time of the fetch that initiates the + // redirect. + mozilla::TimeStamp mRedirectStartTimeStamp; + // A time value equal to the time immediately after receiving the last byte of + // the response of the last redirect. + mozilla::TimeStamp mRedirectEndTimeStamp; }; // Share some code while working around C++'s absurd inability to handle casting diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index f7dd8f180dff..e1694bcf0bcd 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -62,6 +62,8 @@ #include "nsIStreamConverterService.h" #include "nsISiteSecurityService.h" #include "nsCRT.h" +#include "nsPIDOMWindow.h" +#include "nsPerformance.h" #include "CacheObserver.h" #include "mozilla/Telemetry.h" @@ -4774,6 +4776,66 @@ nsHttpChannel::GetAsyncOpen(TimeStamp* _retval) { return NS_OK; } +/** + * @return the number of redirects. There is no check for cross-domain + * redirects. This check must be done by the consumers. + */ +NS_IMETHODIMP +nsHttpChannel::GetRedirectCount(uint16_t *aRedirectCount) +{ + *aRedirectCount = mRedirectCount; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::SetRedirectCount(uint16_t aRedirectCount) +{ + mRedirectCount = aRedirectCount; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::GetRedirectStart(TimeStamp* _retval) +{ + *_retval = mRedirectStartTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::SetRedirectStart(TimeStamp aRedirectStart) +{ + mRedirectStartTimeStamp = aRedirectStart; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::GetRedirectEnd(TimeStamp* _retval) +{ + *_retval = mRedirectEndTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::SetRedirectEnd(TimeStamp aRedirectEnd) +{ + mRedirectEndTimeStamp = aRedirectEnd; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::GetAllRedirectsSameOrigin(bool *aAllRedirectsSameOrigin) +{ + *aAllRedirectsSameOrigin = mAllRedirectsSameOrigin; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::SetAllRedirectsSameOrigin(bool aAllRedirectsSameOrigin) +{ + mAllRedirectsSameOrigin = aAllRedirectsSameOrigin; + return NS_OK; +} + NS_IMETHODIMP nsHttpChannel::GetDomainLookupStart(TimeStamp* _retval) { if (mDNSPrefetch && mDNSPrefetch->TimingsValid()) @@ -4853,6 +4915,20 @@ nsHttpChannel::GetCacheReadEnd(TimeStamp* _retval) { return NS_OK; } +NS_IMETHODIMP +nsHttpChannel::GetInitiatorType(nsAString & aInitiatorType) +{ + aInitiatorType = mInitiatorType; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::SetInitiatorType(const nsAString & aInitiatorType) +{ + mInitiatorType = aInitiatorType; + return NS_OK; +} + #define IMPL_TIMING_ATTR(name) \ NS_IMETHODIMP \ nsHttpChannel::Get##name##Time(PRTime* _retval) { \ @@ -4878,6 +4954,8 @@ IMPL_TIMING_ATTR(ResponseStart) IMPL_TIMING_ATTR(ResponseEnd) IMPL_TIMING_ATTR(CacheReadStart) IMPL_TIMING_ATTR(CacheReadEnd) +IMPL_TIMING_ATTR(RedirectStart) +IMPL_TIMING_ATTR(RedirectEnd) #undef IMPL_TIMING_ATTR @@ -5240,6 +5318,12 @@ nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult st } } + // Register entry to the Performance resource timing + nsPerformance* documentPerformance = GetPerformance(); + if (documentPerformance) { + documentPerformance->AddEntry(this, this); + } + if (mListener) { LOG((" calling OnStopRequest\n")); mListener->OnStopRequest(this, mListenerContext, status); @@ -6218,6 +6302,46 @@ nsHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) return rv; } +nsPerformance* +nsHttpChannel::GetPerformance() +{ + // If performance timing is disabled, there is no need for the nsPerformance + // object anymore. + if (!mTimingEnabled) { + return nullptr; + } + nsCOMPtr loadContext; + NS_QueryNotificationCallbacks(this, loadContext); + if (!loadContext) { + return nullptr; + } + nsCOMPtr domWindow; + loadContext->GetAssociatedWindow(getter_AddRefs(domWindow)); + if (!domWindow) { + return nullptr; + } + nsCOMPtr pDomWindow = do_QueryInterface(domWindow); + if (!pDomWindow) { + return nullptr; + } + if (!pDomWindow->IsInnerWindow()) { + pDomWindow = pDomWindow->GetCurrentInnerWindow(); + if (!pDomWindow) { + return nullptr; + } + } + + nsPerformance* docPerformance = pDomWindow->GetPerformance(); + if (!docPerformance) { + return nullptr; + } + // iframes should be added to the parent's entries list. + if (mLoadFlags & LOAD_DOCUMENT_URI) { + return docPerformance->GetParentPerformance(); + } + return docPerformance; +} + void nsHttpChannel::ForcePending(bool aForcePending) { diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index 59647ea8813d..4b47a0e28655 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -30,6 +30,7 @@ class nsICacheEntryDescriptor; class nsICancelable; class nsIHttpChannelAuthProvider; class nsInputStreamPump; +class nsPerformance; namespace mozilla { namespace net { @@ -420,6 +421,7 @@ private: protected: virtual void DoNotifyListenerCleanup(); + nsPerformance* GetPerformance(); private: // cache telemetry bool mDidReval; diff --git a/widget/cocoa/OSXNotificationCenter.mm b/widget/cocoa/OSXNotificationCenter.mm index 5802406b1f18..bc68b472fc4a 100644 --- a/widget/cocoa/OSXNotificationCenter.mm +++ b/widget/cocoa/OSXNotificationCenter.mm @@ -241,7 +241,8 @@ OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const if (imageUri) { nsresult rv = il->LoadImage(imageUri, nullptr, nullptr, aPrincipal, nullptr, this, nullptr, nsIRequest::LOAD_NORMAL, nullptr, - nullptr, getter_AddRefs(osxni->mIconRequest)); + nullptr, EmptyString(), + getter_AddRefs(osxni->mIconRequest)); if (NS_SUCCEEDED(rv)) { // Set a timer for six seconds. If we don't have an icon by the time this // goes off then we go ahead without an icon. diff --git a/widget/cocoa/nsMenuItemIconX.mm b/widget/cocoa/nsMenuItemIconX.mm index e84817fa9854..aea85c49cc12 100644 --- a/widget/cocoa/nsMenuItemIconX.mm +++ b/widget/cocoa/nsMenuItemIconX.mm @@ -316,7 +316,7 @@ nsMenuItemIconX::LoadIcon(nsIURI* aIconURI) // not exposed to web content nsresult rv = loader->LoadImage(aIconURI, nullptr, nullptr, nullptr, loadGroup, this, nullptr, nsIRequest::LOAD_NORMAL, nullptr, - nullptr, getter_AddRefs(mIconRequest)); + nullptr, EmptyString(), getter_AddRefs(mIconRequest)); if (NS_FAILED(rv)) return rv; // We need to request the icon be decoded (bug 573583, bug 705516).