Backed out changeset 2fc34d798e24 (bug 1917771) for causing failures at baseline...
[gecko.git] / dom / performance / PerformanceMainThread.cpp
blob2ece26e4c488e70e6280c0bf61c66b6cedeee082
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/FragmentDirective.h"
20 #include "mozilla/dom/PerformanceEventTimingBinding.h"
21 #include "mozilla/dom/PerformanceNavigationTiming.h"
22 #include "mozilla/dom/PerformanceResourceTiming.h"
23 #include "mozilla/dom/PerformanceTiming.h"
24 #include "mozilla/StaticPrefs_dom.h"
25 #include "mozilla/PresShell.h"
26 #include "nsIChannel.h"
27 #include "nsIHttpChannel.h"
28 #include "nsIDocShell.h"
29 #include "nsGlobalWindowInner.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 = FragmentDirective::GetSpecIgnoringFragmentDirective(uri, 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->mTextFrameUnions.Clear();
72 mozilla::DropJSObjects(tmp);
73 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
75 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PerformanceMainThread,
76 Performance)
77 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
78 mTiming, mNavigation, mDocEntry, mFCPTiming, mEventTimingEntries,
79 mLargestContentfulPaintEntries, mFirstInputEvent, mPendingPointerDown,
80 mPendingEventTimingEntries, mEventCounts, mTextFrameUnions)
82 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
84 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceMainThread,
85 Performance)
86 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMozMemory)
87 NS_IMPL_CYCLE_COLLECTION_TRACE_END
89 NS_IMPL_ADDREF_INHERITED(PerformanceMainThread, Performance)
90 NS_IMPL_RELEASE_INHERITED(PerformanceMainThread, Performance)
92 // QueryInterface implementation for PerformanceMainThread
93 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceMainThread)
94 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
95 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, EventTarget)
96 NS_INTERFACE_MAP_END_INHERITING(Performance)
98 PerformanceMainThread::PerformanceMainThread(nsPIDOMWindowInner* aWindow,
99 nsDOMNavigationTiming* aDOMTiming,
100 nsITimedChannel* aChannel)
101 : Performance(aWindow->AsGlobal()),
102 mDOMTiming(aDOMTiming),
103 mChannel(aChannel) {
104 MOZ_ASSERT(aWindow, "Parent window object should be provided");
105 if (StaticPrefs::dom_enable_event_timing()) {
106 mEventCounts = new class EventCounts(GetParentObject());
108 CreateNavigationTimingEntry();
110 if (StaticPrefs::dom_enable_largest_contentful_paint()) {
111 nsGlobalWindowInner* owner = GetOwnerWindow();
112 MarkerInnerWindowId innerWindowID =
113 owner ? MarkerInnerWindowId(owner->WindowID())
114 : MarkerInnerWindowId::NoId();
115 // There might be multiple LCP entries and we only care about the latest one
116 // which is also the biggest value. That's why we need to record these
117 // markers in two different places:
118 // - During the Document unload, so we can record the closed pages.
119 // - During the profile capture, so we can record the open pages.
120 // We are capturing the second one here.
121 // Our static analysis doesn't allow capturing ref-counted pointers in
122 // lambdas, so we need to hide it in a uintptr_t. This is safe because this
123 // lambda will be destroyed in ~PerformanceMainThread().
124 uintptr_t self = reinterpret_cast<uintptr_t>(this);
125 profiler_add_state_change_callback(
126 // Using the "Pausing" state as "GeneratingProfile" profile happens too
127 // late; we can not record markers if the profiler is already paused.
128 ProfilingState::Pausing,
129 [self, innerWindowID](ProfilingState aProfilingState) {
130 const PerformanceMainThread* selfPtr =
131 reinterpret_cast<const PerformanceMainThread*>(self);
133 selfPtr->GetDOMTiming()->MaybeAddLCPProfilerMarker(innerWindowID);
135 self);
139 PerformanceMainThread::~PerformanceMainThread() {
140 profiler_remove_state_change_callback(reinterpret_cast<uintptr_t>(this));
141 mozilla::DropJSObjects(this);
144 void PerformanceMainThread::GetMozMemory(JSContext* aCx,
145 JS::MutableHandle<JSObject*> aObj) {
146 if (!mMozMemory) {
147 JS::Rooted<JSObject*> mozMemoryObj(aCx, JS_NewPlainObject(aCx));
148 JS::Rooted<JSObject*> gcMemoryObj(aCx, js::gc::NewMemoryInfoObject(aCx));
149 if (!mozMemoryObj || !gcMemoryObj) {
150 MOZ_CRASH("out of memory creating performance.mozMemory");
152 if (!JS_DefineProperty(aCx, mozMemoryObj, "gc", gcMemoryObj,
153 JSPROP_ENUMERATE)) {
154 MOZ_CRASH("out of memory creating performance.mozMemory");
156 mMozMemory = mozMemoryObj;
157 mozilla::HoldJSObjects(this);
160 aObj.set(mMozMemory);
163 PerformanceTiming* PerformanceMainThread::Timing() {
164 if (!mTiming) {
165 // For navigation timing, the third argument (an nsIHttpChannel) is null
166 // since the cross-domain redirect were already checked. The last
167 // argument (zero time) for performance.timing is the navigation start
168 // value.
169 mTiming = new PerformanceTiming(this, mChannel, nullptr,
170 mDOMTiming->GetNavigationStart());
173 return mTiming;
176 void PerformanceMainThread::DispatchBufferFullEvent() {
177 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
178 // it bubbles, and it isn't cancelable
179 event->InitEvent(u"resourcetimingbufferfull"_ns, true, false);
180 event->SetTrusted(true);
181 DispatchEvent(*event);
184 PerformanceNavigation* PerformanceMainThread::Navigation() {
185 if (!mNavigation) {
186 mNavigation = new PerformanceNavigation(this);
189 return mNavigation;
193 * An entry should be added only after the resource is loaded.
194 * This method is not thread safe and can only be called on the main thread.
196 void PerformanceMainThread::AddEntry(nsIHttpChannel* channel,
197 nsITimedChannel* timedChannel) {
198 MOZ_ASSERT(NS_IsMainThread());
200 nsAutoString initiatorType;
201 nsAutoString entryName;
203 UniquePtr<PerformanceTimingData> performanceTimingData(
204 PerformanceTimingData::Create(timedChannel, channel, 0, initiatorType,
205 entryName));
206 if (!performanceTimingData) {
207 return;
209 AddRawEntry(std::move(performanceTimingData), initiatorType, entryName);
212 void PerformanceMainThread::AddEntry(const nsString& entryName,
213 const nsString& initiatorType,
214 UniquePtr<PerformanceTimingData>&& aData) {
215 AddRawEntry(std::move(aData), initiatorType, entryName);
218 void PerformanceMainThread::AddRawEntry(UniquePtr<PerformanceTimingData> aData,
219 const nsAString& aInitiatorType,
220 const nsAString& aEntryName) {
221 // The PerformanceResourceTiming object will use the PerformanceTimingData
222 // object to get all the required timings.
223 auto entry =
224 MakeRefPtr<PerformanceResourceTiming>(std::move(aData), this, aEntryName);
225 entry->SetInitiatorType(aInitiatorType);
226 InsertResourceEntry(entry);
229 void PerformanceMainThread::SetFCPTimingEntry(PerformancePaintTiming* aEntry) {
230 MOZ_ASSERT(aEntry);
231 if (!mFCPTiming) {
232 mFCPTiming = aEntry;
233 QueueEntry(aEntry);
237 void PerformanceMainThread::InsertEventTimingEntry(
238 PerformanceEventTiming* aEventEntry) {
239 mPendingEventTimingEntries.insertBack(aEventEntry);
241 if (mHasQueuedRefreshdriverObserver) {
242 return;
245 PresShell* presShell = GetPresShell();
246 if (!presShell) {
247 return;
250 nsPresContext* presContext = presShell->GetPresContext();
251 if (!presContext) {
252 return;
255 // Using PostRefreshObserver is fine because we don't
256 // run any JS between the `mark paint timing` step and the
257 // `pending Event Timing entries` step. So mixing the order
258 // here is fine.
259 mHasQueuedRefreshdriverObserver = true;
260 presContext->RegisterManagedPostRefreshObserver(
261 new ManagedPostRefreshObserver(
262 presContext, [performance = RefPtr<PerformanceMainThread>(this)](
263 bool aWasCanceled) {
264 if (!aWasCanceled) {
265 // XXX Should we do this even if canceled?
266 performance->DispatchPendingEventTimingEntries();
268 performance->mHasQueuedRefreshdriverObserver = false;
269 return ManagedPostRefreshObserver::Unregister::Yes;
270 }));
273 void PerformanceMainThread::BufferEventTimingEntryIfNeeded(
274 PerformanceEventTiming* aEventEntry) {
275 if (mEventTimingEntries.Length() < kDefaultEventTimingBufferSize) {
276 mEventTimingEntries.AppendElement(aEventEntry);
280 void PerformanceMainThread::BufferLargestContentfulPaintEntryIfNeeded(
281 LargestContentfulPaint* aEntry) {
282 MOZ_ASSERT(StaticPrefs::dom_enable_largest_contentful_paint());
283 if (mLargestContentfulPaintEntries.Length() <
284 kMaxLargestContentfulPaintBufferSize) {
285 mLargestContentfulPaintEntries.AppendElement(aEntry);
289 void PerformanceMainThread::DispatchPendingEventTimingEntries() {
290 DOMHighResTimeStamp renderingTime = NowUnclamped();
292 while (!mPendingEventTimingEntries.isEmpty()) {
293 RefPtr<PerformanceEventTiming> entry =
294 mPendingEventTimingEntries.popFirst();
296 entry->SetDuration(renderingTime - entry->RawStartTime());
297 IncEventCount(entry->GetName());
299 if (entry->RawDuration() >= kDefaultEventTimingMinDuration) {
300 QueueEntry(entry);
303 if (!mHasDispatchedInputEvent) {
304 switch (entry->GetMessage()) {
305 case ePointerDown: {
306 mPendingPointerDown = entry->Clone();
307 mPendingPointerDown->SetEntryType(u"first-input"_ns);
308 break;
310 case ePointerUp: {
311 if (mPendingPointerDown) {
312 MOZ_ASSERT(!mFirstInputEvent);
313 mFirstInputEvent = mPendingPointerDown.forget();
314 QueueEntry(mFirstInputEvent);
315 SetHasDispatchedInputEvent();
317 break;
319 case ePointerClick:
320 case eKeyDown:
321 case eMouseDown: {
322 mFirstInputEvent = entry->Clone();
323 mFirstInputEvent->SetEntryType(u"first-input"_ns);
324 QueueEntry(mFirstInputEvent);
325 SetHasDispatchedInputEvent();
326 break;
328 default:
329 break;
335 DOMHighResTimeStamp PerformanceMainThread::GetPerformanceTimingFromString(
336 const nsAString& aProperty) {
337 // ::Measure expects the values returned from this function to be passed
338 // through ReduceTimePrecision already.
339 if (!IsPerformanceTimingAttribute(aProperty)) {
340 return 0;
342 // Values from Timing() are already reduced
343 if (aProperty.EqualsLiteral("redirectStart")) {
344 return Timing()->RedirectStart();
346 if (aProperty.EqualsLiteral("redirectEnd")) {
347 return Timing()->RedirectEnd();
349 if (aProperty.EqualsLiteral("fetchStart")) {
350 return Timing()->FetchStart();
352 if (aProperty.EqualsLiteral("domainLookupStart")) {
353 return Timing()->DomainLookupStart();
355 if (aProperty.EqualsLiteral("domainLookupEnd")) {
356 return Timing()->DomainLookupEnd();
358 if (aProperty.EqualsLiteral("connectStart")) {
359 return Timing()->ConnectStart();
361 if (aProperty.EqualsLiteral("secureConnectionStart")) {
362 return Timing()->SecureConnectionStart();
364 if (aProperty.EqualsLiteral("connectEnd")) {
365 return Timing()->ConnectEnd();
367 if (aProperty.EqualsLiteral("requestStart")) {
368 return Timing()->RequestStart();
370 if (aProperty.EqualsLiteral("responseStart")) {
371 return Timing()->ResponseStart();
373 if (aProperty.EqualsLiteral("responseEnd")) {
374 return Timing()->ResponseEnd();
376 // Values from GetDOMTiming() are not.
377 DOMHighResTimeStamp retValue;
378 if (aProperty.EqualsLiteral("navigationStart")) {
379 // DOMHighResTimeStamp is in relation to navigationStart, so this will be
380 // zero.
381 retValue = GetDOMTiming()->GetNavigationStart();
382 } else if (aProperty.EqualsLiteral("unloadEventStart")) {
383 retValue = GetDOMTiming()->GetUnloadEventStart();
384 } else if (aProperty.EqualsLiteral("unloadEventEnd")) {
385 retValue = GetDOMTiming()->GetUnloadEventEnd();
386 } else if (aProperty.EqualsLiteral("domLoading")) {
387 retValue = GetDOMTiming()->GetDomLoading();
388 } else if (aProperty.EqualsLiteral("domInteractive")) {
389 retValue = GetDOMTiming()->GetDomInteractive();
390 } else if (aProperty.EqualsLiteral("domContentLoadedEventStart")) {
391 retValue = GetDOMTiming()->GetDomContentLoadedEventStart();
392 } else if (aProperty.EqualsLiteral("domContentLoadedEventEnd")) {
393 retValue = GetDOMTiming()->GetDomContentLoadedEventEnd();
394 } else if (aProperty.EqualsLiteral("domComplete")) {
395 retValue = GetDOMTiming()->GetDomComplete();
396 } else if (aProperty.EqualsLiteral("loadEventStart")) {
397 retValue = GetDOMTiming()->GetLoadEventStart();
398 } else if (aProperty.EqualsLiteral("loadEventEnd")) {
399 retValue = GetDOMTiming()->GetLoadEventEnd();
400 } else {
401 MOZ_CRASH(
402 "IsPerformanceTimingAttribute and GetPerformanceTimingFromString are "
403 "out "
404 "of sync");
406 return nsRFPService::ReduceTimePrecisionAsMSecs(
407 retValue, GetRandomTimelineSeed(), mRTPCallerType);
410 void PerformanceMainThread::InsertUserEntry(PerformanceEntry* aEntry) {
411 MOZ_ASSERT(NS_IsMainThread());
413 nsAutoCString uri;
414 double markCreationEpoch = 0;
416 if (StaticPrefs::dom_performance_enable_user_timing_logging() ||
417 StaticPrefs::dom_performance_enable_notify_performance_timing()) {
418 nsresult rv = NS_ERROR_FAILURE;
419 nsGlobalWindowInner* owner = GetOwnerWindow();
420 if (owner && owner->GetDocumentURI()) {
421 rv = owner->GetDocumentURI()->GetHost(uri);
424 if (NS_FAILED(rv)) {
425 // If we have no URI, just put in "none".
426 uri.AssignLiteral("none");
429 // PR_Now() returns a signed 64-bit integer. Since it represents a
430 // timestamp, only ~32-bits will represent the value which should safely fit
431 // into a double.
432 markCreationEpoch = static_cast<double>(PR_Now() / PR_USEC_PER_MSEC);
434 if (StaticPrefs::dom_performance_enable_user_timing_logging()) {
435 Performance::LogEntry(aEntry, uri);
439 if (StaticPrefs::dom_performance_enable_notify_performance_timing()) {
440 TimingNotification(aEntry, uri, markCreationEpoch);
443 Performance::InsertUserEntry(aEntry);
446 TimeStamp PerformanceMainThread::CreationTimeStamp() const {
447 return GetDOMTiming()->GetNavigationStartTimeStamp();
450 DOMHighResTimeStamp PerformanceMainThread::CreationTime() const {
451 return GetDOMTiming()->GetNavigationStart();
454 void PerformanceMainThread::CreateNavigationTimingEntry() {
455 MOZ_ASSERT(!mDocEntry, "mDocEntry should be null.");
457 if (!StaticPrefs::dom_enable_performance_navigation_timing()) {
458 return;
461 nsAutoString name;
462 GetURLSpecFromChannel(mChannel, name);
464 UniquePtr<PerformanceTimingData> timing(
465 new PerformanceTimingData(mChannel, nullptr, 0));
467 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
468 if (httpChannel) {
469 timing->SetPropertiesFromHttpChannel(httpChannel, mChannel);
472 mDocEntry = new PerformanceNavigationTiming(std::move(timing), this, name);
475 void PerformanceMainThread::UpdateNavigationTimingEntry() {
476 if (!mDocEntry) {
477 return;
480 // Let's update some values.
481 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
482 if (httpChannel) {
483 mDocEntry->UpdatePropertiesFromHttpChannel(httpChannel, mChannel);
487 void PerformanceMainThread::QueueNavigationTimingEntry() {
488 if (!mDocEntry) {
489 return;
492 UpdateNavigationTimingEntry();
494 QueueEntry(mDocEntry);
497 void PerformanceMainThread::QueueLargestContentfulPaintEntry(
498 LargestContentfulPaint* aEntry) {
499 MOZ_ASSERT(StaticPrefs::dom_enable_largest_contentful_paint());
500 QueueEntry(aEntry);
503 EventCounts* PerformanceMainThread::EventCounts() {
504 MOZ_ASSERT(StaticPrefs::dom_enable_event_timing());
505 return mEventCounts;
508 void PerformanceMainThread::GetEntries(
509 nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
510 aRetval = mResourceEntries.Clone();
511 aRetval.AppendElements(mUserEntries);
513 if (mDocEntry) {
514 aRetval.AppendElement(mDocEntry);
517 if (mFCPTiming) {
518 aRetval.AppendElement(mFCPTiming);
520 aRetval.Sort(PerformanceEntryComparator());
523 void PerformanceMainThread::GetEntriesByType(
524 const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
525 RefPtr<nsAtom> type = NS_Atomize(aEntryType);
526 if (type == nsGkAtoms::navigation) {
527 aRetval.Clear();
529 if (mDocEntry) {
530 aRetval.AppendElement(mDocEntry);
532 return;
535 if (type == nsGkAtoms::paint) {
536 if (mFCPTiming) {
537 aRetval.AppendElement(mFCPTiming);
538 return;
542 if (type == nsGkAtoms::firstInput && mFirstInputEvent) {
543 aRetval.AppendElement(mFirstInputEvent);
544 return;
547 Performance::GetEntriesByType(aEntryType, aRetval);
549 void PerformanceMainThread::GetEntriesByTypeForObserver(
550 const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
551 if (aEntryType.EqualsLiteral("event")) {
552 aRetval.AppendElements(mEventTimingEntries);
553 return;
556 if (StaticPrefs::dom_enable_largest_contentful_paint()) {
557 if (aEntryType.EqualsLiteral("largest-contentful-paint")) {
558 aRetval.AppendElements(mLargestContentfulPaintEntries);
559 return;
563 return GetEntriesByType(aEntryType, aRetval);
566 void PerformanceMainThread::GetEntriesByName(
567 const nsAString& aName, const Optional<nsAString>& aEntryType,
568 nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
569 Performance::GetEntriesByName(aName, aEntryType, aRetval);
571 if (mFCPTiming && mFCPTiming->GetName()->Equals(aName) &&
572 (!aEntryType.WasPassed() ||
573 mFCPTiming->GetEntryType()->Equals(aEntryType.Value()))) {
574 aRetval.AppendElement(mFCPTiming);
575 return;
578 // The navigation entry is the first one. If it exists and the name matches,
579 // let put it in front.
580 if (mDocEntry && mDocEntry->GetName()->Equals(aName)) {
581 aRetval.InsertElementAt(0, mDocEntry);
582 return;
586 mozilla::PresShell* PerformanceMainThread::GetPresShell() {
587 nsIGlobalObject* ownerGlobal = GetOwnerGlobal();
588 if (!ownerGlobal) {
589 return nullptr;
591 if (Document* doc = ownerGlobal->GetAsInnerWindow()->GetExtantDoc()) {
592 return doc->GetPresShell();
594 return nullptr;
597 void PerformanceMainThread::IncEventCount(const nsAtom* aType) {
598 MOZ_ASSERT(StaticPrefs::dom_enable_event_timing());
600 // This occurs when the pref was false when the performance
601 // object was first created, and became true later. It's
602 // okay to return early because eventCounts is not exposed.
603 if (!mEventCounts) {
604 return;
607 ErrorResult rv;
608 uint64_t count = EventCounts_Binding::MaplikeHelpers::Get(
609 mEventCounts, nsDependentAtomString(aType), rv);
610 MOZ_ASSERT(!rv.Failed());
611 EventCounts_Binding::MaplikeHelpers::Set(
612 mEventCounts, nsDependentAtomString(aType), ++count, rv);
613 MOZ_ASSERT(!rv.Failed());
616 size_t PerformanceMainThread::SizeOfEventEntries(
617 mozilla::MallocSizeOf aMallocSizeOf) const {
618 size_t eventEntries = 0;
619 for (const PerformanceEventTiming* entry : mEventTimingEntries) {
620 eventEntries += entry->SizeOfIncludingThis(aMallocSizeOf);
622 return eventEntries;
625 void PerformanceMainThread::ProcessElementTiming() {
626 if (!StaticPrefs::dom_enable_largest_contentful_paint()) {
627 return;
629 const bool shouldLCPDataEmpty =
630 HasDispatchedInputEvent() || HasDispatchedScrollEvent();
631 MOZ_ASSERT_IF(shouldLCPDataEmpty, mTextFrameUnions.IsEmpty());
633 if (shouldLCPDataEmpty) {
634 return;
637 nsPresContext* presContext = GetPresShell()->GetPresContext();
638 MOZ_ASSERT(presContext);
640 // After https://github.com/w3c/largest-contentful-paint/issues/104 is
641 // resolved, LargestContentfulPaint and FirstContentfulPaint should
642 // be using the same timestamp, which should be the same timestamp
643 // as to what https://w3c.github.io/paint-timing/#mark-paint-timing step 2
644 // defines.
645 // TODO(sefeng): Check the timestamp after this issue is resolved.
646 TimeStamp rawNowTime = presContext->GetMarkPaintTimingStart();
648 MOZ_ASSERT(GetOwnerGlobal());
649 Document* document = GetOwnerGlobal()->GetAsInnerWindow()->GetExtantDoc();
650 if (!document ||
651 !nsContentUtils::GetInProcessSubtreeRootDocument(document)->IsActive()) {
652 return;
655 nsTArray<ImagePendingRendering> imagesPendingRendering =
656 std::move(mImagesPendingRendering);
657 for (const auto& imagePendingRendering : imagesPendingRendering) {
658 RefPtr<Element> element = imagePendingRendering.GetElement();
659 if (!element) {
660 continue;
663 MOZ_ASSERT(imagePendingRendering.mLoadTime <= rawNowTime);
664 if (imgRequestProxy* requestProxy =
665 imagePendingRendering.GetImgRequestProxy()) {
666 requestProxy->GetLCPTimings().Set(imagePendingRendering.mLoadTime,
667 rawNowTime);
671 MOZ_ASSERT(mImagesPendingRendering.IsEmpty());
674 void PerformanceMainThread::FinalizeLCPEntriesForText() {
675 nsPresContext* presContext = GetPresShell()->GetPresContext();
676 MOZ_ASSERT(presContext);
678 bool canFinalize = StaticPrefs::dom_enable_largest_contentful_paint() &&
679 !presContext->HasStoppedGeneratingLCP();
680 nsTHashMap<nsRefPtrHashKey<Element>, nsRect> textFrameUnion =
681 std::move(GetTextFrameUnions());
682 if (canFinalize) {
683 for (const auto& textFrameUnion : textFrameUnion) {
684 LCPHelpers::FinalizeLCPEntryForText(
685 this, presContext->GetMarkPaintTimingStart(), textFrameUnion.GetKey(),
686 textFrameUnion.GetData(), presContext);
689 MOZ_ASSERT(GetTextFrameUnions().IsEmpty());
692 bool PerformanceMainThread::IsPendingLCPCandidate(
693 Element* aElement, imgRequestProxy* aImgRequestProxy) {
694 Document* doc = aElement->GetComposedDoc();
695 MOZ_ASSERT(doc, "Element should be connected when it's painted");
696 if (!aElement->HasFlag(ELEMENT_IN_CONTENT_IDENTIFIER_FOR_LCP)) {
697 MOZ_ASSERT(!doc->ContentIdentifiersForLCP().Contains(aElement));
698 return false;
701 if (auto entry = doc->ContentIdentifiersForLCP().Lookup(aElement)) {
702 return entry.Data().Contains(aImgRequestProxy);
705 MOZ_ASSERT_UNREACHABLE("we should always have an entry when the flag exists");
706 return false;
709 bool PerformanceMainThread::UpdateLargestContentfulPaintSize(double aSize) {
710 if (aSize > mLargestContentfulPaintSize) {
711 mLargestContentfulPaintSize = aSize;
712 return true;
714 return false;
717 void PerformanceMainThread::SetHasDispatchedScrollEvent() {
718 mHasDispatchedScrollEvent = true;
719 ClearGeneratedTempDataForLCP();
722 void PerformanceMainThread::SetHasDispatchedInputEvent() {
723 mHasDispatchedInputEvent = true;
724 ClearGeneratedTempDataForLCP();
727 void PerformanceMainThread::ClearGeneratedTempDataForLCP() {
728 mTextFrameUnions.Clear();
729 mImagesPendingRendering.Clear();
731 nsIGlobalObject* ownerGlobal = GetOwnerGlobal();
732 if (!ownerGlobal) {
733 return;
736 if (Document* document = ownerGlobal->GetAsInnerWindow()->GetExtantDoc()) {
737 document->ContentIdentifiersForLCP().Clear();
740 } // namespace mozilla::dom