PerformanceMainThread.cpp (26980B)
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 "PerformanceMainThread.h" 8 9 #include "LargestContentfulPaint.h" 10 #include "PerformanceEventTiming.h" 11 #include "PerformanceInteractionMetrics.h" 12 #include "PerformanceNavigation.h" 13 #include "PerformancePaintTiming.h" 14 #include "js/GCAPI.h" 15 #include "js/PropertyAndElement.h" // JS_DefineProperty 16 #include "jsapi.h" 17 #include "mozilla/HoldDropJSObjects.h" 18 #include "mozilla/PresShell.h" 19 #include "mozilla/StaticPrefs_dom.h" 20 #include "mozilla/TextEvents.h" 21 #include "mozilla/dom/Document.h" 22 #include "mozilla/dom/Event.h" 23 #include "mozilla/dom/EventCounts.h" 24 #include "mozilla/dom/FragmentDirective.h" 25 #include "mozilla/dom/PerformanceEventTimingBinding.h" 26 #include "mozilla/dom/PerformanceNavigationTiming.h" 27 #include "mozilla/dom/PerformanceResourceTiming.h" 28 #include "mozilla/dom/PerformanceTiming.h" 29 #include "nsContainerFrame.h" 30 #include "nsGkAtoms.h" 31 #include "nsGlobalWindowInner.h" 32 #include "nsIChannel.h" 33 #include "nsIDocShell.h" 34 #include "nsIHttpChannel.h" 35 36 namespace mozilla::dom { 37 38 extern mozilla::LazyLogModule gLCPLogging; 39 40 namespace { 41 42 void GetURLSpecFromChannel(nsITimedChannel* aChannel, nsAString& aSpec) { 43 aSpec.AssignLiteral("document"); 44 45 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aChannel); 46 if (!channel) { 47 return; 48 } 49 50 nsCOMPtr<nsIURI> uri; 51 nsresult rv = channel->GetURI(getter_AddRefs(uri)); 52 if (NS_WARN_IF(NS_FAILED(rv)) || !uri) { 53 return; 54 } 55 56 nsAutoCString spec; 57 rv = FragmentDirective::GetSpecIgnoringFragmentDirective(uri, spec); 58 if (NS_WARN_IF(NS_FAILED(rv))) { 59 return; 60 } 61 62 CopyUTF8toUTF16(spec, aSpec); 63 } 64 65 } // namespace 66 67 NS_IMPL_CYCLE_COLLECTION_CLASS(PerformanceMainThread) 68 69 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PerformanceMainThread, 70 Performance) 71 NS_IMPL_CYCLE_COLLECTION_UNLINK( 72 mTiming, mNavigation, mDocEntry, mFCPTiming, mEventTimingEntries, 73 mLargestContentfulPaintEntries, mFirstInputEvent, mPendingPointerDown, 74 mPendingEventTimingEntries, mEventCounts, mInteractionMetrics) 75 tmp->mTextFrameUnions.Clear(); 76 mozilla::DropJSObjects(tmp); 77 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 78 79 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PerformanceMainThread, 80 Performance) 81 NS_IMPL_CYCLE_COLLECTION_TRAVERSE( 82 mTiming, mNavigation, mDocEntry, mFCPTiming, mEventTimingEntries, 83 mLargestContentfulPaintEntries, mFirstInputEvent, mPendingPointerDown, 84 mPendingEventTimingEntries, mEventCounts, mTextFrameUnions, 85 mInteractionMetrics) 86 87 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 88 89 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceMainThread, 90 Performance) 91 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMozMemory) 92 NS_IMPL_CYCLE_COLLECTION_TRACE_END 93 94 NS_IMPL_ADDREF_INHERITED(PerformanceMainThread, Performance) 95 NS_IMPL_RELEASE_INHERITED(PerformanceMainThread, Performance) 96 97 // QueryInterface implementation for PerformanceMainThread 98 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceMainThread) 99 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 100 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, EventTarget) 101 NS_INTERFACE_MAP_END_INHERITING(Performance) 102 103 PerformanceMainThread::PerformanceMainThread(nsPIDOMWindowInner* aWindow, 104 nsDOMNavigationTiming* aDOMTiming, 105 nsITimedChannel* aChannel) 106 : Performance(aWindow->AsGlobal()), 107 mDOMTiming(aDOMTiming), 108 mChannel(aChannel) { 109 MOZ_ASSERT(aWindow, "Parent window object should be provided"); 110 if (StaticPrefs::dom_enable_event_timing()) { 111 mEventCounts = new class EventCounts(GetParentObject()); 112 } 113 CreateNavigationTimingEntry(); 114 115 if (StaticPrefs::dom_enable_largest_contentful_paint()) { 116 nsGlobalWindowInner* owner = GetOwnerWindow(); 117 MarkerInnerWindowId innerWindowID = 118 owner ? MarkerInnerWindowId(owner->WindowID()) 119 : MarkerInnerWindowId::NoId(); 120 // There might be multiple LCP entries and we only care about the latest one 121 // which is also the biggest value. That's why we need to record these 122 // markers in two different places: 123 // - During the Document unload, so we can record the closed pages. 124 // - During the profile capture, so we can record the open pages. 125 // We are capturing the second one here. 126 // Our static analysis doesn't allow capturing ref-counted pointers in 127 // lambdas, so we need to hide it in a uintptr_t. This is safe because this 128 // lambda will be destroyed in ~PerformanceMainThread(). 129 uintptr_t self = reinterpret_cast<uintptr_t>(this); 130 profiler_add_state_change_callback( 131 // Using the "Pausing" state as "GeneratingProfile" profile happens too 132 // late; we can not record markers if the profiler is already paused. 133 ProfilingState::Pausing, 134 [self, innerWindowID](ProfilingState aProfilingState) { 135 const PerformanceMainThread* selfPtr = 136 reinterpret_cast<const PerformanceMainThread*>(self); 137 138 selfPtr->GetDOMTiming()->MaybeAddLCPProfilerMarker(innerWindowID); 139 }, 140 self); 141 } 142 } 143 144 PerformanceMainThread::~PerformanceMainThread() { 145 profiler_remove_state_change_callback(reinterpret_cast<uintptr_t>(this)); 146 mozilla::DropJSObjects(this); 147 } 148 149 void PerformanceMainThread::GetMozMemory(JSContext* aCx, 150 JS::MutableHandle<JSObject*> aObj) { 151 if (!mMozMemory) { 152 JS::Rooted<JSObject*> mozMemoryObj(aCx, JS_NewPlainObject(aCx)); 153 JS::Rooted<JSObject*> gcMemoryObj(aCx, js::gc::NewMemoryInfoObject(aCx)); 154 if (!mozMemoryObj || !gcMemoryObj) { 155 MOZ_CRASH("out of memory creating performance.mozMemory"); 156 } 157 if (!JS_DefineProperty(aCx, mozMemoryObj, "gc", gcMemoryObj, 158 JSPROP_ENUMERATE)) { 159 MOZ_CRASH("out of memory creating performance.mozMemory"); 160 } 161 mMozMemory = mozMemoryObj; 162 mozilla::HoldJSObjects(this); 163 } 164 165 aObj.set(mMozMemory); 166 } 167 168 PerformanceTiming* PerformanceMainThread::Timing() { 169 if (!mTiming) { 170 // For navigation timing, the third argument (an nsIHttpChannel) is null 171 // since the cross-domain redirect were already checked. The last 172 // argument (zero time) for performance.timing is the navigation start 173 // value. 174 mTiming = new PerformanceTiming(this, mChannel, nullptr, 175 mDOMTiming->GetNavigationStart()); 176 } 177 178 return mTiming; 179 } 180 181 void PerformanceMainThread::DispatchResourceTimingBufferFullEvent() { 182 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr); 183 // it bubbles, and it isn't cancelable 184 event->InitEvent(u"resourcetimingbufferfull"_ns, true, false); 185 event->SetTrusted(true); 186 DispatchEvent(*event); 187 } 188 189 PerformanceNavigation* PerformanceMainThread::Navigation() { 190 if (!mNavigation) { 191 mNavigation = new PerformanceNavigation(this); 192 } 193 194 return mNavigation; 195 } 196 197 /** 198 * An entry should be added only after the resource is loaded. 199 * This method is not thread safe and can only be called on the main thread. 200 */ 201 void PerformanceMainThread::AddEntry(nsIHttpChannel* channel, 202 nsITimedChannel* timedChannel) { 203 MOZ_ASSERT(NS_IsMainThread()); 204 205 nsAutoString initiatorType; 206 nsAutoString entryName; 207 208 UniquePtr<PerformanceTimingData> performanceTimingData( 209 PerformanceTimingData::Create(timedChannel, channel, 0, initiatorType, 210 entryName)); 211 if (!performanceTimingData) { 212 return; 213 } 214 AddRawEntry(std::move(performanceTimingData), initiatorType, entryName); 215 } 216 217 void PerformanceMainThread::AddEntry(const nsString& entryName, 218 const nsString& initiatorType, 219 UniquePtr<PerformanceTimingData>&& aData) { 220 AddRawEntry(std::move(aData), initiatorType, entryName); 221 } 222 223 void PerformanceMainThread::AddRawEntry(UniquePtr<PerformanceTimingData> aData, 224 const nsAString& aInitiatorType, 225 const nsAString& aEntryName) { 226 // The PerformanceResourceTiming object will use the PerformanceTimingData 227 // object to get all the required timings. 228 auto entry = 229 MakeRefPtr<PerformanceResourceTiming>(std::move(aData), this, aEntryName); 230 entry->SetInitiatorType(aInitiatorType); 231 InsertResourceEntry(entry); 232 } 233 234 void PerformanceMainThread::SetFCPTimingEntry(PerformancePaintTiming* aEntry) { 235 MOZ_ASSERT(aEntry); 236 if (!mFCPTiming) { 237 mFCPTiming = aEntry; 238 QueueEntry(aEntry); 239 } 240 } 241 242 void PerformanceMainThread::InsertEventTimingEntry( 243 PerformanceEventTiming* aEventEntry) { 244 mPendingEventTimingEntries.insertBack(aEventEntry); 245 246 if (mHasQueuedRefreshdriverObserver) { 247 return; 248 } 249 250 PresShell* presShell = GetPresShell(); 251 if (!presShell) { 252 return; 253 } 254 255 nsPresContext* presContext = presShell->GetPresContext(); 256 if (!presContext) { 257 return; 258 } 259 260 // Using PostRefreshObserver is fine because we don't 261 // run any JS between the `mark paint timing` step and the 262 // `pending Event Timing entries` step. So mixing the order 263 // here is fine. 264 mHasQueuedRefreshdriverObserver = true; 265 presContext->RegisterManagedPostRefreshObserver( 266 new ManagedPostRefreshObserver( 267 presContext, [performance = RefPtr<PerformanceMainThread>(this)]( 268 bool aWasCanceled) { 269 if (!aWasCanceled) { 270 // XXX Should we do this even if canceled? 271 performance->DispatchPendingEventTimingEntries(); 272 } 273 performance->mHasQueuedRefreshdriverObserver = false; 274 return ManagedPostRefreshObserver::Unregister::Yes; 275 })); 276 } 277 278 void PerformanceMainThread::BufferEventTimingEntryIfNeeded( 279 PerformanceEventTiming* aEventEntry) { 280 if (mEventTimingEntries.Length() < kDefaultEventTimingBufferSize) { 281 mEventTimingEntries.AppendElement(aEventEntry); 282 } 283 } 284 285 void PerformanceMainThread::BufferLargestContentfulPaintEntryIfNeeded( 286 LargestContentfulPaint* aEntry) { 287 MOZ_ASSERT(StaticPrefs::dom_enable_largest_contentful_paint()); 288 if (mLargestContentfulPaintEntries.Length() < 289 kMaxLargestContentfulPaintBufferSize) { 290 mLargestContentfulPaintEntries.AppendElement(aEntry); 291 } 292 } 293 294 void PerformanceMainThread::DispatchPendingEventTimingEntries() { 295 DOMHighResTimeStamp renderingTime = NowUnclamped(); 296 297 auto entriesToBeQueuedEnd = mPendingEventTimingEntries.end(); 298 for (auto it = mPendingEventTimingEntries.begin(); 299 it != mPendingEventTimingEntries.end(); ++it) { 300 // Set its duration if it's not set already. 301 PerformanceEventTiming* entry = *it; 302 if (entry->RawDuration().isNothing()) { 303 entry->SetDuration(renderingTime - entry->RawStartTime()); 304 } 305 306 if (!(mPendingEventTimingEntries.end() != entriesToBeQueuedEnd) && 307 !entry->HasKnownInteractionId()) { 308 entriesToBeQueuedEnd = it; 309 } 310 } 311 312 if (!StaticPrefs::dom_performance_event_timing_enable_interactionid() || 313 mPendingEventTimingEntries.begin() != entriesToBeQueuedEnd) { 314 while (mPendingEventTimingEntries.begin() != entriesToBeQueuedEnd) { 315 RefPtr<PerformanceEventTiming> entry = 316 mPendingEventTimingEntries.popFirst(); 317 if (entry->RawDuration().valueOr(0) >= kDefaultEventTimingMinDuration) { 318 QueueEntry(entry); 319 } 320 321 // Perform the following steps to update the event counts: 322 IncEventCount(entry->GetName()); 323 324 // If window’s has dispatched input event is false, run the following 325 // steps: 326 if (StaticPrefs::dom_performance_event_timing_enable_interactionid()) { 327 if (!mHasDispatchedInputEvent && entry->InteractionId() != 0) { 328 mFirstInputEvent = entry->Clone(); 329 mFirstInputEvent->SetEntryType(nsGkAtoms::firstInput); 330 QueueEntry(mFirstInputEvent); 331 SetHasDispatchedInputEvent(); 332 } 333 } else { 334 if (!mHasDispatchedInputEvent) { 335 switch (entry->GetMessage()) { 336 case ePointerDown: { 337 mPendingPointerDown = entry->Clone(); 338 mPendingPointerDown->SetEntryType(nsGkAtoms::firstInput); 339 break; 340 } 341 case ePointerUp: { 342 if (mPendingPointerDown) { 343 MOZ_ASSERT(!mFirstInputEvent); 344 mFirstInputEvent = mPendingPointerDown.forget(); 345 QueueEntry(mFirstInputEvent); 346 SetHasDispatchedInputEvent(); 347 } 348 break; 349 } 350 case ePointerClick: 351 case eKeyDown: 352 case eMouseDown: { 353 mFirstInputEvent = entry->Clone(); 354 mFirstInputEvent->SetEntryType(nsGkAtoms::firstInput); 355 QueueEntry(mFirstInputEvent); 356 SetHasDispatchedInputEvent(); 357 break; 358 } 359 default: 360 break; 361 } 362 } 363 } 364 } 365 } 366 } 367 368 PerformanceInteractionMetrics& 369 PerformanceMainThread::GetPerformanceInteractionMetrics() { 370 return mInteractionMetrics; 371 } 372 373 void PerformanceMainThread::SetInteractionId( 374 PerformanceEventTiming* aEventTiming, const WidgetEvent* aEvent) { 375 MOZ_ASSERT(NS_IsMainThread()); 376 if (!StaticPrefs::dom_performance_event_timing_enable_interactionid() || 377 aEvent->mFlags.mOnlyChromeDispatch || !aEvent->IsTrusted()) { 378 aEventTiming->SetInteractionId(0); 379 return; 380 } 381 382 aEventTiming->SetInteractionId( 383 mInteractionMetrics.ComputeInteractionId(aEventTiming, aEvent)); 384 } 385 386 DOMHighResTimeStamp PerformanceMainThread::GetPerformanceTimingFromString( 387 const nsAString& aProperty) { 388 // ::Measure expects the values returned from this function to be passed 389 // through ReduceTimePrecision already. 390 if (!IsPerformanceTimingAttribute(aProperty)) { 391 return 0; 392 } 393 // Values from Timing() are already reduced 394 if (aProperty.EqualsLiteral("redirectStart")) { 395 return Timing()->RedirectStart(); 396 } 397 if (aProperty.EqualsLiteral("redirectEnd")) { 398 return Timing()->RedirectEnd(); 399 } 400 if (aProperty.EqualsLiteral("fetchStart")) { 401 return Timing()->FetchStart(); 402 } 403 if (aProperty.EqualsLiteral("domainLookupStart")) { 404 return Timing()->DomainLookupStart(); 405 } 406 if (aProperty.EqualsLiteral("domainLookupEnd")) { 407 return Timing()->DomainLookupEnd(); 408 } 409 if (aProperty.EqualsLiteral("connectStart")) { 410 return Timing()->ConnectStart(); 411 } 412 if (aProperty.EqualsLiteral("secureConnectionStart")) { 413 return Timing()->SecureConnectionStart(); 414 } 415 if (aProperty.EqualsLiteral("connectEnd")) { 416 return Timing()->ConnectEnd(); 417 } 418 if (aProperty.EqualsLiteral("requestStart")) { 419 return Timing()->RequestStart(); 420 } 421 if (aProperty.EqualsLiteral("responseStart")) { 422 return Timing()->ResponseStart(); 423 } 424 if (aProperty.EqualsLiteral("responseEnd")) { 425 return Timing()->ResponseEnd(); 426 } 427 // Values from GetDOMTiming() are not. 428 DOMHighResTimeStamp retValue; 429 if (aProperty.EqualsLiteral("navigationStart")) { 430 // DOMHighResTimeStamp is in relation to navigationStart, so this will be 431 // zero. 432 retValue = GetDOMTiming()->GetNavigationStart(); 433 } else if (aProperty.EqualsLiteral("unloadEventStart")) { 434 retValue = GetDOMTiming()->GetUnloadEventStart(); 435 } else if (aProperty.EqualsLiteral("unloadEventEnd")) { 436 retValue = GetDOMTiming()->GetUnloadEventEnd(); 437 } else if (aProperty.EqualsLiteral("domLoading")) { 438 retValue = GetDOMTiming()->GetDomLoading(); 439 } else if (aProperty.EqualsLiteral("domInteractive")) { 440 retValue = GetDOMTiming()->GetDomInteractive(); 441 } else if (aProperty.EqualsLiteral("domContentLoadedEventStart")) { 442 retValue = GetDOMTiming()->GetDomContentLoadedEventStart(); 443 } else if (aProperty.EqualsLiteral("domContentLoadedEventEnd")) { 444 retValue = GetDOMTiming()->GetDomContentLoadedEventEnd(); 445 } else if (aProperty.EqualsLiteral("domComplete")) { 446 retValue = GetDOMTiming()->GetDomComplete(); 447 } else if (aProperty.EqualsLiteral("loadEventStart")) { 448 retValue = GetDOMTiming()->GetLoadEventStart(); 449 } else if (aProperty.EqualsLiteral("loadEventEnd")) { 450 retValue = GetDOMTiming()->GetLoadEventEnd(); 451 } else { 452 MOZ_CRASH( 453 "IsPerformanceTimingAttribute and GetPerformanceTimingFromString are " 454 "out " 455 "of sync"); 456 } 457 return nsRFPService::ReduceTimePrecisionAsMSecs( 458 retValue, GetRandomTimelineSeed(), mRTPCallerType); 459 } 460 461 void PerformanceMainThread::InsertUserEntry(PerformanceEntry* aEntry) { 462 MOZ_ASSERT(NS_IsMainThread()); 463 464 nsAutoCString uri; 465 double markCreationEpoch = 0; 466 467 if (StaticPrefs::dom_performance_enable_user_timing_logging() || 468 StaticPrefs::dom_performance_enable_notify_performance_timing()) { 469 nsresult rv = NS_ERROR_FAILURE; 470 nsGlobalWindowInner* owner = GetOwnerWindow(); 471 if (owner && owner->GetDocumentURI()) { 472 rv = owner->GetDocumentURI()->GetHost(uri); 473 } 474 475 if (NS_FAILED(rv)) { 476 // If we have no URI, just put in "none". 477 uri.AssignLiteral("none"); 478 } 479 480 // PR_Now() returns a signed 64-bit integer. Since it represents a 481 // timestamp, only ~32-bits will represent the value which should safely fit 482 // into a double. 483 markCreationEpoch = static_cast<double>(PR_Now() / PR_USEC_PER_MSEC); 484 485 if (StaticPrefs::dom_performance_enable_user_timing_logging()) { 486 Performance::LogEntry(aEntry, uri); 487 } 488 } 489 490 if (StaticPrefs::dom_performance_enable_notify_performance_timing()) { 491 TimingNotification(aEntry, uri, markCreationEpoch); 492 } 493 494 Performance::InsertUserEntry(aEntry); 495 } 496 497 TimeStamp PerformanceMainThread::CreationTimeStamp() const { 498 return GetDOMTiming()->GetNavigationStartTimeStamp(); 499 } 500 501 DOMHighResTimeStamp PerformanceMainThread::CreationTime() const { 502 return GetDOMTiming()->GetNavigationStart(); 503 } 504 505 void PerformanceMainThread::CreateNavigationTimingEntry() { 506 MOZ_ASSERT(!mDocEntry, "mDocEntry should be null."); 507 508 if (!StaticPrefs::dom_enable_performance_navigation_timing()) { 509 return; 510 } 511 512 nsAutoString name; 513 GetURLSpecFromChannel(mChannel, name); 514 515 UniquePtr<PerformanceTimingData> timing( 516 new PerformanceTimingData(mChannel, nullptr, 0)); 517 518 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel); 519 if (httpChannel) { 520 timing->SetPropertiesFromHttpChannel(httpChannel, mChannel); 521 } 522 523 mDocEntry = new PerformanceNavigationTiming(std::move(timing), this, name); 524 } 525 526 void PerformanceMainThread::UpdateNavigationTimingEntry() { 527 if (!mDocEntry) { 528 return; 529 } 530 531 // Let's update some values. 532 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel); 533 if (httpChannel) { 534 mDocEntry->UpdatePropertiesFromHttpChannel(httpChannel, mChannel); 535 } 536 } 537 538 void PerformanceMainThread::QueueNavigationTimingEntry() { 539 if (!mDocEntry) { 540 return; 541 } 542 543 UpdateNavigationTimingEntry(); 544 545 QueueEntry(mDocEntry); 546 } 547 548 void PerformanceMainThread::QueueLargestContentfulPaintEntry( 549 LargestContentfulPaint* aEntry) { 550 MOZ_ASSERT(StaticPrefs::dom_enable_largest_contentful_paint()); 551 QueueEntry(aEntry); 552 } 553 554 EventCounts* PerformanceMainThread::EventCounts() { 555 MOZ_ASSERT(StaticPrefs::dom_enable_event_timing()); 556 return mEventCounts; 557 } 558 559 uint64_t PerformanceMainThread::InteractionCount() { 560 MOZ_ASSERT(StaticPrefs::dom_performance_event_timing_enable_interactionid()); 561 return mInteractionMetrics.InteractionCount(); 562 } 563 564 void PerformanceMainThread::GetEntries( 565 nsTArray<RefPtr<PerformanceEntry>>& aRetval) { 566 aRetval = mResourceEntries.Clone(); 567 aRetval.AppendElements(mUserEntries); 568 569 if (mDocEntry) { 570 aRetval.AppendElement(mDocEntry); 571 } 572 573 if (mFCPTiming) { 574 aRetval.AppendElement(mFCPTiming); 575 } 576 aRetval.Sort(PerformanceEntryComparator()); 577 } 578 579 void PerformanceMainThread::GetEntriesByType( 580 const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) { 581 RefPtr<nsAtom> type = NS_Atomize(aEntryType); 582 if (type == nsGkAtoms::navigation) { 583 aRetval.Clear(); 584 585 if (mDocEntry) { 586 aRetval.AppendElement(mDocEntry); 587 } 588 return; 589 } 590 591 if (type == nsGkAtoms::paint) { 592 if (mFCPTiming) { 593 aRetval.AppendElement(mFCPTiming); 594 return; 595 } 596 } 597 598 if (type == nsGkAtoms::firstInput && mFirstInputEvent) { 599 aRetval.AppendElement(mFirstInputEvent); 600 return; 601 } 602 603 Performance::GetEntriesByType(aEntryType, aRetval); 604 } 605 void PerformanceMainThread::GetEntriesByTypeForObserver( 606 const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) { 607 if (aEntryType.EqualsLiteral("event")) { 608 aRetval.AppendElements(mEventTimingEntries); 609 return; 610 } 611 612 if (StaticPrefs::dom_enable_largest_contentful_paint()) { 613 if (aEntryType.EqualsLiteral("largest-contentful-paint")) { 614 aRetval.AppendElements(mLargestContentfulPaintEntries); 615 return; 616 } 617 } 618 619 return GetEntriesByType(aEntryType, aRetval); 620 } 621 622 void PerformanceMainThread::GetEntriesByName( 623 const nsAString& aName, const Optional<nsAString>& aEntryType, 624 nsTArray<RefPtr<PerformanceEntry>>& aRetval) { 625 Performance::GetEntriesByName(aName, aEntryType, aRetval); 626 627 if (mFCPTiming && mFCPTiming->GetName()->Equals(aName) && 628 (!aEntryType.WasPassed() || 629 mFCPTiming->GetEntryType()->Equals(aEntryType.Value()))) { 630 aRetval.AppendElement(mFCPTiming); 631 return; 632 } 633 634 // The navigation entry is the first one. If it exists and the name matches, 635 // let put it in front. 636 if (mDocEntry && mDocEntry->GetName()->Equals(aName)) { 637 aRetval.InsertElementAt(0, mDocEntry); 638 return; 639 } 640 } 641 642 mozilla::PresShell* PerformanceMainThread::GetPresShell() { 643 nsIGlobalObject* ownerGlobal = GetOwnerGlobal(); 644 if (!ownerGlobal) { 645 return nullptr; 646 } 647 if (Document* doc = ownerGlobal->GetAsInnerWindow()->GetExtantDoc()) { 648 return doc->GetPresShell(); 649 } 650 return nullptr; 651 } 652 653 void PerformanceMainThread::IncEventCount(const nsAtom* aType) { 654 MOZ_ASSERT(StaticPrefs::dom_enable_event_timing()); 655 656 // This occurs when the pref was false when the performance 657 // object was first created, and became true later. It's 658 // okay to return early because eventCounts is not exposed. 659 if (!mEventCounts) { 660 return; 661 } 662 663 IgnoredErrorResult rv; 664 uint64_t count = EventCounts_Binding::MaplikeHelpers::Get( 665 mEventCounts, nsDependentAtomString(aType), rv); 666 if (rv.Failed()) { 667 return; 668 } 669 EventCounts_Binding::MaplikeHelpers::Set( 670 mEventCounts, nsDependentAtomString(aType), ++count, rv); 671 } 672 673 size_t PerformanceMainThread::SizeOfEventEntries( 674 mozilla::MallocSizeOf aMallocSizeOf) const { 675 size_t eventEntries = 0; 676 for (const PerformanceEventTiming* entry : mEventTimingEntries) { 677 eventEntries += entry->SizeOfIncludingThis(aMallocSizeOf); 678 } 679 return eventEntries; 680 } 681 682 void PerformanceMainThread::ProcessElementTiming() { 683 if (!StaticPrefs::dom_enable_largest_contentful_paint()) { 684 return; 685 } 686 const bool shouldLCPDataEmpty = 687 HasDispatchedInputEvent() || HasDispatchedScrollEvent(); 688 MOZ_ASSERT_IF(shouldLCPDataEmpty, mTextFrameUnions.IsEmpty()); 689 690 if (shouldLCPDataEmpty) { 691 return; 692 } 693 694 nsPresContext* presContext = GetPresShell()->GetPresContext(); 695 MOZ_ASSERT(presContext); 696 697 // After https://github.com/w3c/largest-contentful-paint/issues/104 is 698 // resolved, LargestContentfulPaint and FirstContentfulPaint should 699 // be using the same timestamp, which should be the same timestamp 700 // as to what https://w3c.github.io/paint-timing/#mark-paint-timing step 2 701 // defines. 702 // TODO(sefeng): Check the timestamp after this issue is resolved. 703 TimeStamp rawNowTime = presContext->GetMarkPaintTimingStart(); 704 705 MOZ_ASSERT(GetOwnerGlobal()); 706 Document* document = GetOwnerGlobal()->GetAsInnerWindow()->GetExtantDoc(); 707 if (!document || 708 !nsContentUtils::GetInProcessSubtreeRootDocument(document)->IsActive()) { 709 return; 710 } 711 712 nsTArray<ImagePendingRendering> imagesPendingRendering = 713 std::move(mImagesPendingRendering); 714 for (const auto& imagePendingRendering : imagesPendingRendering) { 715 RefPtr<Element> element = imagePendingRendering.GetElement(); 716 if (!element) { 717 continue; 718 } 719 720 MOZ_ASSERT(imagePendingRendering.mLoadTime <= rawNowTime); 721 if (imgRequestProxy* requestProxy = 722 imagePendingRendering.GetImgRequestProxy()) { 723 requestProxy->GetLCPTimings().Set(imagePendingRendering.mLoadTime, 724 rawNowTime); 725 } 726 } 727 728 MOZ_ASSERT(mImagesPendingRendering.IsEmpty()); 729 } 730 731 void PerformanceMainThread::FinalizeLCPEntriesForText() { 732 nsPresContext* presContext = GetPresShell()->GetPresContext(); 733 MOZ_ASSERT(presContext); 734 735 bool canFinalize = StaticPrefs::dom_enable_largest_contentful_paint() && 736 !presContext->HasStoppedGeneratingLCP(); 737 nsTHashMap<nsRefPtrHashKey<Element>, nsRect> textFrameUnion = 738 std::move(GetTextFrameUnions()); 739 if (canFinalize) { 740 for (const auto& textFrameUnion : textFrameUnion) { 741 LCPHelpers::FinalizeLCPEntryForText( 742 this, presContext->GetMarkPaintTimingStart(), textFrameUnion.GetKey(), 743 textFrameUnion.GetData(), presContext); 744 } 745 } 746 MOZ_ASSERT(GetTextFrameUnions().IsEmpty()); 747 } 748 749 bool PerformanceMainThread::IsPendingLCPCandidate( 750 Element* aElement, imgRequestProxy* aImgRequestProxy) { 751 Document* doc = aElement->GetComposedDoc(); 752 MOZ_ASSERT(doc, "Element should be connected when it's painted"); 753 if (!aElement->HasFlag(ELEMENT_IN_CONTENT_IDENTIFIER_FOR_LCP)) { 754 MOZ_ASSERT(!doc->ContentIdentifiersForLCP().Contains(aElement)); 755 return false; 756 } 757 758 if (auto entry = doc->ContentIdentifiersForLCP().Lookup(aElement)) { 759 return entry.Data().Contains(aImgRequestProxy); 760 } 761 762 MOZ_ASSERT_UNREACHABLE("we should always have an entry when the flag exists"); 763 return false; 764 } 765 766 bool PerformanceMainThread::UpdateLargestContentfulPaintSize(double aSize) { 767 if (aSize > mLargestContentfulPaintSize) { 768 mLargestContentfulPaintSize = aSize; 769 return true; 770 } 771 return false; 772 } 773 774 void PerformanceMainThread::SetHasDispatchedScrollEvent() { 775 mHasDispatchedScrollEvent = true; 776 ClearGeneratedTempDataForLCP(); 777 } 778 779 void PerformanceMainThread::SetHasDispatchedInputEvent() { 780 mHasDispatchedInputEvent = true; 781 ClearGeneratedTempDataForLCP(); 782 } 783 784 void PerformanceMainThread::ClearGeneratedTempDataForLCP() { 785 mTextFrameUnions.Clear(); 786 mImagesPendingRendering.Clear(); 787 788 nsIGlobalObject* ownerGlobal = GetOwnerGlobal(); 789 if (!ownerGlobal) { 790 return; 791 } 792 793 if (Document* document = ownerGlobal->GetAsInnerWindow()->GetExtantDoc()) { 794 document->ContentIdentifiersForLCP().Clear(); 795 } 796 } 797 } // namespace mozilla::dom