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