Bug 1892041 - Part 1: Update test262 features. r=spidermonkey-reviewers,dminor
[gecko.git] / dom / performance / PerformanceMainThread.cpp
blob324f944d50e5bc358b457f30768888c12ed0c19e
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/. */
7 #include "PerformanceMainThread.h"
8 #include "PerformanceNavigation.h"
9 #include "PerformancePaintTiming.h"
10 #include "jsapi.h"
11 #include "js/GCAPI.h"
12 #include "js/PropertyAndElement.h" // JS_DefineProperty
13 #include "mozilla/HoldDropJSObjects.h"
14 #include "PerformanceEventTiming.h"
15 #include "LargestContentfulPaint.h"
16 #include "mozilla/dom/Document.h"
17 #include "mozilla/dom/Event.h"
18 #include "mozilla/dom/EventCounts.h"
19 #include "mozilla/dom/PerformanceEventTimingBinding.h"
20 #include "mozilla/dom/PerformanceNavigationTiming.h"
21 #include "mozilla/dom/PerformanceResourceTiming.h"
22 #include "mozilla/dom/PerformanceTiming.h"
23 #include "mozilla/StaticPrefs_dom.h"
24 #include "mozilla/StaticPrefs_privacy.h"
25 #include "mozilla/PresShell.h"
26 #include "nsIChannel.h"
27 #include "nsIHttpChannel.h"
28 #include "nsIDocShell.h"
29 #include "nsTextFrame.h"
30 #include "nsContainerFrame.h"
32 namespace mozilla::dom {
34 extern mozilla::LazyLogModule gLCPLogging;
36 namespace {
38 void GetURLSpecFromChannel(nsITimedChannel* aChannel, nsAString& aSpec) {
39 aSpec.AssignLiteral("document");
41 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aChannel);
42 if (!channel) {
43 return;
46 nsCOMPtr<nsIURI> uri;
47 nsresult rv = channel->GetURI(getter_AddRefs(uri));
48 if (NS_WARN_IF(NS_FAILED(rv)) || !uri) {
49 return;
52 nsAutoCString spec;
53 rv = uri->GetSpec(spec);
54 if (NS_WARN_IF(NS_FAILED(rv))) {
55 return;
58 CopyUTF8toUTF16(spec, aSpec);
61 } // namespace
63 NS_IMPL_CYCLE_COLLECTION_CLASS(PerformanceMainThread)
65 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PerformanceMainThread,
66 Performance)
67 NS_IMPL_CYCLE_COLLECTION_UNLINK(
68 mTiming, mNavigation, mDocEntry, mFCPTiming, mEventTimingEntries,
69 mLargestContentfulPaintEntries, mFirstInputEvent, mPendingPointerDown,
70 mPendingEventTimingEntries, mEventCounts)
71 tmp->mImageLCPEntryMap.Clear();
72 tmp->mTextFrameUnions.Clear();
73 mozilla::DropJSObjects(tmp);
74 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
76 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PerformanceMainThread,
77 Performance)
78 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
79 mTiming, mNavigation, mDocEntry, mFCPTiming, mEventTimingEntries,
80 mLargestContentfulPaintEntries, mFirstInputEvent, mPendingPointerDown,
81 mPendingEventTimingEntries, mEventCounts, mImageLCPEntryMap,
82 mTextFrameUnions)
84 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
86 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceMainThread,
87 Performance)
88 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMozMemory)
89 NS_IMPL_CYCLE_COLLECTION_TRACE_END
91 NS_IMPL_ADDREF_INHERITED(PerformanceMainThread, Performance)
92 NS_IMPL_RELEASE_INHERITED(PerformanceMainThread, Performance)
94 // QueryInterface implementation for PerformanceMainThread
95 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceMainThread)
96 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
97 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, EventTarget)
98 NS_INTERFACE_MAP_END_INHERITING(Performance)
100 PerformanceMainThread::PerformanceMainThread(nsPIDOMWindowInner* aWindow,
101 nsDOMNavigationTiming* aDOMTiming,
102 nsITimedChannel* aChannel)
103 : Performance(aWindow->AsGlobal()),
104 mDOMTiming(aDOMTiming),
105 mChannel(aChannel) {
106 MOZ_ASSERT(aWindow, "Parent window object should be provided");
107 if (StaticPrefs::dom_enable_event_timing()) {
108 mEventCounts = new class EventCounts(GetParentObject());
110 CreateNavigationTimingEntry();
112 if (StaticPrefs::dom_enable_largest_contentful_paint()) {
113 nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
114 MarkerInnerWindowId innerWindowID =
115 owner ? MarkerInnerWindowId(owner->WindowID())
116 : MarkerInnerWindowId::NoId();
117 // There might be multiple LCP entries and we only care about the latest one
118 // which is also the biggest value. That's why we need to record these
119 // markers in two different places:
120 // - During the Document unload, so we can record the closed pages.
121 // - During the profile capture, so we can record the open pages.
122 // We are capturing the second one here.
123 // Our static analysis doesn't allow capturing ref-counted pointers in
124 // lambdas, so we need to hide it in a uintptr_t. This is safe because this
125 // lambda will be destroyed in ~PerformanceMainThread().
126 uintptr_t self = reinterpret_cast<uintptr_t>(this);
127 profiler_add_state_change_callback(
128 // Using the "Pausing" state as "GeneratingProfile" profile happens too
129 // late; we can not record markers if the profiler is already paused.
130 ProfilingState::Pausing,
131 [self, innerWindowID](ProfilingState aProfilingState) {
132 const PerformanceMainThread* selfPtr =
133 reinterpret_cast<const PerformanceMainThread*>(self);
135 selfPtr->GetDOMTiming()->MaybeAddLCPProfilerMarker(innerWindowID);
137 self);
141 PerformanceMainThread::~PerformanceMainThread() {
142 profiler_remove_state_change_callback(reinterpret_cast<uintptr_t>(this));
143 mozilla::DropJSObjects(this);
146 void PerformanceMainThread::GetMozMemory(JSContext* aCx,
147 JS::MutableHandle<JSObject*> aObj) {
148 if (!mMozMemory) {
149 JS::Rooted<JSObject*> mozMemoryObj(aCx, JS_NewPlainObject(aCx));
150 JS::Rooted<JSObject*> gcMemoryObj(aCx, js::gc::NewMemoryInfoObject(aCx));
151 if (!mozMemoryObj || !gcMemoryObj) {
152 MOZ_CRASH("out of memory creating performance.mozMemory");
154 if (!JS_DefineProperty(aCx, mozMemoryObj, "gc", gcMemoryObj,
155 JSPROP_ENUMERATE)) {
156 MOZ_CRASH("out of memory creating performance.mozMemory");
158 mMozMemory = mozMemoryObj;
159 mozilla::HoldJSObjects(this);
162 aObj.set(mMozMemory);
165 PerformanceTiming* PerformanceMainThread::Timing() {
166 if (!mTiming) {
167 // For navigation timing, the third argument (an nsIHttpChannel) is null
168 // since the cross-domain redirect were already checked. The last
169 // argument (zero time) for performance.timing is the navigation start
170 // value.
171 mTiming = new PerformanceTiming(this, mChannel, nullptr,
172 mDOMTiming->GetNavigationStart());
175 return mTiming;
178 void PerformanceMainThread::DispatchBufferFullEvent() {
179 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
180 // it bubbles, and it isn't cancelable
181 event->InitEvent(u"resourcetimingbufferfull"_ns, true, false);
182 event->SetTrusted(true);
183 DispatchEvent(*event);
186 PerformanceNavigation* PerformanceMainThread::Navigation() {
187 if (!mNavigation) {
188 mNavigation = new PerformanceNavigation(this);
191 return mNavigation;
195 * An entry should be added only after the resource is loaded.
196 * This method is not thread safe and can only be called on the main thread.
198 void PerformanceMainThread::AddEntry(nsIHttpChannel* channel,
199 nsITimedChannel* timedChannel) {
200 MOZ_ASSERT(NS_IsMainThread());
202 nsAutoString initiatorType;
203 nsAutoString entryName;
205 UniquePtr<PerformanceTimingData> performanceTimingData(
206 PerformanceTimingData::Create(timedChannel, channel, 0, initiatorType,
207 entryName));
208 if (!performanceTimingData) {
209 return;
211 AddRawEntry(std::move(performanceTimingData), initiatorType, entryName);
214 void PerformanceMainThread::AddEntry(const nsString& entryName,
215 const nsString& initiatorType,
216 UniquePtr<PerformanceTimingData>&& aData) {
217 AddRawEntry(std::move(aData), initiatorType, entryName);
220 void PerformanceMainThread::AddRawEntry(UniquePtr<PerformanceTimingData> aData,
221 const nsAString& aInitiatorType,
222 const nsAString& aEntryName) {
223 // The PerformanceResourceTiming object will use the PerformanceTimingData
224 // object to get all the required timings.
225 auto entry =
226 MakeRefPtr<PerformanceResourceTiming>(std::move(aData), this, aEntryName);
227 entry->SetInitiatorType(aInitiatorType);
228 InsertResourceEntry(entry);
231 void PerformanceMainThread::SetFCPTimingEntry(PerformancePaintTiming* aEntry) {
232 MOZ_ASSERT(aEntry);
233 if (!mFCPTiming) {
234 mFCPTiming = aEntry;
235 QueueEntry(aEntry);
239 void PerformanceMainThread::InsertEventTimingEntry(
240 PerformanceEventTiming* aEventEntry) {
241 mPendingEventTimingEntries.insertBack(aEventEntry);
243 if (mHasQueuedRefreshdriverObserver) {
244 return;
247 PresShell* presShell = GetPresShell();
248 if (!presShell) {
249 return;
252 nsPresContext* presContext = presShell->GetPresContext();
253 if (!presContext) {
254 return;
257 // Using PostRefreshObserver is fine because we don't
258 // run any JS between the `mark paint timing` step and the
259 // `pending Event Timing entries` step. So mixing the order
260 // here is fine.
261 mHasQueuedRefreshdriverObserver = true;
262 presContext->RegisterManagedPostRefreshObserver(
263 new ManagedPostRefreshObserver(
264 presContext, [performance = RefPtr<PerformanceMainThread>(this)](
265 bool aWasCanceled) {
266 if (!aWasCanceled) {
267 // XXX Should we do this even if canceled?
268 performance->DispatchPendingEventTimingEntries();
270 performance->mHasQueuedRefreshdriverObserver = false;
271 return ManagedPostRefreshObserver::Unregister::Yes;
272 }));
275 void PerformanceMainThread::BufferEventTimingEntryIfNeeded(
276 PerformanceEventTiming* aEventEntry) {
277 if (mEventTimingEntries.Length() < kDefaultEventTimingBufferSize) {
278 mEventTimingEntries.AppendElement(aEventEntry);
282 void PerformanceMainThread::BufferLargestContentfulPaintEntryIfNeeded(
283 LargestContentfulPaint* aEntry) {
284 MOZ_ASSERT(StaticPrefs::dom_enable_largest_contentful_paint());
285 if (mLargestContentfulPaintEntries.Length() <
286 kMaxLargestContentfulPaintBufferSize) {
287 mLargestContentfulPaintEntries.AppendElement(aEntry);
291 void PerformanceMainThread::DispatchPendingEventTimingEntries() {
292 DOMHighResTimeStamp renderingTime = NowUnclamped();
294 while (!mPendingEventTimingEntries.isEmpty()) {
295 RefPtr<PerformanceEventTiming> entry =
296 mPendingEventTimingEntries.popFirst();
298 entry->SetDuration(renderingTime - entry->RawStartTime());
299 IncEventCount(entry->GetName());
301 if (entry->RawDuration() >= kDefaultEventTimingMinDuration) {
302 QueueEntry(entry);
305 if (!mHasDispatchedInputEvent) {
306 switch (entry->GetMessage()) {
307 case ePointerDown: {
308 mPendingPointerDown = entry->Clone();
309 mPendingPointerDown->SetEntryType(u"first-input"_ns);
310 break;
312 case ePointerUp: {
313 if (mPendingPointerDown) {
314 MOZ_ASSERT(!mFirstInputEvent);
315 mFirstInputEvent = mPendingPointerDown.forget();
316 QueueEntry(mFirstInputEvent);
317 SetHasDispatchedInputEvent();
319 break;
321 case eMouseClick:
322 case eKeyDown:
323 case eMouseDown: {
324 mFirstInputEvent = entry->Clone();
325 mFirstInputEvent->SetEntryType(u"first-input"_ns);
326 QueueEntry(mFirstInputEvent);
327 SetHasDispatchedInputEvent();
328 break;
330 default:
331 break;
337 DOMHighResTimeStamp PerformanceMainThread::GetPerformanceTimingFromString(
338 const nsAString& aProperty) {
339 // ::Measure expects the values returned from this function to be passed
340 // through ReduceTimePrecision already.
341 if (!IsPerformanceTimingAttribute(aProperty)) {
342 return 0;
344 // Values from Timing() are already reduced
345 if (aProperty.EqualsLiteral("redirectStart")) {
346 return Timing()->RedirectStart();
348 if (aProperty.EqualsLiteral("redirectEnd")) {
349 return Timing()->RedirectEnd();
351 if (aProperty.EqualsLiteral("fetchStart")) {
352 return Timing()->FetchStart();
354 if (aProperty.EqualsLiteral("domainLookupStart")) {
355 return Timing()->DomainLookupStart();
357 if (aProperty.EqualsLiteral("domainLookupEnd")) {
358 return Timing()->DomainLookupEnd();
360 if (aProperty.EqualsLiteral("connectStart")) {
361 return Timing()->ConnectStart();
363 if (aProperty.EqualsLiteral("secureConnectionStart")) {
364 return Timing()->SecureConnectionStart();
366 if (aProperty.EqualsLiteral("connectEnd")) {
367 return Timing()->ConnectEnd();
369 if (aProperty.EqualsLiteral("requestStart")) {
370 return Timing()->RequestStart();
372 if (aProperty.EqualsLiteral("responseStart")) {
373 return Timing()->ResponseStart();
375 if (aProperty.EqualsLiteral("responseEnd")) {
376 return Timing()->ResponseEnd();
378 // Values from GetDOMTiming() are not.
379 DOMHighResTimeStamp retValue;
380 if (aProperty.EqualsLiteral("navigationStart")) {
381 // DOMHighResTimeStamp is in relation to navigationStart, so this will be
382 // zero.
383 retValue = GetDOMTiming()->GetNavigationStart();
384 } else if (aProperty.EqualsLiteral("unloadEventStart")) {
385 retValue = GetDOMTiming()->GetUnloadEventStart();
386 } else if (aProperty.EqualsLiteral("unloadEventEnd")) {
387 retValue = GetDOMTiming()->GetUnloadEventEnd();
388 } else if (aProperty.EqualsLiteral("domLoading")) {
389 retValue = GetDOMTiming()->GetDomLoading();
390 } else if (aProperty.EqualsLiteral("domInteractive")) {
391 retValue = GetDOMTiming()->GetDomInteractive();
392 } else if (aProperty.EqualsLiteral("domContentLoadedEventStart")) {
393 retValue = GetDOMTiming()->GetDomContentLoadedEventStart();
394 } else if (aProperty.EqualsLiteral("domContentLoadedEventEnd")) {
395 retValue = GetDOMTiming()->GetDomContentLoadedEventEnd();
396 } else if (aProperty.EqualsLiteral("domComplete")) {
397 retValue = GetDOMTiming()->GetDomComplete();
398 } else if (aProperty.EqualsLiteral("loadEventStart")) {
399 retValue = GetDOMTiming()->GetLoadEventStart();
400 } else if (aProperty.EqualsLiteral("loadEventEnd")) {
401 retValue = GetDOMTiming()->GetLoadEventEnd();
402 } else {
403 MOZ_CRASH(
404 "IsPerformanceTimingAttribute and GetPerformanceTimingFromString are "
405 "out "
406 "of sync");
408 return nsRFPService::ReduceTimePrecisionAsMSecs(
409 retValue, GetRandomTimelineSeed(), mRTPCallerType);
412 void PerformanceMainThread::InsertUserEntry(PerformanceEntry* aEntry) {
413 MOZ_ASSERT(NS_IsMainThread());
415 nsAutoCString uri;
416 double markCreationEpoch = 0;
418 if (StaticPrefs::dom_performance_enable_user_timing_logging() ||
419 StaticPrefs::dom_performance_enable_notify_performance_timing()) {
420 nsresult rv = NS_ERROR_FAILURE;
421 nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
422 if (owner && owner->GetDocumentURI()) {
423 rv = owner->GetDocumentURI()->GetHost(uri);
426 if (NS_FAILED(rv)) {
427 // If we have no URI, just put in "none".
428 uri.AssignLiteral("none");
431 // PR_Now() returns a signed 64-bit integer. Since it represents a
432 // timestamp, only ~32-bits will represent the value which should safely fit
433 // into a double.
434 markCreationEpoch = static_cast<double>(PR_Now() / PR_USEC_PER_MSEC);
436 if (StaticPrefs::dom_performance_enable_user_timing_logging()) {
437 Performance::LogEntry(aEntry, uri);
441 if (StaticPrefs::dom_performance_enable_notify_performance_timing()) {
442 TimingNotification(aEntry, uri, markCreationEpoch);
445 Performance::InsertUserEntry(aEntry);
448 TimeStamp PerformanceMainThread::CreationTimeStamp() const {
449 return GetDOMTiming()->GetNavigationStartTimeStamp();
452 DOMHighResTimeStamp PerformanceMainThread::CreationTime() const {
453 return GetDOMTiming()->GetNavigationStart();
456 void PerformanceMainThread::CreateNavigationTimingEntry() {
457 MOZ_ASSERT(!mDocEntry, "mDocEntry should be null.");
459 if (!StaticPrefs::dom_enable_performance_navigation_timing()) {
460 return;
463 nsAutoString name;
464 GetURLSpecFromChannel(mChannel, name);
466 UniquePtr<PerformanceTimingData> timing(
467 new PerformanceTimingData(mChannel, nullptr, 0));
469 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
470 if (httpChannel) {
471 timing->SetPropertiesFromHttpChannel(httpChannel, mChannel);
474 mDocEntry = new PerformanceNavigationTiming(std::move(timing), this, name);
477 void PerformanceMainThread::UpdateNavigationTimingEntry() {
478 if (!mDocEntry) {
479 return;
482 // Let's update some values.
483 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
484 if (httpChannel) {
485 mDocEntry->UpdatePropertiesFromHttpChannel(httpChannel, mChannel);
489 void PerformanceMainThread::QueueNavigationTimingEntry() {
490 if (!mDocEntry) {
491 return;
494 UpdateNavigationTimingEntry();
496 QueueEntry(mDocEntry);
499 void PerformanceMainThread::QueueLargestContentfulPaintEntry(
500 LargestContentfulPaint* aEntry) {
501 MOZ_ASSERT(StaticPrefs::dom_enable_largest_contentful_paint());
502 QueueEntry(aEntry);
505 EventCounts* PerformanceMainThread::EventCounts() {
506 MOZ_ASSERT(StaticPrefs::dom_enable_event_timing());
507 return mEventCounts;
510 void PerformanceMainThread::GetEntries(
511 nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
512 aRetval = mResourceEntries.Clone();
513 aRetval.AppendElements(mUserEntries);
515 if (mDocEntry) {
516 aRetval.AppendElement(mDocEntry);
519 if (mFCPTiming) {
520 aRetval.AppendElement(mFCPTiming);
522 aRetval.Sort(PerformanceEntryComparator());
525 void PerformanceMainThread::GetEntriesByType(
526 const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
527 RefPtr<nsAtom> type = NS_Atomize(aEntryType);
528 if (type == nsGkAtoms::navigation) {
529 aRetval.Clear();
531 if (mDocEntry) {
532 aRetval.AppendElement(mDocEntry);
534 return;
537 if (type == nsGkAtoms::paint) {
538 if (mFCPTiming) {
539 aRetval.AppendElement(mFCPTiming);
540 return;
544 if (type == nsGkAtoms::firstInput && mFirstInputEvent) {
545 aRetval.AppendElement(mFirstInputEvent);
546 return;
549 Performance::GetEntriesByType(aEntryType, aRetval);
551 void PerformanceMainThread::GetEntriesByTypeForObserver(
552 const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
553 if (aEntryType.EqualsLiteral("event")) {
554 aRetval.AppendElements(mEventTimingEntries);
555 return;
558 if (StaticPrefs::dom_enable_largest_contentful_paint()) {
559 if (aEntryType.EqualsLiteral("largest-contentful-paint")) {
560 aRetval.AppendElements(mLargestContentfulPaintEntries);
561 return;
565 return GetEntriesByType(aEntryType, aRetval);
568 void PerformanceMainThread::GetEntriesByName(
569 const nsAString& aName, const Optional<nsAString>& aEntryType,
570 nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
571 Performance::GetEntriesByName(aName, aEntryType, aRetval);
573 if (mFCPTiming && mFCPTiming->GetName()->Equals(aName) &&
574 (!aEntryType.WasPassed() ||
575 mFCPTiming->GetEntryType()->Equals(aEntryType.Value()))) {
576 aRetval.AppendElement(mFCPTiming);
577 return;
580 // The navigation entry is the first one. If it exists and the name matches,
581 // let put it in front.
582 if (mDocEntry && mDocEntry->GetName()->Equals(aName)) {
583 aRetval.InsertElementAt(0, mDocEntry);
584 return;
588 mozilla::PresShell* PerformanceMainThread::GetPresShell() {
589 nsIGlobalObject* ownerGlobal = GetOwnerGlobal();
590 if (!ownerGlobal) {
591 return nullptr;
593 if (Document* doc = ownerGlobal->GetAsInnerWindow()->GetExtantDoc()) {
594 return doc->GetPresShell();
596 return nullptr;
599 void PerformanceMainThread::IncEventCount(const nsAtom* aType) {
600 MOZ_ASSERT(StaticPrefs::dom_enable_event_timing());
602 // This occurs when the pref was false when the performance
603 // object was first created, and became true later. It's
604 // okay to return early because eventCounts is not exposed.
605 if (!mEventCounts) {
606 return;
609 ErrorResult rv;
610 uint64_t count = EventCounts_Binding::MaplikeHelpers::Get(
611 mEventCounts, nsDependentAtomString(aType), rv);
612 MOZ_ASSERT(!rv.Failed());
613 EventCounts_Binding::MaplikeHelpers::Set(
614 mEventCounts, nsDependentAtomString(aType), ++count, rv);
615 MOZ_ASSERT(!rv.Failed());
618 size_t PerformanceMainThread::SizeOfEventEntries(
619 mozilla::MallocSizeOf aMallocSizeOf) const {
620 size_t eventEntries = 0;
621 for (const PerformanceEventTiming* entry : mEventTimingEntries) {
622 eventEntries += entry->SizeOfIncludingThis(aMallocSizeOf);
624 return eventEntries;
627 void PerformanceMainThread::ProcessElementTiming() {
628 if (!StaticPrefs::dom_enable_largest_contentful_paint()) {
629 return;
631 const bool shouldLCPDataEmpty =
632 HasDispatchedInputEvent() || HasDispatchedScrollEvent();
633 MOZ_ASSERT_IF(shouldLCPDataEmpty,
634 mTextFrameUnions.IsEmpty() && mImageLCPEntryMap.IsEmpty());
636 if (shouldLCPDataEmpty) {
637 return;
640 nsPresContext* presContext = GetPresShell()->GetPresContext();
641 MOZ_ASSERT(presContext);
643 // After https://github.com/w3c/largest-contentful-paint/issues/104 is
644 // resolved, LargestContentfulPaint and FirstContentfulPaint should
645 // be using the same timestamp, which should be the same timestamp
646 // as to what https://w3c.github.io/paint-timing/#mark-paint-timing step 2
647 // defines.
648 // TODO(sefeng): Check the timestamp after this issue is resolved.
649 TimeStamp rawNowTime = presContext->GetMarkPaintTimingStart();
651 MOZ_ASSERT(GetOwnerGlobal());
652 Document* document = GetOwnerGlobal()->GetAsInnerWindow()->GetExtantDoc();
653 if (!document ||
654 !nsContentUtils::GetInProcessSubtreeRootDocument(document)->IsActive()) {
655 return;
658 nsTArray<ImagePendingRendering> imagesPendingRendering =
659 std::move(mImagesPendingRendering);
660 for (const auto& imagePendingRendering : imagesPendingRendering) {
661 RefPtr<Element> element = imagePendingRendering.GetElement();
662 if (!element) {
663 continue;
666 MOZ_ASSERT(imagePendingRendering.mLoadTime <= rawNowTime);
667 if (imgRequestProxy* requestProxy =
668 imagePendingRendering.GetImgRequestProxy()) {
669 LCPHelpers::CreateLCPEntryForImage(
670 this, element, requestProxy, imagePendingRendering.mLoadTime,
671 rawNowTime, imagePendingRendering.mLCPImageEntryKey);
675 MOZ_ASSERT(mImagesPendingRendering.IsEmpty());
678 void PerformanceMainThread::FinalizeLCPEntriesForText() {
679 nsPresContext* presContext = GetPresShell()->GetPresContext();
680 MOZ_ASSERT(presContext);
682 bool canFinalize = StaticPrefs::dom_enable_largest_contentful_paint() &&
683 !presContext->HasStoppedGeneratingLCP();
684 nsTHashMap<nsRefPtrHashKey<Element>, nsRect> textFrameUnion =
685 std::move(GetTextFrameUnions());
686 if (canFinalize) {
687 for (const auto& textFrameUnion : textFrameUnion) {
688 LCPHelpers::FinalizeLCPEntryForText(
689 this, presContext->GetMarkPaintTimingStart(), textFrameUnion.GetKey(),
690 textFrameUnion.GetData(), presContext);
693 MOZ_ASSERT(GetTextFrameUnions().IsEmpty());
696 void PerformanceMainThread::StoreImageLCPEntry(
697 Element* aElement, imgRequestProxy* aImgRequestProxy,
698 LargestContentfulPaint* aEntry) {
699 mImageLCPEntryMap.InsertOrUpdate({aElement, aImgRequestProxy}, aEntry);
702 already_AddRefed<LargestContentfulPaint>
703 PerformanceMainThread::GetImageLCPEntry(Element* aElement,
704 imgRequestProxy* aImgRequestProxy) {
705 Maybe<RefPtr<LargestContentfulPaint>> entry =
706 mImageLCPEntryMap.Extract({aElement, aImgRequestProxy});
707 if (entry.isNothing()) {
708 return nullptr;
711 Document* doc = aElement->GetComposedDoc();
712 MOZ_ASSERT(doc, "Element should be connected when it's painted");
714 Maybe<LCPImageEntryKey>& contentIdentifier =
715 entry.value()->GetLCPImageEntryKey();
716 if (contentIdentifier.isSome()) {
717 doc->ContentIdentifiersForLCP().EnsureRemoved(contentIdentifier.value());
718 contentIdentifier.reset();
721 return entry.value().forget();
724 bool PerformanceMainThread::UpdateLargestContentfulPaintSize(double aSize) {
725 if (aSize > mLargestContentfulPaintSize) {
726 mLargestContentfulPaintSize = aSize;
727 return true;
729 return false;
732 void PerformanceMainThread::SetHasDispatchedScrollEvent() {
733 mHasDispatchedScrollEvent = true;
734 ClearGeneratedTempDataForLCP();
737 void PerformanceMainThread::SetHasDispatchedInputEvent() {
738 mHasDispatchedInputEvent = true;
739 ClearGeneratedTempDataForLCP();
742 void PerformanceMainThread::ClearGeneratedTempDataForLCP() {
743 mTextFrameUnions.Clear();
744 mImageLCPEntryMap.Clear();
745 mImagesPendingRendering.Clear();
747 nsIGlobalObject* ownerGlobal = GetOwnerGlobal();
748 if (!ownerGlobal) {
749 return;
752 if (Document* document = ownerGlobal->GetAsInnerWindow()->GetExtantDoc()) {
753 document->ContentIdentifiersForLCP().Clear();
756 } // namespace mozilla::dom