Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "PerformanceTiming.h"
8 : #include "mozilla/dom/PerformanceTimingBinding.h"
9 : #include "mozilla/Telemetry.h"
10 : #include "nsIDocShell.h"
11 : #include "nsIDocShellTreeItem.h"
12 : #include "nsIDocument.h"
13 : #include "nsITimedChannel.h"
14 :
15 : namespace mozilla {
16 : namespace dom {
17 :
18 0 : NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PerformanceTiming, mPerformance)
19 :
20 0 : NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(PerformanceTiming, AddRef)
21 0 : NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(PerformanceTiming, Release)
22 :
23 2 : PerformanceTiming::PerformanceTiming(Performance* aPerformance,
24 : nsITimedChannel* aChannel,
25 : nsIHttpChannel* aHttpChannel,
26 2 : DOMHighResTimeStamp aZeroTime)
27 : : mPerformance(aPerformance),
28 : mFetchStart(0.0),
29 : mZeroTime(aZeroTime),
30 : mRedirectCount(0),
31 : mTimingAllowed(true),
32 : mAllRedirectsSameOrigin(true),
33 2 : mInitialized(!!aChannel),
34 4 : mReportCrossOriginRedirect(true)
35 : {
36 2 : MOZ_ASSERT(aPerformance, "Parent performance object should be provided");
37 :
38 4 : if (!nsContentUtils::IsPerformanceTimingEnabled() ||
39 2 : nsContentUtils::ShouldResistFingerprinting()) {
40 0 : mZeroTime = 0;
41 : }
42 :
43 : // The aHttpChannel argument is null if this PerformanceTiming object is
44 : // being used for navigation timing (which is only relevant for documents).
45 : // It has a non-null value if this PerformanceTiming object is being used
46 : // for resource timing, which can include document loads, both toplevel and
47 : // in subframes, and resources linked from a document.
48 2 : if (aHttpChannel) {
49 2 : mTimingAllowed = CheckAllowedOrigin(aHttpChannel, aChannel);
50 2 : bool redirectsPassCheck = false;
51 2 : aChannel->GetAllRedirectsPassTimingAllowCheck(&redirectsPassCheck);
52 2 : mReportCrossOriginRedirect = mTimingAllowed && redirectsPassCheck;
53 : }
54 :
55 2 : InitializeTimingInfo(aChannel);
56 :
57 : // Non-null aHttpChannel implies that this PerformanceTiming object is being
58 : // used for subresources, which is irrelevant to this probe.
59 2 : if (!aHttpChannel &&
60 2 : nsContentUtils::IsPerformanceTimingEnabled() &&
61 0 : IsTopLevelContentDocument()) {
62 0 : Telemetry::Accumulate(Telemetry::TIME_TO_RESPONSE_START_MS,
63 0 : ResponseStartHighRes() - mZeroTime);
64 : }
65 2 : }
66 :
67 : // Copy the timing info from the channel so we don't need to keep the channel
68 : // alive just to get the timestamps.
69 : void
70 2 : PerformanceTiming::InitializeTimingInfo(nsITimedChannel* aChannel)
71 : {
72 2 : if (aChannel) {
73 2 : aChannel->GetAsyncOpen(&mAsyncOpen);
74 2 : aChannel->GetAllRedirectsSameOrigin(&mAllRedirectsSameOrigin);
75 2 : aChannel->GetRedirectCount(&mRedirectCount);
76 2 : aChannel->GetRedirectStart(&mRedirectStart);
77 2 : aChannel->GetRedirectEnd(&mRedirectEnd);
78 2 : aChannel->GetDomainLookupStart(&mDomainLookupStart);
79 2 : aChannel->GetDomainLookupEnd(&mDomainLookupEnd);
80 2 : aChannel->GetConnectStart(&mConnectStart);
81 2 : aChannel->GetConnectEnd(&mConnectEnd);
82 2 : aChannel->GetRequestStart(&mRequestStart);
83 2 : aChannel->GetResponseStart(&mResponseStart);
84 2 : aChannel->GetCacheReadStart(&mCacheReadStart);
85 2 : aChannel->GetResponseEnd(&mResponseEnd);
86 2 : aChannel->GetCacheReadEnd(&mCacheReadEnd);
87 : }
88 2 : }
89 :
90 0 : PerformanceTiming::~PerformanceTiming()
91 : {
92 0 : }
93 :
94 : DOMHighResTimeStamp
95 2 : PerformanceTiming::FetchStartHighRes()
96 : {
97 2 : if (!mFetchStart) {
98 4 : if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
99 2 : nsContentUtils::ShouldResistFingerprinting()) {
100 0 : return mZeroTime;
101 : }
102 2 : MOZ_ASSERT(!mAsyncOpen.IsNull(), "The fetch start time stamp should always be "
103 : "valid if the performance timing is enabled");
104 4 : mFetchStart = (!mAsyncOpen.IsNull())
105 2 : ? TimeStampToDOMHighRes(mAsyncOpen)
106 : : 0.0;
107 : }
108 2 : return mFetchStart;
109 : }
110 :
111 : DOMTimeMilliSec
112 0 : PerformanceTiming::FetchStart()
113 : {
114 0 : return static_cast<int64_t>(FetchStartHighRes());
115 : }
116 :
117 : bool
118 2 : PerformanceTiming::CheckAllowedOrigin(nsIHttpChannel* aResourceChannel,
119 : nsITimedChannel* aChannel)
120 : {
121 2 : if (!IsInitialized()) {
122 0 : return false;
123 : }
124 :
125 : // Check that the current document passes the ckeck.
126 4 : nsCOMPtr<nsILoadInfo> loadInfo;
127 2 : aResourceChannel->GetLoadInfo(getter_AddRefs(loadInfo));
128 2 : if (!loadInfo) {
129 0 : return false;
130 : }
131 :
132 : // TYPE_DOCUMENT loads have no loadingPrincipal. And that's OK, because we
133 : // never actually need to have a performance timing entry for TYPE_DOCUMENT
134 : // loads.
135 2 : if (loadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DOCUMENT) {
136 0 : return false;
137 : }
138 :
139 4 : nsCOMPtr<nsIPrincipal> principal = loadInfo->LoadingPrincipal();
140 :
141 : // Check if the resource is either same origin as the page that started
142 : // the load, or if the response contains the proper Timing-Allow-Origin
143 : // header with the domain of the page that started the load.
144 2 : return aChannel->TimingAllowCheck(principal);
145 : }
146 :
147 : bool
148 0 : PerformanceTiming::TimingAllowed() const
149 : {
150 0 : return mTimingAllowed;
151 : }
152 :
153 : uint16_t
154 0 : PerformanceTiming::GetRedirectCount() const
155 : {
156 0 : if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
157 0 : nsContentUtils::ShouldResistFingerprinting()) {
158 0 : return 0;
159 : }
160 0 : if (!mAllRedirectsSameOrigin) {
161 0 : return 0;
162 : }
163 0 : return mRedirectCount;
164 : }
165 :
166 : bool
167 0 : PerformanceTiming::ShouldReportCrossOriginRedirect() const
168 : {
169 0 : if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
170 0 : nsContentUtils::ShouldResistFingerprinting()) {
171 0 : return false;
172 : }
173 :
174 : // If the redirect count is 0, or if one of the cross-origin
175 : // redirects doesn't have the proper Timing-Allow-Origin header,
176 : // then RedirectStart and RedirectEnd will be set to zero
177 0 : return (mRedirectCount != 0) && mReportCrossOriginRedirect;
178 : }
179 :
180 : /**
181 : * RedirectStartHighRes() is used by both the navigation timing and the
182 : * resource timing. Since, navigation timing and resource timing check and
183 : * interpret cross-domain redirects in a different manner,
184 : * RedirectStartHighRes() will make no checks for cross-domain redirect.
185 : * It's up to the consumers of this method (PerformanceTiming::RedirectStart()
186 : * and PerformanceResourceTiming::RedirectStart() to make such verifications.
187 : *
188 : * @return a valid timing if the Performance Timing is enabled
189 : */
190 : DOMHighResTimeStamp
191 2 : PerformanceTiming::RedirectStartHighRes()
192 : {
193 4 : if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
194 2 : nsContentUtils::ShouldResistFingerprinting()) {
195 0 : return mZeroTime;
196 : }
197 2 : return TimeStampToDOMHighResOrFetchStart(mRedirectStart);
198 : }
199 :
200 : DOMTimeMilliSec
201 0 : PerformanceTiming::RedirectStart()
202 : {
203 0 : if (!IsInitialized()) {
204 0 : return 0;
205 : }
206 : // We have to check if all the redirect URIs had the same origin (since there
207 : // is no check in RedirectStartHighRes())
208 0 : if (mAllRedirectsSameOrigin && mRedirectCount) {
209 0 : return static_cast<int64_t>(RedirectStartHighRes());
210 : }
211 0 : return 0;
212 : }
213 :
214 : /**
215 : * RedirectEndHighRes() is used by both the navigation timing and the resource
216 : * timing. Since, navigation timing and resource timing check and interpret
217 : * cross-domain redirects in a different manner, RedirectEndHighRes() will make
218 : * no checks for cross-domain redirect. It's up to the consumers of this method
219 : * (PerformanceTiming::RedirectEnd() and
220 : * PerformanceResourceTiming::RedirectEnd() to make such verifications.
221 : *
222 : * @return a valid timing if the Performance Timing is enabled
223 : */
224 : DOMHighResTimeStamp
225 0 : PerformanceTiming::RedirectEndHighRes()
226 : {
227 0 : if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
228 0 : nsContentUtils::ShouldResistFingerprinting()) {
229 0 : return mZeroTime;
230 : }
231 0 : return TimeStampToDOMHighResOrFetchStart(mRedirectEnd);
232 : }
233 :
234 : DOMTimeMilliSec
235 0 : PerformanceTiming::RedirectEnd()
236 : {
237 0 : if (!IsInitialized()) {
238 0 : return 0;
239 : }
240 : // We have to check if all the redirect URIs had the same origin (since there
241 : // is no check in RedirectEndHighRes())
242 0 : if (mAllRedirectsSameOrigin && mRedirectCount) {
243 0 : return static_cast<int64_t>(RedirectEndHighRes());
244 : }
245 0 : return 0;
246 : }
247 :
248 : DOMHighResTimeStamp
249 0 : PerformanceTiming::DomainLookupStartHighRes()
250 : {
251 0 : if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
252 0 : nsContentUtils::ShouldResistFingerprinting()) {
253 0 : return mZeroTime;
254 : }
255 0 : return TimeStampToDOMHighResOrFetchStart(mDomainLookupStart);
256 : }
257 :
258 : DOMTimeMilliSec
259 0 : PerformanceTiming::DomainLookupStart()
260 : {
261 0 : return static_cast<int64_t>(DomainLookupStartHighRes());
262 : }
263 :
264 : DOMHighResTimeStamp
265 0 : PerformanceTiming::DomainLookupEndHighRes()
266 : {
267 0 : if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
268 0 : nsContentUtils::ShouldResistFingerprinting()) {
269 0 : return mZeroTime;
270 : }
271 : // Bug 1155008 - nsHttpTransaction is racy. Return DomainLookupStart when null
272 0 : return mDomainLookupEnd.IsNull() ? DomainLookupStartHighRes()
273 0 : : TimeStampToDOMHighRes(mDomainLookupEnd);
274 : }
275 :
276 : DOMTimeMilliSec
277 0 : PerformanceTiming::DomainLookupEnd()
278 : {
279 0 : return static_cast<int64_t>(DomainLookupEndHighRes());
280 : }
281 :
282 : DOMHighResTimeStamp
283 0 : PerformanceTiming::ConnectStartHighRes()
284 : {
285 0 : if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
286 0 : nsContentUtils::ShouldResistFingerprinting()) {
287 0 : return mZeroTime;
288 : }
289 0 : return mConnectStart.IsNull() ? DomainLookupEndHighRes()
290 0 : : TimeStampToDOMHighRes(mConnectStart);
291 : }
292 :
293 : DOMTimeMilliSec
294 0 : PerformanceTiming::ConnectStart()
295 : {
296 0 : return static_cast<int64_t>(ConnectStartHighRes());
297 : }
298 :
299 : DOMHighResTimeStamp
300 0 : PerformanceTiming::ConnectEndHighRes()
301 : {
302 0 : if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
303 0 : nsContentUtils::ShouldResistFingerprinting()) {
304 0 : return mZeroTime;
305 : }
306 : // Bug 1155008 - nsHttpTransaction is racy. Return ConnectStart when null
307 0 : return mConnectEnd.IsNull() ? ConnectStartHighRes()
308 0 : : TimeStampToDOMHighRes(mConnectEnd);
309 : }
310 :
311 : DOMTimeMilliSec
312 0 : PerformanceTiming::ConnectEnd()
313 : {
314 0 : return static_cast<int64_t>(ConnectEndHighRes());
315 : }
316 :
317 : DOMHighResTimeStamp
318 0 : PerformanceTiming::RequestStartHighRes()
319 : {
320 0 : if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
321 0 : nsContentUtils::ShouldResistFingerprinting()) {
322 0 : return mZeroTime;
323 : }
324 0 : return TimeStampToDOMHighResOrFetchStart(mRequestStart);
325 : }
326 :
327 : DOMTimeMilliSec
328 0 : PerformanceTiming::RequestStart()
329 : {
330 0 : return static_cast<int64_t>(RequestStartHighRes());
331 : }
332 :
333 : DOMHighResTimeStamp
334 0 : PerformanceTiming::ResponseStartHighRes()
335 : {
336 0 : if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
337 0 : nsContentUtils::ShouldResistFingerprinting()) {
338 0 : return mZeroTime;
339 : }
340 0 : if (mResponseStart.IsNull() ||
341 0 : (!mCacheReadStart.IsNull() && mCacheReadStart < mResponseStart)) {
342 0 : mResponseStart = mCacheReadStart;
343 : }
344 0 : return TimeStampToDOMHighResOrFetchStart(mResponseStart);
345 : }
346 :
347 : DOMTimeMilliSec
348 0 : PerformanceTiming::ResponseStart()
349 : {
350 0 : return static_cast<int64_t>(ResponseStartHighRes());
351 : }
352 :
353 : DOMHighResTimeStamp
354 0 : PerformanceTiming::ResponseEndHighRes()
355 : {
356 0 : if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
357 0 : nsContentUtils::ShouldResistFingerprinting()) {
358 0 : return mZeroTime;
359 : }
360 0 : if (mResponseEnd.IsNull() ||
361 0 : (!mCacheReadEnd.IsNull() && mCacheReadEnd < mResponseEnd)) {
362 0 : mResponseEnd = mCacheReadEnd;
363 : }
364 : // Bug 1155008 - nsHttpTransaction is racy. Return ResponseStart when null
365 0 : return mResponseEnd.IsNull() ? ResponseStartHighRes()
366 0 : : TimeStampToDOMHighRes(mResponseEnd);
367 : }
368 :
369 : DOMTimeMilliSec
370 0 : PerformanceTiming::ResponseEnd()
371 : {
372 0 : return static_cast<int64_t>(ResponseEndHighRes());
373 : }
374 :
375 : bool
376 6 : PerformanceTiming::IsInitialized() const
377 : {
378 6 : return mInitialized;
379 : }
380 :
381 : JSObject*
382 0 : PerformanceTiming::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
383 : {
384 0 : return PerformanceTimingBinding::Wrap(cx, this, aGivenProto);
385 : }
386 :
387 : bool
388 0 : PerformanceTiming::IsTopLevelContentDocument() const
389 : {
390 0 : nsCOMPtr<nsIDocument> document = mPerformance->GetDocumentIfCurrent();
391 0 : if (!document) {
392 0 : return false;
393 : }
394 0 : nsCOMPtr<nsIDocShell> docShell = document->GetDocShell();
395 0 : if (!docShell) {
396 0 : return false;
397 : }
398 0 : nsCOMPtr<nsIDocShellTreeItem> rootItem;
399 0 : Unused << docShell->GetSameTypeRootTreeItem(getter_AddRefs(rootItem));
400 0 : if (rootItem.get() != static_cast<nsIDocShellTreeItem*>(docShell.get())) {
401 0 : return false;
402 : }
403 0 : return rootItem->ItemType() == nsIDocShellTreeItem::typeContent;
404 : }
405 :
406 : } // dom namespace
407 : } // mozilla namespace
|