Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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_1(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)
39 {
40 MOZ_ASSERT(aPerformance, "Parent performance object should be provided");
41 SetIsDOMBinding();
43 if (!nsContentUtils::IsPerformanceTimingEnabled()) {
44 mZeroTime = 0;
45 }
47 // The aHttpChannel argument is null if this nsPerformanceTiming object
48 // is being used for the navigation timing (document) and has a non-null
49 // value for the resource timing (any resources within the page).
50 if (aHttpChannel) {
51 CheckRedirectCrossOrigin(aHttpChannel);
52 }
53 }
55 nsPerformanceTiming::~nsPerformanceTiming()
56 {
57 }
59 DOMHighResTimeStamp
60 nsPerformanceTiming::FetchStartHighRes()
61 {
62 if (!mFetchStart) {
63 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
64 return mZeroTime;
65 }
66 TimeStamp stamp;
67 mChannel->GetAsyncOpen(&stamp);
68 MOZ_ASSERT(!stamp.IsNull(), "The fetch start time stamp should always be "
69 "valid if the performance timing is enabled");
70 mFetchStart = (!stamp.IsNull())
71 ? TimeStampToDOMHighRes(stamp)
72 : 0.0;
73 }
74 return mFetchStart;
75 }
77 DOMTimeMilliSec
78 nsPerformanceTiming::FetchStart()
79 {
80 return static_cast<int64_t>(FetchStartHighRes());
81 }
83 // This method will implement the timing allow check algorithm
84 // http://w3c-test.org/webperf/specs/ResourceTiming/#timing-allow-check
85 // https://bugzilla.mozilla.org/show_bug.cgi?id=936814
86 void
87 nsPerformanceTiming::CheckRedirectCrossOrigin(nsIHttpChannel* aResourceChannel)
88 {
89 if (!IsInitialized()) {
90 return;
91 }
92 uint16_t redirectCount;
93 mChannel->GetRedirectCount(&redirectCount);
94 if (redirectCount == 0) {
95 return;
96 }
97 nsCOMPtr<nsIURI> resourceURI, referrerURI;
98 aResourceChannel->GetReferrer(getter_AddRefs(referrerURI));
99 aResourceChannel->GetURI(getter_AddRefs(resourceURI));
100 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
101 nsresult rv = ssm->CheckSameOriginURI(resourceURI, referrerURI, false);
102 if (!NS_SUCCEEDED(rv)) {
103 mReportCrossOriginResources = false;
104 }
105 }
107 bool
108 nsPerformanceTiming::IsSameOriginAsReferral() const
109 {
110 return mReportCrossOriginResources;
111 }
113 uint16_t
114 nsPerformanceTiming::GetRedirectCount() const
115 {
116 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
117 return 0;
118 }
119 bool sameOrigin;
120 mChannel->GetAllRedirectsSameOrigin(&sameOrigin);
121 if (!sameOrigin) {
122 return 0;
123 }
124 uint16_t redirectCount;
125 mChannel->GetRedirectCount(&redirectCount);
126 return redirectCount;
127 }
129 /**
130 * RedirectStartHighRes() is used by both the navigation timing and the
131 * resource timing. Since, navigation timing and resource timing check and
132 * interpret cross-domain redirects in a different manner,
133 * RedirectStartHighRes() will make no checks for cross-domain redirect.
134 * It's up to the consumers of this method (nsPerformanceTiming::RedirectStart()
135 * and PerformanceResourceTiming::RedirectStart() to make such verifications.
136 *
137 * @return a valid timing if the Performance Timing is enabled
138 */
139 DOMHighResTimeStamp
140 nsPerformanceTiming::RedirectStartHighRes()
141 {
142 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
143 return mZeroTime;
144 }
145 mozilla::TimeStamp stamp;
146 mChannel->GetRedirectStart(&stamp);
147 return TimeStampToDOMHighResOrFetchStart(stamp);
148 }
150 DOMTimeMilliSec
151 nsPerformanceTiming::RedirectStart()
152 {
153 if (!IsInitialized()) {
154 return mZeroTime;
155 }
156 // We have to check if all the redirect URIs had the same origin (since there
157 // is no check in RedirectStartHighRes())
158 bool sameOrigin;
159 mChannel->GetAllRedirectsSameOrigin(&sameOrigin);
160 if (sameOrigin) {
161 return static_cast<int64_t>(RedirectStartHighRes());
162 }
163 return 0;
164 }
166 /**
167 * RedirectEndHighRes() is used by both the navigation timing and the resource
168 * timing. Since, navigation timing and resource timing check and interpret
169 * cross-domain redirects in a different manner, RedirectEndHighRes() will make
170 * no checks for cross-domain redirect. It's up to the consumers of this method
171 * (nsPerformanceTiming::RedirectEnd() and
172 * PerformanceResourceTiming::RedirectEnd() to make such verifications.
173 *
174 * @return a valid timing if the Performance Timing is enabled
175 */
176 DOMHighResTimeStamp
177 nsPerformanceTiming::RedirectEndHighRes()
178 {
179 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
180 return mZeroTime;
181 }
182 mozilla::TimeStamp stamp;
183 mChannel->GetRedirectEnd(&stamp);
184 return TimeStampToDOMHighResOrFetchStart(stamp);
185 }
187 DOMTimeMilliSec
188 nsPerformanceTiming::RedirectEnd()
189 {
190 if (!IsInitialized()) {
191 return mZeroTime;
192 }
193 // We have to check if all the redirect URIs had the same origin (since there
194 // is no check in RedirectEndHighRes())
195 bool sameOrigin;
196 mChannel->GetAllRedirectsSameOrigin(&sameOrigin);
197 if (sameOrigin) {
198 return static_cast<int64_t>(RedirectEndHighRes());
199 }
200 return 0;
201 }
203 DOMHighResTimeStamp
204 nsPerformanceTiming::DomainLookupStartHighRes()
205 {
206 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
207 return mZeroTime;
208 }
209 mozilla::TimeStamp stamp;
210 mChannel->GetDomainLookupStart(&stamp);
211 return TimeStampToDOMHighResOrFetchStart(stamp);
212 }
214 DOMTimeMilliSec
215 nsPerformanceTiming::DomainLookupStart()
216 {
217 return static_cast<int64_t>(DomainLookupStartHighRes());
218 }
220 DOMHighResTimeStamp
221 nsPerformanceTiming::DomainLookupEndHighRes()
222 {
223 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
224 return mZeroTime;
225 }
226 mozilla::TimeStamp stamp;
227 mChannel->GetDomainLookupEnd(&stamp);
228 return TimeStampToDOMHighResOrFetchStart(stamp);
229 }
231 DOMTimeMilliSec
232 nsPerformanceTiming::DomainLookupEnd()
233 {
234 return static_cast<int64_t>(DomainLookupEndHighRes());
235 }
237 DOMHighResTimeStamp
238 nsPerformanceTiming::ConnectStartHighRes()
239 {
240 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
241 return mZeroTime;
242 }
243 mozilla::TimeStamp stamp;
244 mChannel->GetConnectStart(&stamp);
245 return TimeStampToDOMHighResOrFetchStart(stamp);
246 }
248 DOMTimeMilliSec
249 nsPerformanceTiming::ConnectStart()
250 {
251 return static_cast<int64_t>(ConnectStartHighRes());
252 }
254 DOMHighResTimeStamp
255 nsPerformanceTiming::ConnectEndHighRes()
256 {
257 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
258 return mZeroTime;
259 }
260 mozilla::TimeStamp stamp;
261 mChannel->GetConnectEnd(&stamp);
262 return TimeStampToDOMHighResOrFetchStart(stamp);
263 }
265 DOMTimeMilliSec
266 nsPerformanceTiming::ConnectEnd()
267 {
268 return static_cast<int64_t>(ConnectEndHighRes());
269 }
271 DOMHighResTimeStamp
272 nsPerformanceTiming::RequestStartHighRes()
273 {
274 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
275 return mZeroTime;
276 }
277 mozilla::TimeStamp stamp;
278 mChannel->GetRequestStart(&stamp);
279 return TimeStampToDOMHighResOrFetchStart(stamp);
280 }
282 DOMTimeMilliSec
283 nsPerformanceTiming::RequestStart()
284 {
285 return static_cast<int64_t>(RequestStartHighRes());
286 }
288 DOMHighResTimeStamp
289 nsPerformanceTiming::ResponseStartHighRes()
290 {
291 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
292 return mZeroTime;
293 }
294 mozilla::TimeStamp stamp;
295 mChannel->GetResponseStart(&stamp);
296 mozilla::TimeStamp cacheStamp;
297 mChannel->GetCacheReadStart(&cacheStamp);
298 if (stamp.IsNull() || (!cacheStamp.IsNull() && cacheStamp < stamp)) {
299 stamp = cacheStamp;
300 }
301 return TimeStampToDOMHighResOrFetchStart(stamp);
302 }
304 DOMTimeMilliSec
305 nsPerformanceTiming::ResponseStart()
306 {
307 return static_cast<int64_t>(ResponseStartHighRes());
308 }
310 DOMHighResTimeStamp
311 nsPerformanceTiming::ResponseEndHighRes()
312 {
313 if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
314 return mZeroTime;
315 }
316 mozilla::TimeStamp stamp;
317 mChannel->GetResponseEnd(&stamp);
318 mozilla::TimeStamp cacheStamp;
319 mChannel->GetCacheReadEnd(&cacheStamp);
320 if (stamp.IsNull() || (!cacheStamp.IsNull() && cacheStamp < stamp)) {
321 stamp = cacheStamp;
322 }
323 return TimeStampToDOMHighResOrFetchStart(stamp);
324 }
326 DOMTimeMilliSec
327 nsPerformanceTiming::ResponseEnd()
328 {
329 return static_cast<int64_t>(ResponseEndHighRes());
330 }
332 bool
333 nsPerformanceTiming::IsInitialized() const
334 {
335 return !!mChannel;
336 }
338 JSObject*
339 nsPerformanceTiming::WrapObject(JSContext *cx)
340 {
341 return dom::PerformanceTimingBinding::Wrap(cx, this);
342 }
345 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(nsPerformanceNavigation, mPerformance)
347 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsPerformanceNavigation, AddRef)
348 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsPerformanceNavigation, Release)
350 nsPerformanceNavigation::nsPerformanceNavigation(nsPerformance* aPerformance)
351 : mPerformance(aPerformance)
352 {
353 MOZ_ASSERT(aPerformance, "Parent performance object should be provided");
354 SetIsDOMBinding();
355 }
357 nsPerformanceNavigation::~nsPerformanceNavigation()
358 {
359 }
361 JSObject*
362 nsPerformanceNavigation::WrapObject(JSContext *cx)
363 {
364 return dom::PerformanceNavigationBinding::Wrap(cx, this);
365 }
368 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_5(nsPerformance,
369 mWindow, mTiming,
370 mNavigation, mEntries,
371 mParentPerformance)
372 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPerformance)
373 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPerformance)
375 nsPerformance::nsPerformance(nsIDOMWindow* aWindow,
376 nsDOMNavigationTiming* aDOMTiming,
377 nsITimedChannel* aChannel,
378 nsPerformance* aParentPerformance)
379 : mWindow(aWindow),
380 mDOMTiming(aDOMTiming),
381 mChannel(aChannel),
382 mParentPerformance(aParentPerformance),
383 mBufferSizeSet(kDefaultBufferSize),
384 mPrimaryBufferSize(kDefaultBufferSize)
385 {
386 MOZ_ASSERT(aWindow, "Parent window object should be provided");
387 SetIsDOMBinding();
388 }
390 nsPerformance::~nsPerformance()
391 {
392 }
394 // QueryInterface implementation for nsPerformance
395 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPerformance)
396 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
397 NS_INTERFACE_MAP_ENTRY(nsISupports)
398 NS_INTERFACE_MAP_END
401 nsPerformanceTiming*
402 nsPerformance::Timing()
403 {
404 if (!mTiming) {
405 // For navigation timing, the third argument (an nsIHtttpChannel) is null
406 // since the cross-domain redirect were already checked.
407 // The last argument (zero time) for performance.timing is the navigation
408 // start value.
409 mTiming = new nsPerformanceTiming(this, mChannel, nullptr,
410 mDOMTiming->GetNavigationStart());
411 }
412 return mTiming;
413 }
415 nsPerformanceNavigation*
416 nsPerformance::Navigation()
417 {
418 if (!mNavigation) {
419 mNavigation = new nsPerformanceNavigation(this);
420 }
421 return mNavigation;
422 }
424 DOMHighResTimeStamp
425 nsPerformance::Now()
426 {
427 return GetDOMTiming()->TimeStampToDOMHighRes(mozilla::TimeStamp::Now());
428 }
430 JSObject*
431 nsPerformance::WrapObject(JSContext *cx)
432 {
433 return dom::PerformanceBinding::Wrap(cx, this);
434 }
436 void
437 nsPerformance::GetEntries(nsTArray<nsRefPtr<PerformanceEntry> >& retval)
438 {
439 MOZ_ASSERT(NS_IsMainThread());
441 retval.Clear();
442 uint32_t count = mEntries.Length();
443 if (count > mPrimaryBufferSize) {
444 count = mPrimaryBufferSize;
445 }
446 retval.AppendElements(mEntries.Elements(), count);
447 }
449 void
450 nsPerformance::GetEntriesByType(const nsAString& entryType,
451 nsTArray<nsRefPtr<PerformanceEntry> >& retval)
452 {
453 MOZ_ASSERT(NS_IsMainThread());
455 retval.Clear();
456 uint32_t count = mEntries.Length();
457 for (uint32_t i = 0 ; i < count && i < mPrimaryBufferSize ; i++) {
458 if (mEntries[i]->GetEntryType().Equals(entryType)) {
459 retval.AppendElement(mEntries[i]);
460 }
461 }
462 }
464 void
465 nsPerformance::GetEntriesByName(const nsAString& name,
466 const mozilla::dom::Optional<nsAString>& entryType,
467 nsTArray<nsRefPtr<PerformanceEntry> >& retval)
468 {
469 MOZ_ASSERT(NS_IsMainThread());
471 retval.Clear();
472 uint32_t count = mEntries.Length();
473 for (uint32_t i = 0 ; i < count && i < mPrimaryBufferSize ; i++) {
474 if (mEntries[i]->GetName().Equals(name) &&
475 (!entryType.WasPassed() ||
476 mEntries[i]->GetEntryType().Equals(entryType.Value()))) {
477 retval.AppendElement(mEntries[i]);
478 }
479 }
480 }
482 void
483 nsPerformance::ClearResourceTimings()
484 {
485 MOZ_ASSERT(NS_IsMainThread());
486 mPrimaryBufferSize = mBufferSizeSet;
487 mEntries.Clear();
488 }
490 void
491 nsPerformance::SetResourceTimingBufferSize(uint64_t maxSize)
492 {
493 MOZ_ASSERT(NS_IsMainThread());
494 mBufferSizeSet = maxSize;
495 if (mBufferSizeSet < mEntries.Length()) {
496 // call onresourcetimingbufferfull
497 // https://bugzilla.mozilla.org/show_bug.cgi?id=936813
498 }
499 }
501 /**
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.
504 */
505 void
506 nsPerformance::AddEntry(nsIHttpChannel* channel,
507 nsITimedChannel* timedChannel)
508 {
509 MOZ_ASSERT(NS_IsMainThread());
510 // Check if resource timing is prefed off.
511 if (!nsContentUtils::IsResourceTimingEnabled()) {
512 return;
513 }
514 if (channel && timedChannel) {
515 nsAutoCString name;
516 nsAutoString initiatorType;
517 nsCOMPtr<nsIURI> originalURI;
519 timedChannel->GetInitiatorType(initiatorType);
521 // According to the spec, "The name attribute must return the resolved URL
522 // of the requested resource. This attribute must not change even if the
523 // fetch redirected to a different URL."
524 channel->GetOriginalURI(getter_AddRefs(originalURI));
525 originalURI->GetSpec(name);
526 NS_ConvertUTF8toUTF16 entryName(name);
528 // The nsITimedChannel argument will be used to gather all the timings.
529 // The nsIHttpChannel argument will be used to check if any cross-origin
530 // redirects occurred.
531 // The last argument is the "zero time" (offset). Since we don't want
532 // any offset for the resource timing, this will be set to "0" - the
533 // resource timing returns a relative timing (no offset).
534 nsRefPtr<nsPerformanceTiming> performanceTiming =
535 new nsPerformanceTiming(this, timedChannel, channel,
536 0);
538 // The PerformanceResourceTiming object will use the nsPerformanceTiming
539 // object to get all the required timings.
540 nsRefPtr<dom::PerformanceResourceTiming> performanceEntry =
541 new dom::PerformanceResourceTiming(performanceTiming, this);
543 performanceEntry->SetName(entryName);
544 performanceEntry->SetEntryType(NS_LITERAL_STRING("resource"));
545 // If the initiator type had no valid value, then set it to the default
546 // ("other") value.
547 if (initiatorType.IsEmpty()) {
548 initiatorType = NS_LITERAL_STRING("other");
549 }
550 performanceEntry->SetInitiatorType(initiatorType);
552 mEntries.InsertElementSorted(performanceEntry,
553 PerformanceEntryComparator());
554 if (mEntries.Length() > mPrimaryBufferSize) {
555 // call onresourcetimingbufferfull
556 // https://bugzilla.mozilla.org/show_bug.cgi?id=936813
557 }
558 }
559 }
561 bool
562 nsPerformance::PerformanceEntryComparator::Equals(
563 const PerformanceEntry* aElem1,
564 const PerformanceEntry* aElem2) const
565 {
566 NS_ABORT_IF_FALSE(aElem1 && aElem2,
567 "Trying to compare null performance entries");
568 return aElem1->StartTime() == aElem2->StartTime();
569 }
571 bool
572 nsPerformance::PerformanceEntryComparator::LessThan(
573 const PerformanceEntry* aElem1,
574 const PerformanceEntry* aElem2) const
575 {
576 NS_ABORT_IF_FALSE(aElem1 && aElem2,
577 "Trying to compare null performance entries");
578 return aElem1->StartTime() < aElem2->StartTime();
579 }