Bumping manifests a=b2g-bump
[gecko.git] / dom / base / nsPerformance.cpp
blob6a6bb0c82506e70bb4213be68464dd8f38a0cad2
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"
7 #include "nsCOMPtr.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"
14 #include "nsIURI.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),
35 mChannel(aChannel),
36 mFetchStart(0.0),
37 mZeroTime(aZeroTime),
38 mReportCrossOriginResources(true)
40 MOZ_ASSERT(aPerformance, "Parent performance object should be provided");
41 SetIsDOMBinding();
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).
45 if (aHttpChannel) {
46 CheckRedirectCrossOrigin(aHttpChannel);
50 nsPerformanceTiming::~nsPerformanceTiming()
54 DOMHighResTimeStamp
55 nsPerformanceTiming::FetchStartHighRes()
57 if (!mFetchStart) {
58 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
59 return mZeroTime;
61 TimeStamp stamp;
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)
67 : 0.0;
69 return mFetchStart;
72 DOMTimeMilliSec
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
81 void
82 nsPerformanceTiming::CheckRedirectCrossOrigin(nsIHttpChannel* aResourceChannel)
84 if (!IsInitialized()) {
85 return;
87 uint16_t redirectCount;
88 mChannel->GetRedirectCount(&redirectCount);
89 if (redirectCount == 0) {
90 return;
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;
102 bool
103 nsPerformanceTiming::IsSameOriginAsReferral() const
105 return mReportCrossOriginResources;
108 uint16_t
109 nsPerformanceTiming::GetRedirectCount() const
111 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
112 return 0;
114 bool sameOrigin;
115 mChannel->GetAllRedirectsSameOrigin(&sameOrigin);
116 if (!sameOrigin) {
117 return 0;
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
134 DOMHighResTimeStamp
135 nsPerformanceTiming::RedirectStartHighRes()
137 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
138 return mZeroTime;
140 mozilla::TimeStamp stamp;
141 mChannel->GetRedirectStart(&stamp);
142 return TimeStampToDOMHighResOrFetchStart(stamp);
145 DOMTimeMilliSec
146 nsPerformanceTiming::RedirectStart()
148 if (!IsInitialized()) {
149 return mZeroTime;
151 // We have to check if all the redirect URIs had the same origin (since there
152 // is no check in RedirectStartHighRes())
153 bool sameOrigin;
154 mChannel->GetAllRedirectsSameOrigin(&sameOrigin);
155 if (sameOrigin) {
156 return static_cast<int64_t>(RedirectStartHighRes());
158 return 0;
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
171 DOMHighResTimeStamp
172 nsPerformanceTiming::RedirectEndHighRes()
174 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
175 return mZeroTime;
177 mozilla::TimeStamp stamp;
178 mChannel->GetRedirectEnd(&stamp);
179 return TimeStampToDOMHighResOrFetchStart(stamp);
182 DOMTimeMilliSec
183 nsPerformanceTiming::RedirectEnd()
185 if (!IsInitialized()) {
186 return mZeroTime;
188 // We have to check if all the redirect URIs had the same origin (since there
189 // is no check in RedirectEndHighRes())
190 bool sameOrigin;
191 mChannel->GetAllRedirectsSameOrigin(&sameOrigin);
192 if (sameOrigin) {
193 return static_cast<int64_t>(RedirectEndHighRes());
195 return 0;
198 DOMHighResTimeStamp
199 nsPerformanceTiming::DomainLookupStartHighRes()
201 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
202 return mZeroTime;
204 mozilla::TimeStamp stamp;
205 mChannel->GetDomainLookupStart(&stamp);
206 return TimeStampToDOMHighResOrFetchStart(stamp);
209 DOMTimeMilliSec
210 nsPerformanceTiming::DomainLookupStart()
212 return static_cast<int64_t>(DomainLookupStartHighRes());
215 DOMHighResTimeStamp
216 nsPerformanceTiming::DomainLookupEndHighRes()
218 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
219 return mZeroTime;
221 mozilla::TimeStamp stamp;
222 mChannel->GetDomainLookupEnd(&stamp);
223 return TimeStampToDOMHighResOrFetchStart(stamp);
226 DOMTimeMilliSec
227 nsPerformanceTiming::DomainLookupEnd()
229 return static_cast<int64_t>(DomainLookupEndHighRes());
232 DOMHighResTimeStamp
233 nsPerformanceTiming::ConnectStartHighRes()
235 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
236 return mZeroTime;
238 mozilla::TimeStamp stamp;
239 mChannel->GetConnectStart(&stamp);
240 return TimeStampToDOMHighResOrFetchStart(stamp);
243 DOMTimeMilliSec
244 nsPerformanceTiming::ConnectStart()
246 return static_cast<int64_t>(ConnectStartHighRes());
249 DOMHighResTimeStamp
250 nsPerformanceTiming::ConnectEndHighRes()
252 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
253 return mZeroTime;
255 mozilla::TimeStamp stamp;
256 mChannel->GetConnectEnd(&stamp);
257 return TimeStampToDOMHighResOrFetchStart(stamp);
260 DOMTimeMilliSec
261 nsPerformanceTiming::ConnectEnd()
263 return static_cast<int64_t>(ConnectEndHighRes());
266 DOMHighResTimeStamp
267 nsPerformanceTiming::RequestStartHighRes()
269 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
270 return mZeroTime;
272 mozilla::TimeStamp stamp;
273 mChannel->GetRequestStart(&stamp);
274 return TimeStampToDOMHighResOrFetchStart(stamp);
277 DOMTimeMilliSec
278 nsPerformanceTiming::RequestStart()
280 return static_cast<int64_t>(RequestStartHighRes());
283 DOMHighResTimeStamp
284 nsPerformanceTiming::ResponseStartHighRes()
286 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
287 return mZeroTime;
289 mozilla::TimeStamp stamp;
290 mChannel->GetResponseStart(&stamp);
291 mozilla::TimeStamp cacheStamp;
292 mChannel->GetCacheReadStart(&cacheStamp);
293 if (stamp.IsNull() || (!cacheStamp.IsNull() && cacheStamp < stamp)) {
294 stamp = cacheStamp;
296 return TimeStampToDOMHighResOrFetchStart(stamp);
299 DOMTimeMilliSec
300 nsPerformanceTiming::ResponseStart()
302 return static_cast<int64_t>(ResponseStartHighRes());
305 DOMHighResTimeStamp
306 nsPerformanceTiming::ResponseEndHighRes()
308 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
309 return mZeroTime;
311 mozilla::TimeStamp stamp;
312 mChannel->GetResponseEnd(&stamp);
313 mozilla::TimeStamp cacheStamp;
314 mChannel->GetCacheReadEnd(&cacheStamp);
315 if (stamp.IsNull() || (!cacheStamp.IsNull() && cacheStamp < stamp)) {
316 stamp = cacheStamp;
318 return TimeStampToDOMHighResOrFetchStart(stamp);
321 DOMTimeMilliSec
322 nsPerformanceTiming::ResponseEnd()
324 return static_cast<int64_t>(ResponseEndHighRes());
327 bool
328 nsPerformanceTiming::IsInitialized() const
330 return !!mChannel;
333 JSObject*
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");
349 SetIsDOMBinding();
352 nsPerformanceNavigation::~nsPerformanceNavigation()
356 JSObject*
357 nsPerformanceNavigation::WrapObject(JSContext *cx)
359 return dom::PerformanceNavigationBinding::Wrap(cx, this);
363 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsPerformance, DOMEventTargetHelper,
364 mWindow, mTiming,
365 mNavigation, mEntries,
366 mParentPerformance)
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),
375 mWindow(aWindow),
376 mDOMTiming(aDOMTiming),
377 mChannel(aChannel),
378 mParentPerformance(aParentPerformance),
379 mPrimaryBufferSize(kDefaultBufferSize)
381 MOZ_ASSERT(aWindow, "Parent window object should be provided");
382 SetIsDOMBinding();
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)
396 nsPerformanceTiming*
397 nsPerformance::Timing()
399 if (!mTiming) {
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
403 // start value.
404 mTiming = new nsPerformanceTiming(this, mChannel, nullptr,
405 mDOMTiming->GetNavigationStart());
407 return mTiming;
410 void
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()
428 if (!mNavigation) {
429 mNavigation = new nsPerformanceNavigation(this);
431 return mNavigation;
434 DOMHighResTimeStamp
435 nsPerformance::Now()
437 return GetDOMTiming()->TimeStampToDOMHighRes(mozilla::TimeStamp::Now());
440 JSObject*
441 nsPerformance::WrapObject(JSContext *cx)
443 return dom::PerformanceBinding::Wrap(cx, this);
446 void
447 nsPerformance::GetEntries(nsTArray<nsRefPtr<PerformanceEntry> >& retval)
449 MOZ_ASSERT(NS_IsMainThread());
451 retval = mEntries;
454 void
455 nsPerformance::GetEntriesByType(const nsAString& entryType,
456 nsTArray<nsRefPtr<PerformanceEntry> >& retval)
458 MOZ_ASSERT(NS_IsMainThread());
460 retval.Clear();
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]);
469 void
470 nsPerformance::GetEntriesByName(const nsAString& name,
471 const mozilla::dom::Optional<nsAString>& entryType,
472 nsTArray<nsRefPtr<PerformanceEntry> >& retval)
474 MOZ_ASSERT(NS_IsMainThread());
476 retval.Clear();
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]);
487 void
488 nsPerformance::ClearResourceTimings()
490 MOZ_ASSERT(NS_IsMainThread());
491 mEntries.Clear();
494 void
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.
505 void
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()) {
512 return;
515 // Don't add the entry if the buffer is full
516 if (mEntries.Length() >= mPrimaryBufferSize) {
517 return;
520 if (channel && timedChannel) {
521 nsAutoCString name;
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
552 // ("other") value.
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();
567 bool
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();
577 bool
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();