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 #ifndef mozilla_dom_PerformanceTiming_h
8 #define mozilla_dom_PerformanceTiming_h
9 
10 #include "mozilla/Attributes.h"
11 #include "nsContentUtils.h"
12 #include "nsDOMNavigationTiming.h"
13 #include "nsRFPService.h"
14 #include "nsWrapperCache.h"
15 #include "Performance.h"
16 
17 class nsIHttpChannel;
18 class nsITimedChannel;
19 
20 namespace mozilla {
21 namespace dom {
22 
23 class PerformanceTiming;
24 
25 class PerformanceTimingData final {
26   friend class PerformanceTiming;
27 
28  public:
29   // This can return null.
30   static PerformanceTimingData* Create(nsITimedChannel* aChannel,
31                                        nsIHttpChannel* aHttpChannel,
32                                        DOMHighResTimeStamp aZeroTime,
33                                        nsAString& aInitiatorType,
34                                        nsAString& aEntryName);
35 
36   PerformanceTimingData(nsITimedChannel* aChannel, nsIHttpChannel* aHttpChannel,
37                         DOMHighResTimeStamp aZeroTime);
38 
39   void SetPropertiesFromHttpChannel(nsIHttpChannel* aHttpChannel);
40 
IsInitialized()41   bool IsInitialized() const { return mInitialized; }
42 
NextHopProtocol()43   const nsString& NextHopProtocol() const { return mNextHopProtocol; }
44 
TransferSize()45   uint64_t TransferSize() const { return mTimingAllowed ? mTransferSize : 0; }
46 
EncodedBodySize()47   uint64_t EncodedBodySize() const {
48     return mTimingAllowed ? mEncodedBodySize : 0;
49   }
50 
DecodedBodySize()51   uint64_t DecodedBodySize() const {
52     return mTimingAllowed ? mDecodedBodySize : 0;
53   }
54 
55   /**
56    * @param   aStamp
57    *          The TimeStamp recorded for a specific event. This TimeStamp can
58    *          be null.
59    * @return  the duration of an event with a given TimeStamp, relative to the
60    *          navigationStart TimeStamp (the moment the user landed on the
61    *          page), if the given TimeStamp is valid. Otherwise, it will return
62    *          the FetchStart timing value.
63    */
TimeStampToReducedDOMHighResOrFetchStart(Performance * aPerformance,TimeStamp aStamp)64   inline DOMHighResTimeStamp TimeStampToReducedDOMHighResOrFetchStart(
65       Performance* aPerformance, TimeStamp aStamp) {
66     MOZ_ASSERT(aPerformance);
67 
68     if (aStamp.IsNull()) {
69       return FetchStartHighRes(aPerformance);
70     }
71 
72     DOMHighResTimeStamp rawTimestamp =
73         TimeStampToDOMHighRes(aPerformance, aStamp);
74     if (aPerformance->IsSystemPrincipal()) {
75       return rawTimestamp;
76     }
77 
78     return nsRFPService::ReduceTimePrecisionAsMSecs(
79         rawTimestamp, aPerformance->GetRandomTimelineSeed());
80   }
81 
82   /**
83    * The nsITimedChannel records an absolute timestamp for each event.
84    * The nsDOMNavigationTiming will record the moment when the user landed on
85    * the page. This is a window.performance unique timestamp, so it can be used
86    * for all the events (navigation timing and resource timing events).
87    *
88    * The algorithm operates in 2 steps:
89    * 1. The first step is to subtract the two timestamps: the argument (the
90    * event's timestamp) and the navigation start timestamp. This will result in
91    * a relative timestamp of the event (relative to the navigation start -
92    * window.performance.timing.navigationStart).
93    * 2. The second step is to add any required offset (the mZeroTime). For now,
94    * this offset value is either 0 (for the resource timing), or equal to
95    * "performance.navigationStart" (for navigation timing).
96    * For the resource timing, mZeroTime is set to 0, causing the result to be a
97    * relative time.
98    * For the navigation timing, mZeroTime is set to
99    * "performance.navigationStart" causing the result be an absolute time.
100    *
101    * @param   aStamp
102    *          The TimeStamp recorded for a specific event. This TimeStamp can't
103    *          be null.
104    * @return  number of milliseconds value as one of:
105    * - relative to the navigation start time, time the user has landed on the
106    *   page
107    * - an absolute wall clock time since the unix epoch
108    */
TimeStampToDOMHighRes(Performance * aPerformance,TimeStamp aStamp)109   inline DOMHighResTimeStamp TimeStampToDOMHighRes(Performance* aPerformance,
110                                                    TimeStamp aStamp) const {
111     MOZ_ASSERT(aPerformance);
112     MOZ_ASSERT(!aStamp.IsNull());
113 
114     TimeDuration duration = aStamp - aPerformance->CreationTimeStamp();
115     return duration.ToMilliseconds() + mZeroTime;
116   }
117 
118   // The last channel's AsyncOpen time.  This may occur before the FetchStart
119   // in some cases.
120   DOMHighResTimeStamp AsyncOpenHighRes(Performance* aPerformance);
121 
122   // High resolution (used by resource timing)
123   DOMHighResTimeStamp WorkerStartHighRes(Performance* aPerformance);
124   DOMHighResTimeStamp FetchStartHighRes(Performance* aPerformance);
125   DOMHighResTimeStamp RedirectStartHighRes(Performance* aPerformance);
126   DOMHighResTimeStamp RedirectEndHighRes(Performance* aPerformance);
127   DOMHighResTimeStamp DomainLookupStartHighRes(Performance* aPerformance);
128   DOMHighResTimeStamp DomainLookupEndHighRes(Performance* aPerformance);
129   DOMHighResTimeStamp ConnectStartHighRes(Performance* aPerformance);
130   DOMHighResTimeStamp SecureConnectionStartHighRes(Performance* aPerformance);
131   DOMHighResTimeStamp ConnectEndHighRes(Performance* aPerformance);
132   DOMHighResTimeStamp RequestStartHighRes(Performance* aPerformance);
133   DOMHighResTimeStamp ResponseStartHighRes(Performance* aPerformance);
134   DOMHighResTimeStamp ResponseEndHighRes(Performance* aPerformance);
135 
ZeroTime()136   DOMHighResTimeStamp ZeroTime() const { return mZeroTime; }
137 
RedirectCountReal()138   uint8_t RedirectCountReal() const { return mRedirectCount; }
139   uint8_t GetRedirectCount() const;
140 
AllRedirectsSameOrigin()141   bool AllRedirectsSameOrigin() const { return mAllRedirectsSameOrigin; }
142 
143   // If this is false the values of redirectStart/End will be 0 This is false if
144   // no redirects occured, or if any of the responses failed the
145   // timing-allow-origin check in HttpBaseChannel::TimingAllowCheck
146   bool ShouldReportCrossOriginRedirect() const;
147 
148   // Cached result of CheckAllowedOrigin. If false, security sensitive
149   // attributes of the resourceTiming object will be set to 0
TimingAllowed()150   bool TimingAllowed() const { return mTimingAllowed; }
151 
152  private:
153   // Checks if the resource is either same origin as the page that started
154   // the load, or if the response contains the Timing-Allow-Origin header
155   // with a value of * or matching the domain of the loading Principal
156   bool CheckAllowedOrigin(nsIHttpChannel* aResourceChannel,
157                           nsITimedChannel* aChannel);
158 
159   nsString mNextHopProtocol;
160 
161   TimeStamp mAsyncOpen;
162   TimeStamp mRedirectStart;
163   TimeStamp mRedirectEnd;
164   TimeStamp mDomainLookupStart;
165   TimeStamp mDomainLookupEnd;
166   TimeStamp mConnectStart;
167   TimeStamp mSecureConnectionStart;
168   TimeStamp mConnectEnd;
169   TimeStamp mRequestStart;
170   TimeStamp mResponseStart;
171   TimeStamp mCacheReadStart;
172   TimeStamp mResponseEnd;
173   TimeStamp mCacheReadEnd;
174 
175   // ServiceWorker interception timing information
176   TimeStamp mWorkerStart;
177   TimeStamp mWorkerRequestStart;
178   TimeStamp mWorkerResponseEnd;
179 
180   // This is an offset that will be added to each timing ([ms] resolution).
181   // There are only 2 possible values: (1) logicaly equal to navigationStart
182   // TimeStamp (results are absolute timstamps - wallclock); (2) "0" (results
183   // are relative to the navigation start).
184   DOMHighResTimeStamp mZeroTime;
185 
186   DOMHighResTimeStamp mFetchStart;
187 
188   uint64_t mEncodedBodySize;
189   uint64_t mTransferSize;
190   uint64_t mDecodedBodySize;
191 
192   uint8_t mRedirectCount;
193 
194   bool mAllRedirectsSameOrigin;
195 
196   // If the resourceTiming object should have non-zero redirectStart and
197   // redirectEnd attributes. It is false if there were no redirects, or if any
198   // of the responses didn't pass the timing-allow-check
199   bool mReportCrossOriginRedirect;
200 
201   bool mSecureConnection;
202 
203   bool mTimingAllowed;
204 
205   bool mInitialized;
206 };
207 
208 // Script "performance.timing" object
209 class PerformanceTiming final : public nsWrapperCache {
210  public:
211   /**
212    * @param   aPerformance
213    *          The performance object (the JS parent).
214    *          This will allow access to "window.performance.timing" attribute
215    * for the navigation timing (can't be null).
216    * @param   aChannel
217    *          An nsITimedChannel used to gather all the networking timings by
218    * both the navigation timing and the resource timing (can't be null).
219    * @param   aHttpChannel
220    *          An nsIHttpChannel (the resource's http channel).
221    *          This will be used by the resource timing cross-domain check
222    *          algorithm.
223    *          Argument is null for the navigation timing (navigation timing uses
224    *          another algorithm for the cross-domain redirects).
225    * @param   aZeroTime
226    *          The offset that will be added to the timestamp of each event. This
227    *          argument should be equal to performance.navigationStart for
228    *          navigation timing and "0" for the resource timing.
229    */
230   PerformanceTiming(Performance* aPerformance, nsITimedChannel* aChannel,
231                     nsIHttpChannel* aHttpChannel,
232                     DOMHighResTimeStamp aZeroTime);
233   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PerformanceTiming)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(PerformanceTiming)234   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(PerformanceTiming)
235 
236   nsDOMNavigationTiming* GetDOMTiming() const {
237     return mPerformance->GetDOMTiming();
238   }
239 
GetParentObject()240   Performance* GetParentObject() const { return mPerformance; }
241 
242   virtual JSObject* WrapObject(JSContext* cx,
243                                JS::Handle<JSObject*> aGivenProto) override;
244 
245   // PerformanceNavigation WebIDL methods
NavigationStart()246   DOMTimeMilliSec NavigationStart() const {
247     if (!nsContentUtils::IsPerformanceTimingEnabled() ||
248         nsContentUtils::ShouldResistFingerprinting()) {
249       return 0;
250     }
251     if (mPerformance->IsSystemPrincipal()) {
252       return GetDOMTiming()->GetNavigationStart();
253     }
254     return nsRFPService::ReduceTimePrecisionAsMSecs(
255         GetDOMTiming()->GetNavigationStart(),
256         mPerformance->GetRandomTimelineSeed());
257   }
258 
UnloadEventStart()259   DOMTimeMilliSec UnloadEventStart() {
260     if (!nsContentUtils::IsPerformanceTimingEnabled() ||
261         nsContentUtils::ShouldResistFingerprinting()) {
262       return 0;
263     }
264     if (mPerformance->IsSystemPrincipal()) {
265       return GetDOMTiming()->GetUnloadEventStart();
266     }
267     return nsRFPService::ReduceTimePrecisionAsMSecs(
268         GetDOMTiming()->GetUnloadEventStart(),
269         mPerformance->GetRandomTimelineSeed());
270   }
271 
UnloadEventEnd()272   DOMTimeMilliSec UnloadEventEnd() {
273     if (!nsContentUtils::IsPerformanceTimingEnabled() ||
274         nsContentUtils::ShouldResistFingerprinting()) {
275       return 0;
276     }
277     if (mPerformance->IsSystemPrincipal()) {
278       return GetDOMTiming()->GetUnloadEventEnd();
279     }
280     return nsRFPService::ReduceTimePrecisionAsMSecs(
281         GetDOMTiming()->GetUnloadEventEnd(),
282         mPerformance->GetRandomTimelineSeed());
283   }
284 
285   // Low resolution (used by navigation timing)
286   DOMTimeMilliSec FetchStart();
287   DOMTimeMilliSec RedirectStart();
288   DOMTimeMilliSec RedirectEnd();
289   DOMTimeMilliSec DomainLookupStart();
290   DOMTimeMilliSec DomainLookupEnd();
291   DOMTimeMilliSec ConnectStart();
292   DOMTimeMilliSec SecureConnectionStart();
293   DOMTimeMilliSec ConnectEnd();
294   DOMTimeMilliSec RequestStart();
295   DOMTimeMilliSec ResponseStart();
296   DOMTimeMilliSec ResponseEnd();
297 
DomLoading()298   DOMTimeMilliSec DomLoading() {
299     if (!nsContentUtils::IsPerformanceTimingEnabled() ||
300         nsContentUtils::ShouldResistFingerprinting()) {
301       return 0;
302     }
303     if (mPerformance->IsSystemPrincipal()) {
304       return GetDOMTiming()->GetDomLoading();
305     }
306     return nsRFPService::ReduceTimePrecisionAsMSecs(
307         GetDOMTiming()->GetDomLoading(), mPerformance->GetRandomTimelineSeed());
308   }
309 
DomInteractive()310   DOMTimeMilliSec DomInteractive() const {
311     if (!nsContentUtils::IsPerformanceTimingEnabled() ||
312         nsContentUtils::ShouldResistFingerprinting()) {
313       return 0;
314     }
315     if (mPerformance->IsSystemPrincipal()) {
316       return GetDOMTiming()->GetDomInteractive();
317     }
318     return nsRFPService::ReduceTimePrecisionAsMSecs(
319         GetDOMTiming()->GetDomInteractive(),
320         mPerformance->GetRandomTimelineSeed());
321   }
322 
DomContentLoadedEventStart()323   DOMTimeMilliSec DomContentLoadedEventStart() const {
324     if (!nsContentUtils::IsPerformanceTimingEnabled() ||
325         nsContentUtils::ShouldResistFingerprinting()) {
326       return 0;
327     }
328     if (mPerformance->IsSystemPrincipal()) {
329       return GetDOMTiming()->GetDomContentLoadedEventStart();
330     }
331     return nsRFPService::ReduceTimePrecisionAsMSecs(
332         GetDOMTiming()->GetDomContentLoadedEventStart(),
333         mPerformance->GetRandomTimelineSeed());
334   }
335 
DomContentLoadedEventEnd()336   DOMTimeMilliSec DomContentLoadedEventEnd() const {
337     if (!nsContentUtils::IsPerformanceTimingEnabled() ||
338         nsContentUtils::ShouldResistFingerprinting()) {
339       return 0;
340     }
341     if (mPerformance->IsSystemPrincipal()) {
342       return GetDOMTiming()->GetDomContentLoadedEventEnd();
343     }
344     return nsRFPService::ReduceTimePrecisionAsMSecs(
345         GetDOMTiming()->GetDomContentLoadedEventEnd(),
346         mPerformance->GetRandomTimelineSeed());
347   }
348 
DomComplete()349   DOMTimeMilliSec DomComplete() const {
350     if (!nsContentUtils::IsPerformanceTimingEnabled() ||
351         nsContentUtils::ShouldResistFingerprinting()) {
352       return 0;
353     }
354     if (mPerformance->IsSystemPrincipal()) {
355       return GetDOMTiming()->GetDomComplete();
356     }
357     return nsRFPService::ReduceTimePrecisionAsMSecs(
358         GetDOMTiming()->GetDomComplete(),
359         mPerformance->GetRandomTimelineSeed());
360   }
361 
LoadEventStart()362   DOMTimeMilliSec LoadEventStart() const {
363     if (!nsContentUtils::IsPerformanceTimingEnabled() ||
364         nsContentUtils::ShouldResistFingerprinting()) {
365       return 0;
366     }
367     if (mPerformance->IsSystemPrincipal()) {
368       return GetDOMTiming()->GetLoadEventStart();
369     }
370     return nsRFPService::ReduceTimePrecisionAsMSecs(
371         GetDOMTiming()->GetLoadEventStart(),
372         mPerformance->GetRandomTimelineSeed());
373   }
374 
LoadEventEnd()375   DOMTimeMilliSec LoadEventEnd() const {
376     if (!nsContentUtils::IsPerformanceTimingEnabled() ||
377         nsContentUtils::ShouldResistFingerprinting()) {
378       return 0;
379     }
380     if (mPerformance->IsSystemPrincipal()) {
381       return GetDOMTiming()->GetLoadEventEnd();
382     }
383     return nsRFPService::ReduceTimePrecisionAsMSecs(
384         GetDOMTiming()->GetLoadEventEnd(),
385         mPerformance->GetRandomTimelineSeed());
386   }
387 
TimeToNonBlankPaint()388   DOMTimeMilliSec TimeToNonBlankPaint() const {
389     if (!nsContentUtils::IsPerformanceTimingEnabled() ||
390         nsContentUtils::ShouldResistFingerprinting()) {
391       return 0;
392     }
393     if (mPerformance->IsSystemPrincipal()) {
394       return GetDOMTiming()->GetTimeToNonBlankPaint();
395     }
396     return nsRFPService::ReduceTimePrecisionAsMSecs(
397         GetDOMTiming()->GetTimeToNonBlankPaint(),
398         mPerformance->GetRandomTimelineSeed());
399   }
400 
Data()401   PerformanceTimingData* Data() const { return mTimingData.get(); }
402 
403  private:
404   ~PerformanceTiming();
405 
406   bool IsTopLevelContentDocument() const;
407 
408   RefPtr<Performance> mPerformance;
409 
410   UniquePtr<PerformanceTimingData> mTimingData;
411 };
412 
413 }  // namespace dom
414 }  // namespace mozilla
415 
416 #endif  // mozilla_dom_PerformanceTiming_h
417