PerformanceTiming.cpp (29503B)
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 9 #include "mozilla/BasePrincipal.h" 10 #include "mozilla/StaticPrefs_dom.h" 11 #include "mozilla/dom/BrowsingContext.h" 12 #include "mozilla/dom/Document.h" 13 #include "mozilla/dom/FragmentDirective.h" 14 #include "mozilla/dom/PerformanceResourceTimingBinding.h" 15 #include "mozilla/dom/PerformanceTimingBinding.h" 16 #include "mozilla/glean/DomPerformanceMetrics.h" 17 #include "nsIDocShell.h" 18 #include "nsIDocShellTreeItem.h" 19 #include "nsIHttpChannel.h" 20 #include "nsITimedChannel.h" 21 22 namespace mozilla::dom { 23 24 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PerformanceTiming, mPerformance) 25 26 /* static */ 27 PerformanceTimingData* PerformanceTimingData::Create( 28 nsITimedChannel* aTimedChannel, nsIHttpChannel* aChannel, 29 DOMHighResTimeStamp aZeroTime, nsAString& aInitiatorType, 30 nsAString& aEntryName) { 31 MOZ_ASSERT(NS_IsMainThread()); 32 33 // Check if resource timing is prefed off. 34 if (!StaticPrefs::dom_enable_resource_timing()) { 35 return nullptr; 36 } 37 38 if (!aChannel || !aTimedChannel) { 39 return nullptr; 40 } 41 42 bool reportTiming = true; 43 aTimedChannel->GetReportResourceTiming(&reportTiming); 44 45 if (!reportTiming) { 46 return nullptr; 47 } 48 49 aTimedChannel->GetInitiatorType(aInitiatorType); 50 51 // If the initiator type had no valid value, then set it to the default 52 // ("other") value. 53 if (aInitiatorType.IsEmpty()) { 54 aInitiatorType = u"other"_ns; 55 } 56 57 // According to the spec, "The name attribute must return the resolved URL 58 // of the requested resource. This attribute must not change even if the 59 // fetch redirected to a different URL." 60 nsCOMPtr<nsIURI> originalURI; 61 aChannel->GetOriginalURI(getter_AddRefs(originalURI)); 62 63 nsAutoCString name; 64 FragmentDirective::GetSpecIgnoringFragmentDirective(originalURI, name); 65 CopyUTF8toUTF16(name, aEntryName); 66 67 // The nsITimedChannel argument will be used to gather all the timings. 68 // The nsIHttpChannel argument will be used to check if any cross-origin 69 // redirects occurred. 70 // The last argument is the "zero time" (offset). Since we don't want 71 // any offset for the resource timing, this will be set to "0" - the 72 // resource timing returns a relative timing (no offset). 73 return new PerformanceTimingData(aTimedChannel, aChannel, 0); 74 } 75 76 /* static */ 77 PerformanceTimingData* PerformanceTimingData::Create( 78 const CacheablePerformanceTimingData& aCachedData, 79 DOMHighResTimeStamp aZeroTime, TimeStamp aStartTime, TimeStamp aEndTime, 80 RenderBlockingStatusType aRenderBlockingStatus) { 81 MOZ_ASSERT(NS_IsMainThread()); 82 83 // Check if resource timing is prefed off. 84 if (!StaticPrefs::dom_enable_resource_timing()) { 85 return nullptr; 86 } 87 88 return new PerformanceTimingData(aCachedData, aZeroTime, aStartTime, aEndTime, 89 aRenderBlockingStatus); 90 } 91 92 PerformanceTiming::PerformanceTiming(Performance* aPerformance, 93 nsITimedChannel* aChannel, 94 nsIHttpChannel* aHttpChannel, 95 DOMHighResTimeStamp aZeroTime) 96 : mPerformance(aPerformance) { 97 MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); 98 99 mTimingData.reset(new PerformanceTimingData( 100 aChannel, aHttpChannel, 101 nsRFPService::ReduceTimePrecisionAsMSecs( 102 aZeroTime, aPerformance->GetRandomTimelineSeed(), 103 aPerformance->GetRTPCallerType()))); 104 105 // Non-null aHttpChannel implies that this PerformanceTiming object is being 106 // used for subresources, which is irrelevant to this probe. 107 if (!aHttpChannel && StaticPrefs::dom_enable_performance() && 108 IsTopLevelContentDocument()) { 109 glean::performance_time::response_start.AccumulateRawDuration( 110 TimeDuration::FromMilliseconds( 111 mTimingData->ResponseStartHighRes(aPerformance) - 112 mTimingData->ZeroTime())); 113 } 114 } 115 116 CacheablePerformanceTimingData::CacheablePerformanceTimingData( 117 nsITimedChannel* aChannel, nsIHttpChannel* aHttpChannel) 118 : mEncodedBodySize(0), 119 mDecodedBodySize(0), 120 mResponseStatus(0), 121 mRedirectCount(0), 122 mAllRedirectsSameOrigin(true), 123 mAllRedirectsPassTAO(true), 124 mSecureConnection(false), 125 mTimingAllowed(true), 126 mInitialized(false) { 127 mInitialized = !!aChannel; 128 129 nsCOMPtr<nsIURI> uri; 130 if (aHttpChannel) { 131 aHttpChannel->GetURI(getter_AddRefs(uri)); 132 } else { 133 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); 134 if (httpChannel) { 135 httpChannel->GetURI(getter_AddRefs(uri)); 136 } 137 } 138 139 if (uri) { 140 mSecureConnection = uri->SchemeIs("https"); 141 } 142 143 if (aChannel) { 144 aChannel->GetAllRedirectsSameOrigin(&mAllRedirectsSameOrigin); 145 aChannel->GetAllRedirectsPassTimingAllowCheck(&mAllRedirectsPassTAO); 146 aChannel->GetRedirectCount(&mRedirectCount); 147 } 148 149 // The aHttpChannel argument is null if this PerformanceTiming object is 150 // being used for navigation timing (which is only relevant for documents). 151 // It has a non-null value if this PerformanceTiming object is being used 152 // for resource timing, which can include document loads, both toplevel and 153 // in subframes, and resources linked from a document. 154 if (aHttpChannel) { 155 SetCacheablePropertiesFromHttpChannel(aHttpChannel, aChannel); 156 } 157 } 158 159 // Copy the timing info from the channel so we don't need to keep the channel 160 // alive just to get the timestamps. 161 PerformanceTimingData::PerformanceTimingData(nsITimedChannel* aChannel, 162 nsIHttpChannel* aHttpChannel, 163 DOMHighResTimeStamp aZeroTime) 164 : CacheablePerformanceTimingData(aChannel, aHttpChannel), 165 mZeroTime(0.0), 166 mFetchStart(0.0), 167 mTransferSize(0) { 168 mZeroTime = aZeroTime; 169 170 if (!StaticPrefs::dom_enable_performance()) { 171 mZeroTime = 0; 172 } 173 174 if (aChannel) { 175 aChannel->GetAsyncOpen(&mAsyncOpen); 176 aChannel->GetRedirectStart(&mRedirectStart); 177 aChannel->GetRedirectEnd(&mRedirectEnd); 178 aChannel->GetDomainLookupStart(&mDomainLookupStart); 179 aChannel->GetDomainLookupEnd(&mDomainLookupEnd); 180 aChannel->GetConnectStart(&mConnectStart); 181 aChannel->GetSecureConnectionStart(&mSecureConnectionStart); 182 aChannel->GetConnectEnd(&mConnectEnd); 183 aChannel->GetRequestStart(&mRequestStart); 184 aChannel->GetResponseStart(&mResponseStart); 185 aChannel->GetCacheReadStart(&mCacheReadStart); 186 aChannel->GetResponseEnd(&mResponseEnd); 187 aChannel->GetCacheReadEnd(&mCacheReadEnd); 188 189 aChannel->GetDispatchFetchEventStart(&mWorkerStart); 190 aChannel->GetHandleFetchEventStart(&mWorkerRequestStart); 191 // TODO: Track when FetchEvent.respondWith() promise resolves as 192 // ServiceWorker interception responseStart? 193 aChannel->GetHandleFetchEventEnd(&mWorkerResponseEnd); 194 195 // The performance timing api essentially requires that the event timestamps 196 // have a strict relation with each other. The truth, however, is the 197 // browser engages in a number of speculative activities that sometimes mean 198 // connections and lookups begin at different times. Workaround that here by 199 // clamping these values to what we expect FetchStart to be. This means the 200 // later of AsyncOpen or WorkerStart times. 201 if (!mAsyncOpen.IsNull()) { 202 // We want to clamp to the expected FetchStart value. This is later of 203 // the AsyncOpen and WorkerStart values. 204 const TimeStamp* clampTime = &mAsyncOpen; 205 if (!mWorkerStart.IsNull() && mWorkerStart > mAsyncOpen) { 206 clampTime = &mWorkerStart; 207 } 208 209 if (!mDomainLookupStart.IsNull() && mDomainLookupStart < *clampTime) { 210 mDomainLookupStart = *clampTime; 211 } 212 213 if (!mDomainLookupEnd.IsNull() && mDomainLookupEnd < *clampTime) { 214 mDomainLookupEnd = *clampTime; 215 } 216 217 if (!mConnectStart.IsNull() && mConnectStart < *clampTime) { 218 mConnectStart = *clampTime; 219 } 220 221 if (mSecureConnection && !mSecureConnectionStart.IsNull() && 222 mSecureConnectionStart < *clampTime) { 223 mSecureConnectionStart = *clampTime; 224 } 225 226 if (!mConnectEnd.IsNull() && mConnectEnd < *clampTime) { 227 mConnectEnd = *clampTime; 228 } 229 } 230 } 231 232 if (aHttpChannel) { 233 // NOTE: Other fields are set by SetCacheablePropertiesFromHttpChannel, 234 // called inside CacheablePerformanceTimingData constructor. 235 SetTransferSizeFromHttpChannel(aHttpChannel); 236 } 237 238 bool renderBlocking = false; 239 if (aChannel) { 240 aChannel->GetRenderBlocking(&renderBlocking); 241 } 242 mRenderBlockingStatus = renderBlocking 243 ? RenderBlockingStatusType::Blocking 244 : RenderBlockingStatusType::Non_blocking; 245 } 246 247 CacheablePerformanceTimingData::CacheablePerformanceTimingData( 248 const CacheablePerformanceTimingData& aOther) 249 : mEncodedBodySize(aOther.mEncodedBodySize), 250 mDecodedBodySize(aOther.mDecodedBodySize), 251 mResponseStatus(aOther.mResponseStatus), 252 mRedirectCount(aOther.mRedirectCount), 253 mBodyInfoAccessAllowed(aOther.mBodyInfoAccessAllowed), 254 mAllRedirectsSameOrigin(aOther.mAllRedirectsSameOrigin), 255 mAllRedirectsPassTAO(aOther.mAllRedirectsPassTAO), 256 mSecureConnection(aOther.mSecureConnection), 257 mTimingAllowed(aOther.mTimingAllowed), 258 mInitialized(aOther.mInitialized), 259 mNextHopProtocol(aOther.mNextHopProtocol), 260 mContentType(aOther.mContentType) { 261 for (auto& data : aOther.mServerTiming) { 262 mServerTiming.AppendElement(data); 263 } 264 } 265 266 PerformanceTimingData::PerformanceTimingData( 267 const CacheablePerformanceTimingData& aCachedData, 268 DOMHighResTimeStamp aZeroTime, TimeStamp aStartTime, TimeStamp aEndTime, 269 RenderBlockingStatusType aRenderBlockingStatus) 270 : CacheablePerformanceTimingData(aCachedData), 271 mAsyncOpen(aStartTime), 272 mResponseEnd(aEndTime), 273 mZeroTime(aZeroTime), 274 mTransferSize(kLocalCacheTransferSize), 275 mRenderBlockingStatus(aRenderBlockingStatus) { 276 if (!StaticPrefs::dom_enable_performance()) { 277 mZeroTime = 0; 278 } 279 } 280 281 CacheablePerformanceTimingData::CacheablePerformanceTimingData( 282 const IPCPerformanceTimingData& aIPCData) 283 : mEncodedBodySize(aIPCData.encodedBodySize()), 284 mDecodedBodySize(aIPCData.decodedBodySize()), 285 mResponseStatus(aIPCData.responseStatus()), 286 mRedirectCount(aIPCData.redirectCount()), 287 mBodyInfoAccessAllowed(aIPCData.bodyInfoAccessAllowed()), 288 mAllRedirectsSameOrigin(aIPCData.allRedirectsSameOrigin()), 289 mAllRedirectsPassTAO(aIPCData.allRedirectsPassTAO()), 290 mSecureConnection(aIPCData.secureConnection()), 291 mTimingAllowed(aIPCData.timingAllowed()), 292 mInitialized(aIPCData.initialized()), 293 mNextHopProtocol(aIPCData.nextHopProtocol()), 294 mContentType(aIPCData.contentType()) { 295 for (const auto& serverTimingData : aIPCData.serverTiming()) { 296 RefPtr<nsServerTiming> timing = new nsServerTiming(); 297 timing->SetName(serverTimingData.name()); 298 timing->SetDuration(serverTimingData.duration()); 299 timing->SetDescription(serverTimingData.description()); 300 mServerTiming.AppendElement(timing); 301 } 302 } 303 304 PerformanceTimingData::PerformanceTimingData( 305 const IPCPerformanceTimingData& aIPCData) 306 : CacheablePerformanceTimingData(aIPCData), 307 mAsyncOpen(aIPCData.asyncOpen()), 308 mRedirectStart(aIPCData.redirectStart()), 309 mRedirectEnd(aIPCData.redirectEnd()), 310 mDomainLookupStart(aIPCData.domainLookupStart()), 311 mDomainLookupEnd(aIPCData.domainLookupEnd()), 312 mConnectStart(aIPCData.connectStart()), 313 mSecureConnectionStart(aIPCData.secureConnectionStart()), 314 mConnectEnd(aIPCData.connectEnd()), 315 mRequestStart(aIPCData.requestStart()), 316 mResponseStart(aIPCData.responseStart()), 317 mCacheReadStart(aIPCData.cacheReadStart()), 318 mResponseEnd(aIPCData.responseEnd()), 319 mCacheReadEnd(aIPCData.cacheReadEnd()), 320 mWorkerStart(aIPCData.workerStart()), 321 mWorkerRequestStart(aIPCData.workerRequestStart()), 322 mWorkerResponseEnd(aIPCData.workerResponseEnd()), 323 mZeroTime(aIPCData.zeroTime()), 324 mFetchStart(aIPCData.fetchStart()), 325 mTransferSize(aIPCData.transferSize()), 326 mRenderBlockingStatus(aIPCData.renderBlocking() 327 ? RenderBlockingStatusType::Blocking 328 : RenderBlockingStatusType::Non_blocking) {} 329 330 IPCPerformanceTimingData PerformanceTimingData::ToIPC() { 331 nsTArray<IPCServerTiming> ipcServerTiming; 332 for (auto& serverTimingData : mServerTiming) { 333 nsAutoCString name; 334 (void)serverTimingData->GetName(name); 335 double duration = 0; 336 (void)serverTimingData->GetDuration(&duration); 337 nsAutoCString description; 338 (void)serverTimingData->GetDescription(description); 339 ipcServerTiming.AppendElement(IPCServerTiming(name, duration, description)); 340 } 341 bool renderBlocking = 342 mRenderBlockingStatus == RenderBlockingStatusType::Blocking; 343 return IPCPerformanceTimingData( 344 ipcServerTiming, mNextHopProtocol, mAsyncOpen, mRedirectStart, 345 mRedirectEnd, mDomainLookupStart, mDomainLookupEnd, mConnectStart, 346 mSecureConnectionStart, mConnectEnd, mRequestStart, mResponseStart, 347 mCacheReadStart, mResponseEnd, mCacheReadEnd, mWorkerStart, 348 mWorkerRequestStart, mWorkerResponseEnd, mZeroTime, mFetchStart, 349 mEncodedBodySize, mTransferSize, mDecodedBodySize, mResponseStatus, 350 mRedirectCount, renderBlocking, mContentType, mAllRedirectsSameOrigin, 351 mAllRedirectsPassTAO, mSecureConnection, mBodyInfoAccessAllowed, 352 mTimingAllowed, mInitialized); 353 } 354 355 void CacheablePerformanceTimingData::SetCacheablePropertiesFromHttpChannel( 356 nsIHttpChannel* aHttpChannel, nsITimedChannel* aChannel) { 357 MOZ_ASSERT(aHttpChannel); 358 359 nsAutoCString protocol; 360 (void)aHttpChannel->GetProtocolVersion(protocol); 361 CopyUTF8toUTF16(protocol, mNextHopProtocol); 362 363 (void)aHttpChannel->GetEncodedBodySize(&mEncodedBodySize); 364 (void)aHttpChannel->GetDecodedBodySize(&mDecodedBodySize); 365 if (mDecodedBodySize == 0) { 366 mDecodedBodySize = mEncodedBodySize; 367 } 368 369 uint32_t responseStatus = 0; 370 (void)aHttpChannel->GetResponseStatus(&responseStatus); 371 mResponseStatus = static_cast<uint16_t>(responseStatus); 372 373 nsAutoCString contentType; 374 (void)aHttpChannel->GetContentType(contentType); 375 CopyUTF8toUTF16(contentType, mContentType); 376 377 mBodyInfoAccessAllowed = 378 CheckBodyInfoAccessAllowedForOrigin(aHttpChannel, aChannel); 379 mTimingAllowed = CheckTimingAllowedForOrigin(aHttpChannel, aChannel); 380 aChannel->GetAllRedirectsPassTimingAllowCheck(&mAllRedirectsPassTAO); 381 382 aChannel->GetNativeServerTiming(mServerTiming); 383 } 384 385 void PerformanceTimingData::SetPropertiesFromHttpChannel( 386 nsIHttpChannel* aHttpChannel, nsITimedChannel* aChannel) { 387 SetCacheablePropertiesFromHttpChannel(aHttpChannel, aChannel); 388 SetTransferSizeFromHttpChannel(aHttpChannel); 389 } 390 391 void PerformanceTimingData::SetTransferSizeFromHttpChannel( 392 nsIHttpChannel* aHttpChannel) { 393 (void)aHttpChannel->GetTransferSize(&mTransferSize); 394 } 395 396 PerformanceTiming::~PerformanceTiming() = default; 397 398 DOMHighResTimeStamp PerformanceTimingData::FetchStartHighRes( 399 Performance* aPerformance) { 400 MOZ_ASSERT(aPerformance); 401 402 if (!mFetchStart) { 403 if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) { 404 return mZeroTime; 405 } 406 MOZ_ASSERT(!mAsyncOpen.IsNull(), 407 "The fetch start time stamp should always be " 408 "valid if the performance timing is enabled"); 409 if (!mAsyncOpen.IsNull()) { 410 if (!mWorkerRequestStart.IsNull() && mWorkerRequestStart > mAsyncOpen) { 411 mFetchStart = TimeStampToDOMHighRes(aPerformance, mWorkerRequestStart); 412 } else { 413 mFetchStart = TimeStampToDOMHighRes(aPerformance, mAsyncOpen); 414 } 415 } 416 } 417 return nsRFPService::ReduceTimePrecisionAsMSecs( 418 mFetchStart, aPerformance->GetRandomTimelineSeed(), 419 aPerformance->GetRTPCallerType()); 420 } 421 422 DOMTimeMilliSec PerformanceTiming::FetchStart() { 423 return static_cast<int64_t>(mTimingData->FetchStartHighRes(mPerformance)); 424 } 425 426 nsITimedChannel::BodyInfoAccess 427 CacheablePerformanceTimingData::CheckBodyInfoAccessAllowedForOrigin( 428 nsIHttpChannel* aResourceChannel, nsITimedChannel* aChannel) { 429 // Check if the resource is either same origin as the page that started 430 // the load, or if the response contains an Access-Control-Allow-Origin 431 // header with the domain of the page that started the load. 432 MOZ_ASSERT(aChannel); 433 434 if (!IsInitialized()) { 435 return nsITimedChannel::BodyInfoAccess::DISALLOWED; 436 } 437 438 // Check that the current document passes the check. 439 nsCOMPtr<nsILoadInfo> loadInfo = aResourceChannel->LoadInfo(); 440 441 // TYPE_DOCUMENT loads have no loadingPrincipal. 442 if (loadInfo->GetExternalContentPolicyType() == 443 ExtContentPolicy::TYPE_DOCUMENT) { 444 return nsITimedChannel::BodyInfoAccess::ALLOW_ALL; 445 } 446 447 nsCOMPtr<nsIPrincipal> principal = loadInfo->GetLoadingPrincipal(); 448 if (!principal) { 449 return nsITimedChannel::BodyInfoAccess::DISALLOWED; 450 } 451 return aChannel->BodyInfoAccessAllowedCheck(principal); 452 } 453 454 bool CacheablePerformanceTimingData::CheckTimingAllowedForOrigin( 455 nsIHttpChannel* aResourceChannel, nsITimedChannel* aChannel) { 456 // Check if the resource is either same origin as the page that started 457 // the load, or if the response contains the proper Timing-Allow-Origin 458 // header with the domain of the page that started the load. 459 MOZ_ASSERT(aChannel); 460 461 if (!IsInitialized()) { 462 return false; 463 } 464 465 // Check that the current document passes the check. 466 nsCOMPtr<nsILoadInfo> loadInfo = aResourceChannel->LoadInfo(); 467 468 // TYPE_DOCUMENT loads have no loadingPrincipal. 469 if (loadInfo->GetExternalContentPolicyType() == 470 ExtContentPolicy::TYPE_DOCUMENT) { 471 return true; 472 } 473 474 nsCOMPtr<nsIPrincipal> principal = loadInfo->GetLoadingPrincipal(); 475 return principal && aChannel->TimingAllowCheck(principal); 476 } 477 478 uint8_t CacheablePerformanceTimingData::GetRedirectCount() const { 479 if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) { 480 return 0; 481 } 482 if (!mAllRedirectsSameOrigin) { 483 return 0; 484 } 485 return mRedirectCount; 486 } 487 488 bool PerformanceTimingData::ShouldReportCrossOriginRedirect( 489 bool aEnsureSameOriginAndIgnoreTAO) const { 490 if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) { 491 return false; 492 } 493 494 if (!mTimingAllowed || mRedirectCount == 0) { 495 return false; 496 } 497 498 // If the redirect count is 0, or if one of the cross-origin 499 // redirects doesn't have the proper Timing-Allow-Origin header, 500 // then RedirectStart and RedirectEnd will be set to zero 501 return aEnsureSameOriginAndIgnoreTAO ? mAllRedirectsSameOrigin 502 : mAllRedirectsPassTAO; 503 } 504 505 DOMHighResTimeStamp PerformanceTimingData::AsyncOpenHighRes( 506 Performance* aPerformance) { 507 MOZ_ASSERT(aPerformance); 508 509 if (!StaticPrefs::dom_enable_performance() || !IsInitialized() || 510 mAsyncOpen.IsNull()) { 511 return mZeroTime; 512 } 513 DOMHighResTimeStamp rawValue = 514 TimeStampToDOMHighRes(aPerformance, mAsyncOpen); 515 return nsRFPService::ReduceTimePrecisionAsMSecs( 516 rawValue, aPerformance->GetRandomTimelineSeed(), 517 aPerformance->GetRTPCallerType()); 518 } 519 520 DOMHighResTimeStamp PerformanceTimingData::WorkerStartHighRes( 521 Performance* aPerformance) { 522 MOZ_ASSERT(aPerformance); 523 524 if (!StaticPrefs::dom_enable_performance() || !IsInitialized() || 525 mWorkerStart.IsNull()) { 526 return mZeroTime; 527 } 528 DOMHighResTimeStamp rawValue = 529 TimeStampToDOMHighRes(aPerformance, mWorkerStart); 530 return nsRFPService::ReduceTimePrecisionAsMSecs( 531 rawValue, aPerformance->GetRandomTimelineSeed(), 532 aPerformance->GetRTPCallerType()); 533 } 534 535 /** 536 * RedirectStartHighRes() is used by both the navigation timing and the 537 * resource timing. Since, navigation timing and resource timing check and 538 * interpret cross-domain redirects in a different manner, 539 * RedirectStartHighRes() will make no checks for cross-domain redirect. 540 * It's up to the consumers of this method (PerformanceTiming::RedirectStart() 541 * and PerformanceResourceTiming::RedirectStart() to make such verifications. 542 * 543 * @return a valid timing if the Performance Timing is enabled 544 */ 545 DOMHighResTimeStamp PerformanceTimingData::RedirectStartHighRes( 546 Performance* aPerformance) { 547 MOZ_ASSERT(aPerformance); 548 549 if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) { 550 return mZeroTime; 551 } 552 return TimeStampToReducedDOMHighResOrFetchStart(aPerformance, mRedirectStart); 553 } 554 555 DOMTimeMilliSec PerformanceTiming::RedirectStart() { 556 if (!mTimingData->IsInitialized()) { 557 return 0; 558 } 559 // We have to check if all the redirect URIs had the same origin (since there 560 // is no check in RedirectStartHighRes()) 561 if (mTimingData->AllRedirectsSameOrigin() && 562 mTimingData->RedirectCountReal()) { 563 return static_cast<int64_t>( 564 mTimingData->RedirectStartHighRes(mPerformance)); 565 } 566 return 0; 567 } 568 569 /** 570 * RedirectEndHighRes() is used by both the navigation timing and the resource 571 * timing. Since, navigation timing and resource timing check and interpret 572 * cross-domain redirects in a different manner, RedirectEndHighRes() will make 573 * no checks for cross-domain redirect. It's up to the consumers of this method 574 * (PerformanceTiming::RedirectEnd() and 575 * PerformanceResourceTiming::RedirectEnd() to make such verifications. 576 * 577 * @return a valid timing if the Performance Timing is enabled 578 */ 579 DOMHighResTimeStamp PerformanceTimingData::RedirectEndHighRes( 580 Performance* aPerformance) { 581 MOZ_ASSERT(aPerformance); 582 583 if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) { 584 return mZeroTime; 585 } 586 return TimeStampToReducedDOMHighResOrFetchStart(aPerformance, mRedirectEnd); 587 } 588 589 DOMTimeMilliSec PerformanceTiming::RedirectEnd() { 590 if (!mTimingData->IsInitialized()) { 591 return 0; 592 } 593 // We have to check if all the redirect URIs had the same origin (since there 594 // is no check in RedirectEndHighRes()) 595 if (mTimingData->AllRedirectsSameOrigin() && 596 mTimingData->RedirectCountReal()) { 597 return static_cast<int64_t>(mTimingData->RedirectEndHighRes(mPerformance)); 598 } 599 return 0; 600 } 601 602 DOMHighResTimeStamp PerformanceTimingData::DomainLookupStartHighRes( 603 Performance* aPerformance) { 604 MOZ_ASSERT(aPerformance); 605 606 if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) { 607 return mZeroTime; 608 } 609 // Bug 1637985 - DomainLookup information may be useful for fingerprinting. 610 if (aPerformance->ShouldResistFingerprinting()) { 611 return FetchStartHighRes(aPerformance); 612 } 613 return TimeStampToReducedDOMHighResOrFetchStart(aPerformance, 614 mDomainLookupStart); 615 } 616 617 DOMTimeMilliSec PerformanceTiming::DomainLookupStart() { 618 return static_cast<int64_t>( 619 mTimingData->DomainLookupStartHighRes(mPerformance)); 620 } 621 622 DOMHighResTimeStamp PerformanceTimingData::DomainLookupEndHighRes( 623 Performance* aPerformance) { 624 MOZ_ASSERT(aPerformance); 625 626 if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) { 627 return mZeroTime; 628 } 629 // Bug 1637985 - DomainLookup information may be useful for fingerprinting. 630 if (aPerformance->ShouldResistFingerprinting()) { 631 return FetchStartHighRes(aPerformance); 632 } 633 // Bug 1155008 - nsHttpTransaction is racy. Return DomainLookupStart when null 634 if (mDomainLookupEnd.IsNull()) { 635 return DomainLookupStartHighRes(aPerformance); 636 } 637 DOMHighResTimeStamp rawValue = 638 TimeStampToDOMHighRes(aPerformance, mDomainLookupEnd); 639 return nsRFPService::ReduceTimePrecisionAsMSecs( 640 rawValue, aPerformance->GetRandomTimelineSeed(), 641 aPerformance->GetRTPCallerType()); 642 } 643 644 DOMTimeMilliSec PerformanceTiming::DomainLookupEnd() { 645 return static_cast<int64_t>( 646 mTimingData->DomainLookupEndHighRes(mPerformance)); 647 } 648 649 DOMHighResTimeStamp PerformanceTimingData::ConnectStartHighRes( 650 Performance* aPerformance) { 651 MOZ_ASSERT(aPerformance); 652 653 if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) { 654 return mZeroTime; 655 } 656 if (mConnectStart.IsNull()) { 657 return DomainLookupEndHighRes(aPerformance); 658 } 659 DOMHighResTimeStamp rawValue = 660 TimeStampToDOMHighRes(aPerformance, mConnectStart); 661 return nsRFPService::ReduceTimePrecisionAsMSecs( 662 rawValue, aPerformance->GetRandomTimelineSeed(), 663 aPerformance->GetRTPCallerType()); 664 } 665 666 DOMTimeMilliSec PerformanceTiming::ConnectStart() { 667 return static_cast<int64_t>(mTimingData->ConnectStartHighRes(mPerformance)); 668 } 669 670 DOMHighResTimeStamp PerformanceTimingData::SecureConnectionStartHighRes( 671 Performance* aPerformance) { 672 MOZ_ASSERT(aPerformance); 673 674 if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) { 675 return mZeroTime; 676 } 677 if (!mSecureConnection) { 678 return 0; // We use 0 here, because mZeroTime is sometimes set to the 679 // navigation start time. 680 } 681 if (mSecureConnectionStart.IsNull()) { 682 return ConnectStartHighRes(aPerformance); 683 } 684 DOMHighResTimeStamp rawValue = 685 TimeStampToDOMHighRes(aPerformance, mSecureConnectionStart); 686 return nsRFPService::ReduceTimePrecisionAsMSecs( 687 rawValue, aPerformance->GetRandomTimelineSeed(), 688 aPerformance->GetRTPCallerType()); 689 } 690 691 DOMTimeMilliSec PerformanceTiming::SecureConnectionStart() { 692 return static_cast<int64_t>( 693 mTimingData->SecureConnectionStartHighRes(mPerformance)); 694 } 695 696 DOMHighResTimeStamp PerformanceTimingData::ConnectEndHighRes( 697 Performance* aPerformance) { 698 MOZ_ASSERT(aPerformance); 699 700 if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) { 701 return mZeroTime; 702 } 703 // Bug 1155008 - nsHttpTransaction is racy. Return ConnectStart when null 704 if (mConnectEnd.IsNull()) { 705 return ConnectStartHighRes(aPerformance); 706 } 707 DOMHighResTimeStamp rawValue = 708 TimeStampToDOMHighRes(aPerformance, mConnectEnd); 709 return nsRFPService::ReduceTimePrecisionAsMSecs( 710 rawValue, aPerformance->GetRandomTimelineSeed(), 711 aPerformance->GetRTPCallerType()); 712 } 713 714 DOMTimeMilliSec PerformanceTiming::ConnectEnd() { 715 return static_cast<int64_t>(mTimingData->ConnectEndHighRes(mPerformance)); 716 } 717 718 DOMHighResTimeStamp PerformanceTimingData::RequestStartHighRes( 719 Performance* aPerformance) { 720 MOZ_ASSERT(aPerformance); 721 722 if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) { 723 return mZeroTime; 724 } 725 726 if (mRequestStart.IsNull()) { 727 mRequestStart = mWorkerRequestStart; 728 } 729 730 return TimeStampToReducedDOMHighResOrFetchStart(aPerformance, mRequestStart); 731 } 732 733 DOMTimeMilliSec PerformanceTiming::RequestStart() { 734 return static_cast<int64_t>(mTimingData->RequestStartHighRes(mPerformance)); 735 } 736 737 DOMHighResTimeStamp PerformanceTimingData::ResponseStartHighRes( 738 Performance* aPerformance) { 739 MOZ_ASSERT(aPerformance); 740 741 if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) { 742 return mZeroTime; 743 } 744 if (mResponseStart.IsNull() || 745 (!mCacheReadStart.IsNull() && mCacheReadStart < mResponseStart)) { 746 mResponseStart = mCacheReadStart; 747 } 748 749 if (mResponseStart.IsNull() || 750 (!mRequestStart.IsNull() && mResponseStart < mRequestStart)) { 751 mResponseStart = mRequestStart; 752 } 753 return TimeStampToReducedDOMHighResOrFetchStart(aPerformance, mResponseStart); 754 } 755 756 DOMTimeMilliSec PerformanceTiming::ResponseStart() { 757 return static_cast<int64_t>(mTimingData->ResponseStartHighRes(mPerformance)); 758 } 759 760 DOMHighResTimeStamp PerformanceTimingData::ResponseEndHighRes( 761 Performance* aPerformance) { 762 MOZ_ASSERT(aPerformance); 763 764 if (!StaticPrefs::dom_enable_performance() || !IsInitialized()) { 765 return mZeroTime; 766 } 767 if (mResponseEnd.IsNull() || 768 (!mCacheReadEnd.IsNull() && mCacheReadEnd < mResponseEnd)) { 769 mResponseEnd = mCacheReadEnd; 770 } 771 if (mResponseEnd.IsNull()) { 772 mResponseEnd = mWorkerResponseEnd; 773 } 774 // Bug 1155008 - nsHttpTransaction is racy. Return ResponseStart when null 775 if (mResponseEnd.IsNull()) { 776 return ResponseStartHighRes(aPerformance); 777 } 778 DOMHighResTimeStamp rawValue = 779 TimeStampToDOMHighRes(aPerformance, mResponseEnd); 780 return nsRFPService::ReduceTimePrecisionAsMSecs( 781 rawValue, aPerformance->GetRandomTimelineSeed(), 782 aPerformance->GetRTPCallerType()); 783 } 784 785 DOMTimeMilliSec PerformanceTiming::ResponseEnd() { 786 return static_cast<int64_t>(mTimingData->ResponseEndHighRes(mPerformance)); 787 } 788 789 JSObject* PerformanceTiming::WrapObject(JSContext* cx, 790 JS::Handle<JSObject*> aGivenProto) { 791 return PerformanceTiming_Binding::Wrap(cx, this, aGivenProto); 792 } 793 794 bool PerformanceTiming::IsTopLevelContentDocument() const { 795 nsCOMPtr<Document> document = mPerformance->GetDocumentIfCurrent(); 796 if (!document) { 797 return false; 798 } 799 800 if (BrowsingContext* bc = document->GetBrowsingContext()) { 801 return bc->IsTopContent(); 802 } 803 return false; 804 } 805 806 nsTArray<nsCOMPtr<nsIServerTiming>> 807 CacheablePerformanceTimingData::GetServerTiming() { 808 if (!StaticPrefs::dom_enable_performance() || !IsInitialized() || 809 !TimingAllowed()) { 810 return nsTArray<nsCOMPtr<nsIServerTiming>>(); 811 } 812 813 return mServerTiming.Clone(); 814 } 815 816 } // namespace mozilla::dom