1
0
Fork 0
firefox/dom/performance/PerformanceMainThread.cpp
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

796 lines
26 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "PerformanceMainThread.h"
#include "PerformanceInteractionMetrics.h"
#include "PerformanceNavigation.h"
#include "PerformancePaintTiming.h"
#include "jsapi.h"
#include "js/GCAPI.h"
#include "js/PropertyAndElement.h" // JS_DefineProperty
#include "mozilla/HoldDropJSObjects.h"
#include "PerformanceEventTiming.h"
#include "LargestContentfulPaint.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/EventCounts.h"
#include "mozilla/dom/FragmentDirective.h"
#include "mozilla/dom/PerformanceEventTimingBinding.h"
#include "mozilla/dom/PerformanceNavigationTiming.h"
#include "mozilla/dom/PerformanceResourceTiming.h"
#include "mozilla/dom/PerformanceTiming.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/PresShell.h"
#include "nsGkAtoms.h"
#include "nsIChannel.h"
#include "nsIHttpChannel.h"
#include "nsIDocShell.h"
#include "nsGlobalWindowInner.h"
#include "nsContainerFrame.h"
#include "mozilla/TextEvents.h"
namespace mozilla::dom {
extern mozilla::LazyLogModule gLCPLogging;
namespace {
void GetURLSpecFromChannel(nsITimedChannel* aChannel, nsAString& aSpec) {
aSpec.AssignLiteral("document");
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aChannel);
if (!channel) {
return;
}
nsCOMPtr<nsIURI> uri;
nsresult rv = channel->GetURI(getter_AddRefs(uri));
if (NS_WARN_IF(NS_FAILED(rv)) || !uri) {
return;
}
nsAutoCString spec;
rv = FragmentDirective::GetSpecIgnoringFragmentDirective(uri, spec);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
CopyUTF8toUTF16(spec, aSpec);
}
} // namespace
NS_IMPL_CYCLE_COLLECTION_CLASS(PerformanceMainThread)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PerformanceMainThread,
Performance)
NS_IMPL_CYCLE_COLLECTION_UNLINK(
mTiming, mNavigation, mDocEntry, mFCPTiming, mEventTimingEntries,
mLargestContentfulPaintEntries, mFirstInputEvent, mPendingPointerDown,
mPendingEventTimingEntries, mEventCounts, mInteractionMetrics)
tmp->mTextFrameUnions.Clear();
mozilla::DropJSObjects(tmp);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PerformanceMainThread,
Performance)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
mTiming, mNavigation, mDocEntry, mFCPTiming, mEventTimingEntries,
mLargestContentfulPaintEntries, mFirstInputEvent, mPendingPointerDown,
mPendingEventTimingEntries, mEventCounts, mTextFrameUnions,
mInteractionMetrics)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceMainThread,
Performance)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMozMemory)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_ADDREF_INHERITED(PerformanceMainThread, Performance)
NS_IMPL_RELEASE_INHERITED(PerformanceMainThread, Performance)
// QueryInterface implementation for PerformanceMainThread
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceMainThread)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, EventTarget)
NS_INTERFACE_MAP_END_INHERITING(Performance)
PerformanceMainThread::PerformanceMainThread(nsPIDOMWindowInner* aWindow,
nsDOMNavigationTiming* aDOMTiming,
nsITimedChannel* aChannel)
: Performance(aWindow->AsGlobal()),
mDOMTiming(aDOMTiming),
mChannel(aChannel) {
MOZ_ASSERT(aWindow, "Parent window object should be provided");
if (StaticPrefs::dom_enable_event_timing()) {
mEventCounts = new class EventCounts(GetParentObject());
}
CreateNavigationTimingEntry();
if (StaticPrefs::dom_enable_largest_contentful_paint()) {
nsGlobalWindowInner* owner = GetOwnerWindow();
MarkerInnerWindowId innerWindowID =
owner ? MarkerInnerWindowId(owner->WindowID())
: MarkerInnerWindowId::NoId();
// There might be multiple LCP entries and we only care about the latest one
// which is also the biggest value. That's why we need to record these
// markers in two different places:
// - During the Document unload, so we can record the closed pages.
// - During the profile capture, so we can record the open pages.
// We are capturing the second one here.
// Our static analysis doesn't allow capturing ref-counted pointers in
// lambdas, so we need to hide it in a uintptr_t. This is safe because this
// lambda will be destroyed in ~PerformanceMainThread().
uintptr_t self = reinterpret_cast<uintptr_t>(this);
profiler_add_state_change_callback(
// Using the "Pausing" state as "GeneratingProfile" profile happens too
// late; we can not record markers if the profiler is already paused.
ProfilingState::Pausing,
[self, innerWindowID](ProfilingState aProfilingState) {
const PerformanceMainThread* selfPtr =
reinterpret_cast<const PerformanceMainThread*>(self);
selfPtr->GetDOMTiming()->MaybeAddLCPProfilerMarker(innerWindowID);
},
self);
}
}
PerformanceMainThread::~PerformanceMainThread() {
profiler_remove_state_change_callback(reinterpret_cast<uintptr_t>(this));
mozilla::DropJSObjects(this);
}
void PerformanceMainThread::GetMozMemory(JSContext* aCx,
JS::MutableHandle<JSObject*> aObj) {
if (!mMozMemory) {
JS::Rooted<JSObject*> mozMemoryObj(aCx, JS_NewPlainObject(aCx));
JS::Rooted<JSObject*> gcMemoryObj(aCx, js::gc::NewMemoryInfoObject(aCx));
if (!mozMemoryObj || !gcMemoryObj) {
MOZ_CRASH("out of memory creating performance.mozMemory");
}
if (!JS_DefineProperty(aCx, mozMemoryObj, "gc", gcMemoryObj,
JSPROP_ENUMERATE)) {
MOZ_CRASH("out of memory creating performance.mozMemory");
}
mMozMemory = mozMemoryObj;
mozilla::HoldJSObjects(this);
}
aObj.set(mMozMemory);
}
PerformanceTiming* PerformanceMainThread::Timing() {
if (!mTiming) {
// For navigation timing, the third argument (an nsIHttpChannel) is null
// since the cross-domain redirect were already checked. The last
// argument (zero time) for performance.timing is the navigation start
// value.
mTiming = new PerformanceTiming(this, mChannel, nullptr,
mDOMTiming->GetNavigationStart());
}
return mTiming;
}
void PerformanceMainThread::DispatchBufferFullEvent() {
RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
// it bubbles, and it isn't cancelable
event->InitEvent(u"resourcetimingbufferfull"_ns, true, false);
event->SetTrusted(true);
DispatchEvent(*event);
}
PerformanceNavigation* PerformanceMainThread::Navigation() {
if (!mNavigation) {
mNavigation = new PerformanceNavigation(this);
}
return mNavigation;
}
/**
* An entry should be added only after the resource is loaded.
* This method is not thread safe and can only be called on the main thread.
*/
void PerformanceMainThread::AddEntry(nsIHttpChannel* channel,
nsITimedChannel* timedChannel) {
MOZ_ASSERT(NS_IsMainThread());
nsAutoString initiatorType;
nsAutoString entryName;
UniquePtr<PerformanceTimingData> performanceTimingData(
PerformanceTimingData::Create(timedChannel, channel, 0, initiatorType,
entryName));
if (!performanceTimingData) {
return;
}
AddRawEntry(std::move(performanceTimingData), initiatorType, entryName);
}
void PerformanceMainThread::AddEntry(const nsString& entryName,
const nsString& initiatorType,
UniquePtr<PerformanceTimingData>&& aData) {
AddRawEntry(std::move(aData), initiatorType, entryName);
}
void PerformanceMainThread::AddRawEntry(UniquePtr<PerformanceTimingData> aData,
const nsAString& aInitiatorType,
const nsAString& aEntryName) {
// The PerformanceResourceTiming object will use the PerformanceTimingData
// object to get all the required timings.
auto entry =
MakeRefPtr<PerformanceResourceTiming>(std::move(aData), this, aEntryName);
entry->SetInitiatorType(aInitiatorType);
InsertResourceEntry(entry);
}
void PerformanceMainThread::SetFCPTimingEntry(PerformancePaintTiming* aEntry) {
MOZ_ASSERT(aEntry);
if (!mFCPTiming) {
mFCPTiming = aEntry;
QueueEntry(aEntry);
}
}
void PerformanceMainThread::InsertEventTimingEntry(
PerformanceEventTiming* aEventEntry) {
mPendingEventTimingEntries.insertBack(aEventEntry);
if (mHasQueuedRefreshdriverObserver) {
return;
}
PresShell* presShell = GetPresShell();
if (!presShell) {
return;
}
nsPresContext* presContext = presShell->GetPresContext();
if (!presContext) {
return;
}
// Using PostRefreshObserver is fine because we don't
// run any JS between the `mark paint timing` step and the
// `pending Event Timing entries` step. So mixing the order
// here is fine.
mHasQueuedRefreshdriverObserver = true;
presContext->RegisterManagedPostRefreshObserver(
new ManagedPostRefreshObserver(
presContext, [performance = RefPtr<PerformanceMainThread>(this)](
bool aWasCanceled) {
if (!aWasCanceled) {
// XXX Should we do this even if canceled?
performance->DispatchPendingEventTimingEntries();
}
performance->mHasQueuedRefreshdriverObserver = false;
return ManagedPostRefreshObserver::Unregister::Yes;
}));
}
void PerformanceMainThread::BufferEventTimingEntryIfNeeded(
PerformanceEventTiming* aEventEntry) {
if (mEventTimingEntries.Length() < kDefaultEventTimingBufferSize) {
mEventTimingEntries.AppendElement(aEventEntry);
}
}
void PerformanceMainThread::BufferLargestContentfulPaintEntryIfNeeded(
LargestContentfulPaint* aEntry) {
MOZ_ASSERT(StaticPrefs::dom_enable_largest_contentful_paint());
if (mLargestContentfulPaintEntries.Length() <
kMaxLargestContentfulPaintBufferSize) {
mLargestContentfulPaintEntries.AppendElement(aEntry);
}
}
void PerformanceMainThread::DispatchPendingEventTimingEntries() {
DOMHighResTimeStamp renderingTime = NowUnclamped();
auto entriesToBeQueuedEnd = mPendingEventTimingEntries.end();
for (auto it = mPendingEventTimingEntries.begin();
it != mPendingEventTimingEntries.end(); ++it) {
// Set its duration if it's not set already.
PerformanceEventTiming* entry = *it;
if (entry->RawDuration() == 0) {
entry->SetDuration(renderingTime - entry->RawStartTime());
}
if (!(mPendingEventTimingEntries.end() != entriesToBeQueuedEnd) &&
!entry->HasKnownInteractionId()) {
entriesToBeQueuedEnd = it;
}
}
if (!StaticPrefs::dom_performance_event_timing_enable_interactionid() ||
mPendingEventTimingEntries.begin() != entriesToBeQueuedEnd) {
while (mPendingEventTimingEntries.begin() != entriesToBeQueuedEnd) {
RefPtr<PerformanceEventTiming> entry =
mPendingEventTimingEntries.popFirst();
if (entry->RawDuration() >= kDefaultEventTimingMinDuration) {
QueueEntry(entry);
}
// Perform the following steps to update the event counts:
IncEventCount(entry->GetName());
// If windows has dispatched input event is false, run the following
// steps:
if (StaticPrefs::dom_performance_event_timing_enable_interactionid()) {
if (!mHasDispatchedInputEvent && entry->InteractionId() != 0) {
mFirstInputEvent = entry->Clone();
mFirstInputEvent->SetEntryType(u"first-input"_ns);
QueueEntry(mFirstInputEvent);
SetHasDispatchedInputEvent();
}
} else {
if (!mHasDispatchedInputEvent) {
switch (entry->GetMessage()) {
case ePointerDown: {
mPendingPointerDown = entry->Clone();
mPendingPointerDown->SetEntryType(u"first-input"_ns);
break;
}
case ePointerUp: {
if (mPendingPointerDown) {
MOZ_ASSERT(!mFirstInputEvent);
mFirstInputEvent = mPendingPointerDown.forget();
QueueEntry(mFirstInputEvent);
SetHasDispatchedInputEvent();
}
break;
}
case ePointerClick:
case eKeyDown:
case eMouseDown: {
mFirstInputEvent = entry->Clone();
mFirstInputEvent->SetEntryType(u"first-input"_ns);
QueueEntry(mFirstInputEvent);
SetHasDispatchedInputEvent();
break;
}
default:
break;
}
}
}
}
}
}
PerformanceInteractionMetrics&
PerformanceMainThread::GetPerformanceInteractionMetrics() {
return mInteractionMetrics;
}
void PerformanceMainThread::SetInteractionId(
PerformanceEventTiming* aEventTiming, const WidgetEvent* aEvent) {
MOZ_ASSERT(NS_IsMainThread());
if (!StaticPrefs::dom_performance_event_timing_enable_interactionid() ||
aEvent->mFlags.mOnlyChromeDispatch || !aEvent->IsTrusted()) {
aEventTiming->SetInteractionId(0);
return;
}
aEventTiming->SetInteractionId(
mInteractionMetrics.ComputeInteractionId(aEventTiming, aEvent));
}
DOMHighResTimeStamp PerformanceMainThread::GetPerformanceTimingFromString(
const nsAString& aProperty) {
// ::Measure expects the values returned from this function to be passed
// through ReduceTimePrecision already.
if (!IsPerformanceTimingAttribute(aProperty)) {
return 0;
}
// Values from Timing() are already reduced
if (aProperty.EqualsLiteral("redirectStart")) {
return Timing()->RedirectStart();
}
if (aProperty.EqualsLiteral("redirectEnd")) {
return Timing()->RedirectEnd();
}
if (aProperty.EqualsLiteral("fetchStart")) {
return Timing()->FetchStart();
}
if (aProperty.EqualsLiteral("domainLookupStart")) {
return Timing()->DomainLookupStart();
}
if (aProperty.EqualsLiteral("domainLookupEnd")) {
return Timing()->DomainLookupEnd();
}
if (aProperty.EqualsLiteral("connectStart")) {
return Timing()->ConnectStart();
}
if (aProperty.EqualsLiteral("secureConnectionStart")) {
return Timing()->SecureConnectionStart();
}
if (aProperty.EqualsLiteral("connectEnd")) {
return Timing()->ConnectEnd();
}
if (aProperty.EqualsLiteral("requestStart")) {
return Timing()->RequestStart();
}
if (aProperty.EqualsLiteral("responseStart")) {
return Timing()->ResponseStart();
}
if (aProperty.EqualsLiteral("responseEnd")) {
return Timing()->ResponseEnd();
}
// Values from GetDOMTiming() are not.
DOMHighResTimeStamp retValue;
if (aProperty.EqualsLiteral("navigationStart")) {
// DOMHighResTimeStamp is in relation to navigationStart, so this will be
// zero.
retValue = GetDOMTiming()->GetNavigationStart();
} else if (aProperty.EqualsLiteral("unloadEventStart")) {
retValue = GetDOMTiming()->GetUnloadEventStart();
} else if (aProperty.EqualsLiteral("unloadEventEnd")) {
retValue = GetDOMTiming()->GetUnloadEventEnd();
} else if (aProperty.EqualsLiteral("domLoading")) {
retValue = GetDOMTiming()->GetDomLoading();
} else if (aProperty.EqualsLiteral("domInteractive")) {
retValue = GetDOMTiming()->GetDomInteractive();
} else if (aProperty.EqualsLiteral("domContentLoadedEventStart")) {
retValue = GetDOMTiming()->GetDomContentLoadedEventStart();
} else if (aProperty.EqualsLiteral("domContentLoadedEventEnd")) {
retValue = GetDOMTiming()->GetDomContentLoadedEventEnd();
} else if (aProperty.EqualsLiteral("domComplete")) {
retValue = GetDOMTiming()->GetDomComplete();
} else if (aProperty.EqualsLiteral("loadEventStart")) {
retValue = GetDOMTiming()->GetLoadEventStart();
} else if (aProperty.EqualsLiteral("loadEventEnd")) {
retValue = GetDOMTiming()->GetLoadEventEnd();
} else {
MOZ_CRASH(
"IsPerformanceTimingAttribute and GetPerformanceTimingFromString are "
"out "
"of sync");
}
return nsRFPService::ReduceTimePrecisionAsMSecs(
retValue, GetRandomTimelineSeed(), mRTPCallerType);
}
void PerformanceMainThread::InsertUserEntry(PerformanceEntry* aEntry) {
MOZ_ASSERT(NS_IsMainThread());
nsAutoCString uri;
double markCreationEpoch = 0;
if (StaticPrefs::dom_performance_enable_user_timing_logging() ||
StaticPrefs::dom_performance_enable_notify_performance_timing()) {
nsresult rv = NS_ERROR_FAILURE;
nsGlobalWindowInner* owner = GetOwnerWindow();
if (owner && owner->GetDocumentURI()) {
rv = owner->GetDocumentURI()->GetHost(uri);
}
if (NS_FAILED(rv)) {
// If we have no URI, just put in "none".
uri.AssignLiteral("none");
}
// PR_Now() returns a signed 64-bit integer. Since it represents a
// timestamp, only ~32-bits will represent the value which should safely fit
// into a double.
markCreationEpoch = static_cast<double>(PR_Now() / PR_USEC_PER_MSEC);
if (StaticPrefs::dom_performance_enable_user_timing_logging()) {
Performance::LogEntry(aEntry, uri);
}
}
if (StaticPrefs::dom_performance_enable_notify_performance_timing()) {
TimingNotification(aEntry, uri, markCreationEpoch);
}
Performance::InsertUserEntry(aEntry);
}
TimeStamp PerformanceMainThread::CreationTimeStamp() const {
return GetDOMTiming()->GetNavigationStartTimeStamp();
}
DOMHighResTimeStamp PerformanceMainThread::CreationTime() const {
return GetDOMTiming()->GetNavigationStart();
}
void PerformanceMainThread::CreateNavigationTimingEntry() {
MOZ_ASSERT(!mDocEntry, "mDocEntry should be null.");
if (!StaticPrefs::dom_enable_performance_navigation_timing()) {
return;
}
nsAutoString name;
GetURLSpecFromChannel(mChannel, name);
UniquePtr<PerformanceTimingData> timing(
new PerformanceTimingData(mChannel, nullptr, 0));
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
if (httpChannel) {
timing->SetPropertiesFromHttpChannel(httpChannel, mChannel);
}
mDocEntry = new PerformanceNavigationTiming(std::move(timing), this, name);
}
void PerformanceMainThread::UpdateNavigationTimingEntry() {
if (!mDocEntry) {
return;
}
// Let's update some values.
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
if (httpChannel) {
mDocEntry->UpdatePropertiesFromHttpChannel(httpChannel, mChannel);
}
}
void PerformanceMainThread::QueueNavigationTimingEntry() {
if (!mDocEntry) {
return;
}
UpdateNavigationTimingEntry();
QueueEntry(mDocEntry);
}
void PerformanceMainThread::QueueLargestContentfulPaintEntry(
LargestContentfulPaint* aEntry) {
MOZ_ASSERT(StaticPrefs::dom_enable_largest_contentful_paint());
QueueEntry(aEntry);
}
EventCounts* PerformanceMainThread::EventCounts() {
MOZ_ASSERT(StaticPrefs::dom_enable_event_timing());
return mEventCounts;
}
uint64_t PerformanceMainThread::InteractionCount() {
MOZ_ASSERT(StaticPrefs::dom_performance_event_timing_enable_interactionid());
return mInteractionMetrics.InteractionCount();
}
void PerformanceMainThread::GetEntries(
nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
aRetval = mResourceEntries.Clone();
aRetval.AppendElements(mUserEntries);
if (mDocEntry) {
aRetval.AppendElement(mDocEntry);
}
if (mFCPTiming) {
aRetval.AppendElement(mFCPTiming);
}
aRetval.Sort(PerformanceEntryComparator());
}
void PerformanceMainThread::GetEntriesByType(
const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
RefPtr<nsAtom> type = NS_Atomize(aEntryType);
if (type == nsGkAtoms::navigation) {
aRetval.Clear();
if (mDocEntry) {
aRetval.AppendElement(mDocEntry);
}
return;
}
if (type == nsGkAtoms::paint) {
if (mFCPTiming) {
aRetval.AppendElement(mFCPTiming);
return;
}
}
if (type == nsGkAtoms::firstInput && mFirstInputEvent) {
aRetval.AppendElement(mFirstInputEvent);
return;
}
Performance::GetEntriesByType(aEntryType, aRetval);
}
void PerformanceMainThread::GetEntriesByTypeForObserver(
const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
if (aEntryType.EqualsLiteral("event")) {
aRetval.AppendElements(mEventTimingEntries);
return;
}
if (StaticPrefs::dom_enable_largest_contentful_paint()) {
if (aEntryType.EqualsLiteral("largest-contentful-paint")) {
aRetval.AppendElements(mLargestContentfulPaintEntries);
return;
}
}
return GetEntriesByType(aEntryType, aRetval);
}
void PerformanceMainThread::GetEntriesByName(
const nsAString& aName, const Optional<nsAString>& aEntryType,
nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
Performance::GetEntriesByName(aName, aEntryType, aRetval);
if (mFCPTiming && mFCPTiming->GetName()->Equals(aName) &&
(!aEntryType.WasPassed() ||
mFCPTiming->GetEntryType()->Equals(aEntryType.Value()))) {
aRetval.AppendElement(mFCPTiming);
return;
}
// The navigation entry is the first one. If it exists and the name matches,
// let put it in front.
if (mDocEntry && mDocEntry->GetName()->Equals(aName)) {
aRetval.InsertElementAt(0, mDocEntry);
return;
}
}
mozilla::PresShell* PerformanceMainThread::GetPresShell() {
nsIGlobalObject* ownerGlobal = GetOwnerGlobal();
if (!ownerGlobal) {
return nullptr;
}
if (Document* doc = ownerGlobal->GetAsInnerWindow()->GetExtantDoc()) {
return doc->GetPresShell();
}
return nullptr;
}
void PerformanceMainThread::IncEventCount(const nsAtom* aType) {
MOZ_ASSERT(StaticPrefs::dom_enable_event_timing());
// This occurs when the pref was false when the performance
// object was first created, and became true later. It's
// okay to return early because eventCounts is not exposed.
if (!mEventCounts) {
return;
}
IgnoredErrorResult rv;
uint64_t count = EventCounts_Binding::MaplikeHelpers::Get(
mEventCounts, nsDependentAtomString(aType), rv);
if (rv.Failed()) {
return;
}
EventCounts_Binding::MaplikeHelpers::Set(
mEventCounts, nsDependentAtomString(aType), ++count, rv);
}
size_t PerformanceMainThread::SizeOfEventEntries(
mozilla::MallocSizeOf aMallocSizeOf) const {
size_t eventEntries = 0;
for (const PerformanceEventTiming* entry : mEventTimingEntries) {
eventEntries += entry->SizeOfIncludingThis(aMallocSizeOf);
}
return eventEntries;
}
void PerformanceMainThread::ProcessElementTiming() {
if (!StaticPrefs::dom_enable_largest_contentful_paint()) {
return;
}
const bool shouldLCPDataEmpty =
HasDispatchedInputEvent() || HasDispatchedScrollEvent();
MOZ_ASSERT_IF(shouldLCPDataEmpty, mTextFrameUnions.IsEmpty());
if (shouldLCPDataEmpty) {
return;
}
nsPresContext* presContext = GetPresShell()->GetPresContext();
MOZ_ASSERT(presContext);
// After https://github.com/w3c/largest-contentful-paint/issues/104 is
// resolved, LargestContentfulPaint and FirstContentfulPaint should
// be using the same timestamp, which should be the same timestamp
// as to what https://w3c.github.io/paint-timing/#mark-paint-timing step 2
// defines.
// TODO(sefeng): Check the timestamp after this issue is resolved.
TimeStamp rawNowTime = presContext->GetMarkPaintTimingStart();
MOZ_ASSERT(GetOwnerGlobal());
Document* document = GetOwnerGlobal()->GetAsInnerWindow()->GetExtantDoc();
if (!document ||
!nsContentUtils::GetInProcessSubtreeRootDocument(document)->IsActive()) {
return;
}
nsTArray<ImagePendingRendering> imagesPendingRendering =
std::move(mImagesPendingRendering);
for (const auto& imagePendingRendering : imagesPendingRendering) {
RefPtr<Element> element = imagePendingRendering.GetElement();
if (!element) {
continue;
}
MOZ_ASSERT(imagePendingRendering.mLoadTime <= rawNowTime);
if (imgRequestProxy* requestProxy =
imagePendingRendering.GetImgRequestProxy()) {
requestProxy->GetLCPTimings().Set(imagePendingRendering.mLoadTime,
rawNowTime);
}
}
MOZ_ASSERT(mImagesPendingRendering.IsEmpty());
}
void PerformanceMainThread::FinalizeLCPEntriesForText() {
nsPresContext* presContext = GetPresShell()->GetPresContext();
MOZ_ASSERT(presContext);
bool canFinalize = StaticPrefs::dom_enable_largest_contentful_paint() &&
!presContext->HasStoppedGeneratingLCP();
nsTHashMap<nsRefPtrHashKey<Element>, nsRect> textFrameUnion =
std::move(GetTextFrameUnions());
if (canFinalize) {
for (const auto& textFrameUnion : textFrameUnion) {
LCPHelpers::FinalizeLCPEntryForText(
this, presContext->GetMarkPaintTimingStart(), textFrameUnion.GetKey(),
textFrameUnion.GetData(), presContext);
}
}
MOZ_ASSERT(GetTextFrameUnions().IsEmpty());
}
bool PerformanceMainThread::IsPendingLCPCandidate(
Element* aElement, imgRequestProxy* aImgRequestProxy) {
Document* doc = aElement->GetComposedDoc();
MOZ_ASSERT(doc, "Element should be connected when it's painted");
if (!aElement->HasFlag(ELEMENT_IN_CONTENT_IDENTIFIER_FOR_LCP)) {
MOZ_ASSERT(!doc->ContentIdentifiersForLCP().Contains(aElement));
return false;
}
if (auto entry = doc->ContentIdentifiersForLCP().Lookup(aElement)) {
return entry.Data().Contains(aImgRequestProxy);
}
MOZ_ASSERT_UNREACHABLE("we should always have an entry when the flag exists");
return false;
}
bool PerformanceMainThread::UpdateLargestContentfulPaintSize(double aSize) {
if (aSize > mLargestContentfulPaintSize) {
mLargestContentfulPaintSize = aSize;
return true;
}
return false;
}
void PerformanceMainThread::SetHasDispatchedScrollEvent() {
mHasDispatchedScrollEvent = true;
ClearGeneratedTempDataForLCP();
}
void PerformanceMainThread::SetHasDispatchedInputEvent() {
mHasDispatchedInputEvent = true;
ClearGeneratedTempDataForLCP();
}
void PerformanceMainThread::ClearGeneratedTempDataForLCP() {
mTextFrameUnions.Clear();
mImagesPendingRendering.Clear();
nsIGlobalObject* ownerGlobal = GetOwnerGlobal();
if (!ownerGlobal) {
return;
}
if (Document* document = ownerGlobal->GetAsInnerWindow()->GetExtantDoc()) {
document->ContentIdentifiersForLCP().Clear();
}
}
} // namespace mozilla::dom