1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsPerformance.h"
8 #include "nsIHttpChannel.h"
9 #include "nsITimedChannel.h"
10 #include "nsDOMNavigationTiming.h"
11 #include "nsContentUtils.h"
12 #include "nsIScriptSecurityManager.h"
13 #include "nsIDOMWindow.h"
15 #include "PerformanceEntry.h"
16 #include "PerformanceResourceTiming.h"
17 #include "mozilla/dom/PerformanceBinding.h"
18 #include "mozilla/dom/PerformanceTimingBinding.h"
19 #include "mozilla/dom/PerformanceNavigationBinding.h"
20 #include "mozilla/TimeStamp.h"
21 #include "nsThreadUtils.h"
23 using namespace mozilla
;
25 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPerformanceTiming
, mPerformance
)
27 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsPerformanceTiming
, AddRef
)
28 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsPerformanceTiming
, Release
)
30 nsPerformanceTiming::nsPerformanceTiming(nsPerformance
* aPerformance
,
31 nsITimedChannel
* aChannel
,
32 nsIHttpChannel
* aHttpChannel
,
33 DOMHighResTimeStamp aZeroTime
)
34 : mPerformance(aPerformance
),
38 mReportCrossOriginResources(true)
40 MOZ_ASSERT(aPerformance
, "Parent performance object should be provided");
42 // The aHttpChannel argument is null if this nsPerformanceTiming object
43 // is being used for the navigation timing (document) and has a non-null
44 // value for the resource timing (any resources within the page).
46 CheckRedirectCrossOrigin(aHttpChannel
);
50 nsPerformanceTiming::~nsPerformanceTiming()
55 nsPerformanceTiming::FetchStartHighRes()
58 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
62 mChannel
->GetAsyncOpen(&stamp
);
63 MOZ_ASSERT(!stamp
.IsNull(), "The fetch start time stamp should always be "
64 "valid if the performance timing is enabled");
65 mFetchStart
= (!stamp
.IsNull())
66 ? TimeStampToDOMHighRes(stamp
)
73 nsPerformanceTiming::FetchStart()
75 return static_cast<int64_t>(FetchStartHighRes());
78 // This method will implement the timing allow check algorithm
79 // http://w3c-test.org/webperf/specs/ResourceTiming/#timing-allow-check
80 // https://bugzilla.mozilla.org/show_bug.cgi?id=936814
82 nsPerformanceTiming::CheckRedirectCrossOrigin(nsIHttpChannel
* aResourceChannel
)
84 if (!IsInitialized()) {
87 uint16_t redirectCount
;
88 mChannel
->GetRedirectCount(&redirectCount
);
89 if (redirectCount
== 0) {
92 nsCOMPtr
<nsIURI
> resourceURI
, referrerURI
;
93 aResourceChannel
->GetReferrer(getter_AddRefs(referrerURI
));
94 aResourceChannel
->GetURI(getter_AddRefs(resourceURI
));
95 nsIScriptSecurityManager
* ssm
= nsContentUtils::GetSecurityManager();
96 nsresult rv
= ssm
->CheckSameOriginURI(resourceURI
, referrerURI
, false);
97 if (!NS_SUCCEEDED(rv
)) {
98 mReportCrossOriginResources
= false;
103 nsPerformanceTiming::IsSameOriginAsReferral() const
105 return mReportCrossOriginResources
;
109 nsPerformanceTiming::GetRedirectCount() const
111 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
115 mChannel
->GetAllRedirectsSameOrigin(&sameOrigin
);
119 uint16_t redirectCount
;
120 mChannel
->GetRedirectCount(&redirectCount
);
121 return redirectCount
;
125 * RedirectStartHighRes() is used by both the navigation timing and the
126 * resource timing. Since, navigation timing and resource timing check and
127 * interpret cross-domain redirects in a different manner,
128 * RedirectStartHighRes() will make no checks for cross-domain redirect.
129 * It's up to the consumers of this method (nsPerformanceTiming::RedirectStart()
130 * and PerformanceResourceTiming::RedirectStart() to make such verifications.
132 * @return a valid timing if the Performance Timing is enabled
135 nsPerformanceTiming::RedirectStartHighRes()
137 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
140 mozilla::TimeStamp stamp
;
141 mChannel
->GetRedirectStart(&stamp
);
142 return TimeStampToDOMHighResOrFetchStart(stamp
);
146 nsPerformanceTiming::RedirectStart()
148 if (!IsInitialized()) {
151 // We have to check if all the redirect URIs had the same origin (since there
152 // is no check in RedirectStartHighRes())
154 mChannel
->GetAllRedirectsSameOrigin(&sameOrigin
);
156 return static_cast<int64_t>(RedirectStartHighRes());
162 * RedirectEndHighRes() is used by both the navigation timing and the resource
163 * timing. Since, navigation timing and resource timing check and interpret
164 * cross-domain redirects in a different manner, RedirectEndHighRes() will make
165 * no checks for cross-domain redirect. It's up to the consumers of this method
166 * (nsPerformanceTiming::RedirectEnd() and
167 * PerformanceResourceTiming::RedirectEnd() to make such verifications.
169 * @return a valid timing if the Performance Timing is enabled
172 nsPerformanceTiming::RedirectEndHighRes()
174 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
177 mozilla::TimeStamp stamp
;
178 mChannel
->GetRedirectEnd(&stamp
);
179 return TimeStampToDOMHighResOrFetchStart(stamp
);
183 nsPerformanceTiming::RedirectEnd()
185 if (!IsInitialized()) {
188 // We have to check if all the redirect URIs had the same origin (since there
189 // is no check in RedirectEndHighRes())
191 mChannel
->GetAllRedirectsSameOrigin(&sameOrigin
);
193 return static_cast<int64_t>(RedirectEndHighRes());
199 nsPerformanceTiming::DomainLookupStartHighRes()
201 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
204 mozilla::TimeStamp stamp
;
205 mChannel
->GetDomainLookupStart(&stamp
);
206 return TimeStampToDOMHighResOrFetchStart(stamp
);
210 nsPerformanceTiming::DomainLookupStart()
212 return static_cast<int64_t>(DomainLookupStartHighRes());
216 nsPerformanceTiming::DomainLookupEndHighRes()
218 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
221 mozilla::TimeStamp stamp
;
222 mChannel
->GetDomainLookupEnd(&stamp
);
223 return TimeStampToDOMHighResOrFetchStart(stamp
);
227 nsPerformanceTiming::DomainLookupEnd()
229 return static_cast<int64_t>(DomainLookupEndHighRes());
233 nsPerformanceTiming::ConnectStartHighRes()
235 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
238 mozilla::TimeStamp stamp
;
239 mChannel
->GetConnectStart(&stamp
);
240 return TimeStampToDOMHighResOrFetchStart(stamp
);
244 nsPerformanceTiming::ConnectStart()
246 return static_cast<int64_t>(ConnectStartHighRes());
250 nsPerformanceTiming::ConnectEndHighRes()
252 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
255 mozilla::TimeStamp stamp
;
256 mChannel
->GetConnectEnd(&stamp
);
257 return TimeStampToDOMHighResOrFetchStart(stamp
);
261 nsPerformanceTiming::ConnectEnd()
263 return static_cast<int64_t>(ConnectEndHighRes());
267 nsPerformanceTiming::RequestStartHighRes()
269 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
272 mozilla::TimeStamp stamp
;
273 mChannel
->GetRequestStart(&stamp
);
274 return TimeStampToDOMHighResOrFetchStart(stamp
);
278 nsPerformanceTiming::RequestStart()
280 return static_cast<int64_t>(RequestStartHighRes());
284 nsPerformanceTiming::ResponseStartHighRes()
286 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
289 mozilla::TimeStamp stamp
;
290 mChannel
->GetResponseStart(&stamp
);
291 mozilla::TimeStamp cacheStamp
;
292 mChannel
->GetCacheReadStart(&cacheStamp
);
293 if (stamp
.IsNull() || (!cacheStamp
.IsNull() && cacheStamp
< stamp
)) {
296 return TimeStampToDOMHighResOrFetchStart(stamp
);
300 nsPerformanceTiming::ResponseStart()
302 return static_cast<int64_t>(ResponseStartHighRes());
306 nsPerformanceTiming::ResponseEndHighRes()
308 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
311 mozilla::TimeStamp stamp
;
312 mChannel
->GetResponseEnd(&stamp
);
313 mozilla::TimeStamp cacheStamp
;
314 mChannel
->GetCacheReadEnd(&cacheStamp
);
315 if (stamp
.IsNull() || (!cacheStamp
.IsNull() && cacheStamp
< stamp
)) {
318 return TimeStampToDOMHighResOrFetchStart(stamp
);
322 nsPerformanceTiming::ResponseEnd()
324 return static_cast<int64_t>(ResponseEndHighRes());
328 nsPerformanceTiming::IsInitialized() const
334 nsPerformanceTiming::WrapObject(JSContext
*cx
)
336 return dom::PerformanceTimingBinding::Wrap(cx
, this);
340 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPerformanceNavigation
, mPerformance
)
342 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsPerformanceNavigation
, AddRef
)
343 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsPerformanceNavigation
, Release
)
345 nsPerformanceNavigation::nsPerformanceNavigation(nsPerformance
* aPerformance
)
346 : mPerformance(aPerformance
)
348 MOZ_ASSERT(aPerformance
, "Parent performance object should be provided");
352 nsPerformanceNavigation::~nsPerformanceNavigation()
357 nsPerformanceNavigation::WrapObject(JSContext
*cx
)
359 return dom::PerformanceNavigationBinding::Wrap(cx
, this);
363 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsPerformance
, DOMEventTargetHelper
,
365 mNavigation
, mEntries
,
367 NS_IMPL_ADDREF_INHERITED(nsPerformance
, DOMEventTargetHelper
)
368 NS_IMPL_RELEASE_INHERITED(nsPerformance
, DOMEventTargetHelper
)
370 nsPerformance::nsPerformance(nsPIDOMWindow
* aWindow
,
371 nsDOMNavigationTiming
* aDOMTiming
,
372 nsITimedChannel
* aChannel
,
373 nsPerformance
* aParentPerformance
)
374 : DOMEventTargetHelper(aWindow
),
376 mDOMTiming(aDOMTiming
),
378 mParentPerformance(aParentPerformance
),
379 mPrimaryBufferSize(kDefaultBufferSize
)
381 MOZ_ASSERT(aWindow
, "Parent window object should be provided");
385 nsPerformance::~nsPerformance()
389 // QueryInterface implementation for nsPerformance
390 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPerformance
)
391 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
392 NS_INTERFACE_MAP_ENTRY(nsISupports
)
393 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
397 nsPerformance::Timing()
400 // For navigation timing, the third argument (an nsIHtttpChannel) is null
401 // since the cross-domain redirect were already checked.
402 // The last argument (zero time) for performance.timing is the navigation
404 mTiming
= new nsPerformanceTiming(this, mChannel
, nullptr,
405 mDOMTiming
->GetNavigationStart());
411 nsPerformance::DispatchBufferFullEvent()
413 nsCOMPtr
<nsIDOMEvent
> event
;
414 nsresult rv
= NS_NewDOMEvent(getter_AddRefs(event
), this, nullptr, nullptr);
415 if (NS_SUCCEEDED(rv
)) {
416 // it bubbles, and it isn't cancelable
417 rv
= event
->InitEvent(NS_LITERAL_STRING("resourcetimingbufferfull"), true, false);
418 if (NS_SUCCEEDED(rv
)) {
419 event
->SetTrusted(true);
420 DispatchDOMEvent(nullptr, event
, nullptr, nullptr);
425 nsPerformanceNavigation
*
426 nsPerformance::Navigation()
429 mNavigation
= new nsPerformanceNavigation(this);
437 return GetDOMTiming()->TimeStampToDOMHighRes(mozilla::TimeStamp::Now());
441 nsPerformance::WrapObject(JSContext
*cx
)
443 return dom::PerformanceBinding::Wrap(cx
, this);
447 nsPerformance::GetEntries(nsTArray
<nsRefPtr
<PerformanceEntry
> >& retval
)
449 MOZ_ASSERT(NS_IsMainThread());
455 nsPerformance::GetEntriesByType(const nsAString
& entryType
,
456 nsTArray
<nsRefPtr
<PerformanceEntry
> >& retval
)
458 MOZ_ASSERT(NS_IsMainThread());
461 uint32_t count
= mEntries
.Length();
462 for (uint32_t i
= 0 ; i
< count
; i
++) {
463 if (mEntries
[i
]->GetEntryType().Equals(entryType
)) {
464 retval
.AppendElement(mEntries
[i
]);
470 nsPerformance::GetEntriesByName(const nsAString
& name
,
471 const mozilla::dom::Optional
<nsAString
>& entryType
,
472 nsTArray
<nsRefPtr
<PerformanceEntry
> >& retval
)
474 MOZ_ASSERT(NS_IsMainThread());
477 uint32_t count
= mEntries
.Length();
478 for (uint32_t i
= 0 ; i
< count
; i
++) {
479 if (mEntries
[i
]->GetName().Equals(name
) &&
480 (!entryType
.WasPassed() ||
481 mEntries
[i
]->GetEntryType().Equals(entryType
.Value()))) {
482 retval
.AppendElement(mEntries
[i
]);
488 nsPerformance::ClearResourceTimings()
490 MOZ_ASSERT(NS_IsMainThread());
495 nsPerformance::SetResourceTimingBufferSize(uint64_t maxSize
)
497 MOZ_ASSERT(NS_IsMainThread());
498 mPrimaryBufferSize
= maxSize
;
502 * An entry should be added only after the resource is loaded.
503 * This method is not thread safe and can only be called on the main thread.
506 nsPerformance::AddEntry(nsIHttpChannel
* channel
,
507 nsITimedChannel
* timedChannel
)
509 MOZ_ASSERT(NS_IsMainThread());
510 // Check if resource timing is prefed off.
511 if (!nsContentUtils::IsResourceTimingEnabled()) {
515 // Don't add the entry if the buffer is full
516 if (mEntries
.Length() >= mPrimaryBufferSize
) {
520 if (channel
&& timedChannel
) {
522 nsAutoString initiatorType
;
523 nsCOMPtr
<nsIURI
> originalURI
;
525 timedChannel
->GetInitiatorType(initiatorType
);
527 // According to the spec, "The name attribute must return the resolved URL
528 // of the requested resource. This attribute must not change even if the
529 // fetch redirected to a different URL."
530 channel
->GetOriginalURI(getter_AddRefs(originalURI
));
531 originalURI
->GetSpec(name
);
532 NS_ConvertUTF8toUTF16
entryName(name
);
534 // The nsITimedChannel argument will be used to gather all the timings.
535 // The nsIHttpChannel argument will be used to check if any cross-origin
536 // redirects occurred.
537 // The last argument is the "zero time" (offset). Since we don't want
538 // any offset for the resource timing, this will be set to "0" - the
539 // resource timing returns a relative timing (no offset).
540 nsRefPtr
<nsPerformanceTiming
> performanceTiming
=
541 new nsPerformanceTiming(this, timedChannel
, channel
,
544 // The PerformanceResourceTiming object will use the nsPerformanceTiming
545 // object to get all the required timings.
546 nsRefPtr
<dom::PerformanceResourceTiming
> performanceEntry
=
547 new dom::PerformanceResourceTiming(performanceTiming
, this);
549 performanceEntry
->SetName(entryName
);
550 performanceEntry
->SetEntryType(NS_LITERAL_STRING("resource"));
551 // If the initiator type had no valid value, then set it to the default
553 if (initiatorType
.IsEmpty()) {
554 initiatorType
= NS_LITERAL_STRING("other");
556 performanceEntry
->SetInitiatorType(initiatorType
);
558 mEntries
.InsertElementSorted(performanceEntry
,
559 PerformanceEntryComparator());
560 if (mEntries
.Length() >= mPrimaryBufferSize
) {
561 // call onresourcetimingbufferfull
562 DispatchBufferFullEvent();
568 nsPerformance::PerformanceEntryComparator::Equals(
569 const PerformanceEntry
* aElem1
,
570 const PerformanceEntry
* aElem2
) const
572 NS_ABORT_IF_FALSE(aElem1
&& aElem2
,
573 "Trying to compare null performance entries");
574 return aElem1
->StartTime() == aElem2
->StartTime();
578 nsPerformance::PerformanceEntryComparator::LessThan(
579 const PerformanceEntry
* aElem1
,
580 const PerformanceEntry
* aElem2
) const
582 NS_ABORT_IF_FALSE(aElem1
&& aElem2
,
583 "Trying to compare null performance entries");
584 return aElem1
->StartTime() < aElem2
->StartTime();