mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 15:23:51 +00:00
Bug 1722322 - Implement LargestContentfulPaint r=emilio
Spec: https://w3c.github.io/largest-contentful-paint Differential Revision: https://phabricator.services.mozilla.com/D151079
This commit is contained in:
parent
62e8223aca
commit
611093989e
@ -647,7 +647,7 @@ IntersectionInput DOMIntersectionObserver::ComputeInput(
|
||||
// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
|
||||
// (steps 2.1 - 2.5)
|
||||
IntersectionOutput DOMIntersectionObserver::Intersect(
|
||||
const IntersectionInput& aInput, Element& aTarget,
|
||||
const IntersectionInput& aInput, const Element& aTarget,
|
||||
IsContentVisibilityObserver aIsContentVisibilityObserver) {
|
||||
const bool isSimilarOrigin = SimilarOrigin(aTarget, aInput.mRootNode) ==
|
||||
BrowsingContextOrigin::Similar;
|
||||
|
@ -154,7 +154,7 @@ class DOMIntersectionObserver final : public nsISupports,
|
||||
|
||||
enum class IsContentVisibilityObserver : bool { No, Yes };
|
||||
static IntersectionOutput Intersect(
|
||||
const IntersectionInput&, Element&,
|
||||
const IntersectionInput&, const Element&,
|
||||
IsContentVisibilityObserver = IsContentVisibilityObserver::No);
|
||||
// Intersects with a given rect, already relative to the root frame.
|
||||
static IntersectionOutput Intersect(const IntersectionInput&, const nsRect&);
|
||||
@ -168,6 +168,9 @@ class DOMIntersectionObserver final : public nsISupports,
|
||||
static already_AddRefed<DOMIntersectionObserver>
|
||||
CreateContentVisibilityObserver(Document&);
|
||||
|
||||
static Maybe<nsRect> EdgeInclusiveIntersection(const nsRect& aRect,
|
||||
const nsRect& aOtherRect);
|
||||
|
||||
protected:
|
||||
void Connect();
|
||||
void QueueIntersectionObserverEntry(Element* aTarget,
|
||||
|
@ -2539,6 +2539,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameRequestManager)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentIdentifiersForLCP)
|
||||
|
||||
// Traverse all our nsCOMArrays.
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
|
||||
@ -2664,6 +2665,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mContentIdentifiersForLCP);
|
||||
|
||||
if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) {
|
||||
tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp,
|
||||
|
@ -53,6 +53,7 @@
|
||||
#include "mozilla/dom/RadioGroupContainer.h"
|
||||
#include "mozilla/dom/TreeOrderedArray.h"
|
||||
#include "mozilla/dom/ViewportMetaData.h"
|
||||
#include "mozilla/dom/LargestContentfulPaint.h"
|
||||
#include "mozilla/glean/GleanMetrics.h"
|
||||
#include "nsAtom.h"
|
||||
#include "nsCOMArray.h"
|
||||
@ -2428,6 +2429,10 @@ class Document : public nsINode,
|
||||
|
||||
LinkedList<MediaQueryList>& MediaQueryLists() { return mDOMMediaQueryLists; }
|
||||
|
||||
nsTHashtable<LCPEntryHashEntry>& ContentIdentifiersForLCP() {
|
||||
return mContentIdentifiersForLCP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the compatibility mode for this document
|
||||
*/
|
||||
@ -5043,6 +5048,11 @@ class Document : public nsINode,
|
||||
// Our live MediaQueryLists
|
||||
LinkedList<MediaQueryList> mDOMMediaQueryLists;
|
||||
|
||||
// A hashset to keep track of which {element, imgRequestProxy}
|
||||
// combination has been processed to avoid considering the same
|
||||
// element twice for LargestContentfulPaint.
|
||||
nsTHashtable<LCPEntryHashEntry> mContentIdentifiersForLCP;
|
||||
|
||||
// Array of observers
|
||||
nsTObserverArray<nsIDocumentObserver*> mObservers;
|
||||
|
||||
|
@ -181,8 +181,13 @@ enum : uint32_t {
|
||||
// element or has a HTML datalist element ancestor.
|
||||
ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR = ELEMENT_FLAG_BIT(4),
|
||||
|
||||
// If this flag is set on an element, that means this element
|
||||
// has been considered by our LargestContentfulPaint algorithm and
|
||||
// it's not going to be considered again.
|
||||
ELEMENT_PROCESSED_BY_LCP_FOR_TEXT = ELEMENT_FLAG_BIT(5),
|
||||
|
||||
// Remaining bits are for subclasses
|
||||
ELEMENT_TYPE_SPECIFIC_BITS_OFFSET = NODE_TYPE_SPECIFIC_BITS_OFFSET + 5
|
||||
ELEMENT_TYPE_SPECIFIC_BITS_OFFSET = NODE_TYPE_SPECIFIC_BITS_OFFSET + 6
|
||||
};
|
||||
|
||||
#undef ELEMENT_FLAG_BIT
|
||||
|
@ -55,6 +55,7 @@
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/intl/LocaleService.h"
|
||||
#include "mozilla/intl/Locale.h"
|
||||
#include "mozilla/dom/LargestContentfulPaint.h"
|
||||
#include "mozilla/net/UrlClassifierFeatureFactory.h"
|
||||
#include "mozilla/widget/TextRecognition.h"
|
||||
|
||||
@ -254,9 +255,11 @@ void nsImageLoadingContent::OnLoadComplete(imgIRequest* aRequest,
|
||||
FireEvent(u"error"_ns);
|
||||
}
|
||||
|
||||
SVGObserverUtils::InvalidateDirectRenderingObservers(
|
||||
AsContent()->AsElement());
|
||||
Element* element = AsContent()->AsElement();
|
||||
SVGObserverUtils::InvalidateDirectRenderingObservers(element);
|
||||
MaybeResolveDecodePromises();
|
||||
LargestContentfulPaint::MaybeProcessImageForElementTiming(mCurrentRequest,
|
||||
element);
|
||||
}
|
||||
|
||||
void nsImageLoadingContent::OnUnlockedDraw() {
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "mozilla/dom/NotifyPaintEvent.h"
|
||||
#include "mozilla/dom/PageTransitionEvent.h"
|
||||
#include "mozilla/dom/PerformanceEventTiming.h"
|
||||
#include "mozilla/dom/PerformanceMainThread.h"
|
||||
#include "mozilla/dom/PointerEvent.h"
|
||||
#include "mozilla/dom/RootedDictionary.h"
|
||||
#include "mozilla/dom/ScrollAreaEvent.h"
|
||||
@ -838,6 +839,14 @@ nsresult EventDispatcher::Dispatch(EventTarget* aTarget,
|
||||
if (aPresContext && !aPresContext->IsPrintingOrPrintPreview()) {
|
||||
eventTimingEntry =
|
||||
PerformanceEventTiming::TryGenerateEventTiming(target, aEvent);
|
||||
|
||||
if (aEvent->IsTrusted() && aEvent->mMessage == eScroll) {
|
||||
if (auto* perf = aPresContext->GetPerformanceMainThread()) {
|
||||
if (!perf->HasDispatchedScrollEvent()) {
|
||||
perf->SetHasDispatchedScrollEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool retargeted = false;
|
||||
|
@ -5,15 +5,30 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsRFPService.h"
|
||||
#include "Performance.h"
|
||||
#include "imgRequest.h"
|
||||
#include "PerformanceMainThread.h"
|
||||
#include "LargestContentfulPaint.h"
|
||||
|
||||
#include "mozilla/dom/DOMIntersectionObserver.h"
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
|
||||
#include "mozilla/PresShell.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/nsVideoFrame.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
static LazyLogModule gLCPLogging("LargestContentfulPaint");
|
||||
|
||||
#define LOG(...) MOZ_LOG(gLCPLogging, LogLevel::Debug, (__VA_ARGS__))
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(LargestContentfulPaint, PerformanceEntry,
|
||||
mPerformance, mURI, mElement)
|
||||
mPerformance, mURI, mElement,
|
||||
mLCPImageEntryKey)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LargestContentfulPaint)
|
||||
NS_INTERFACE_MAP_END_INHERITING(PerformanceEntry)
|
||||
@ -21,19 +36,34 @@ NS_INTERFACE_MAP_END_INHERITING(PerformanceEntry)
|
||||
NS_IMPL_ADDREF_INHERITED(LargestContentfulPaint, PerformanceEntry)
|
||||
NS_IMPL_RELEASE_INHERITED(LargestContentfulPaint, PerformanceEntry)
|
||||
|
||||
LargestContentfulPaint::LargestContentfulPaint(Performance* aPerformance,
|
||||
DOMHighResTimeStamp aRenderTime,
|
||||
DOMHighResTimeStamp aLoadTime,
|
||||
unsigned long aSize,
|
||||
nsIURI* aURI, Element* aElement)
|
||||
static double GetAreaInDoublePixelsFromAppUnits(const nsSize& aSize) {
|
||||
return NSAppUnitsToDoublePixels(aSize.Width(), AppUnitsPerCSSPixel()) *
|
||||
NSAppUnitsToDoublePixels(aSize.Height(), AppUnitsPerCSSPixel());
|
||||
}
|
||||
|
||||
static double GetAreaInDoublePixelsFromAppUnits(const nsRect& aRect) {
|
||||
return NSAppUnitsToDoublePixels(aRect.Width(), AppUnitsPerCSSPixel()) *
|
||||
NSAppUnitsToDoublePixels(aRect.Height(), AppUnitsPerCSSPixel());
|
||||
}
|
||||
|
||||
ImagePendingRendering::ImagePendingRendering(
|
||||
const LCPImageEntryKey& aLCPImageEntryKey, DOMHighResTimeStamp aLoadTime)
|
||||
: mLCPImageEntryKey(aLCPImageEntryKey), mLoadTime(aLoadTime) {}
|
||||
|
||||
LargestContentfulPaint::LargestContentfulPaint(
|
||||
PerformanceMainThread* aPerformance, const DOMHighResTimeStamp aRenderTime,
|
||||
const DOMHighResTimeStamp aLoadTime, const unsigned long aSize,
|
||||
nsIURI* aURI, Element* aElement,
|
||||
const Maybe<const LCPImageEntryKey>& aLCPImageEntryKey)
|
||||
: PerformanceEntry(aPerformance->GetParentObject(), u""_ns,
|
||||
kLargestContentfulPaintName),
|
||||
mPerformance(static_cast<PerformanceMainThread*>(aPerformance)),
|
||||
mPerformance(aPerformance),
|
||||
mRenderTime(aRenderTime),
|
||||
mLoadTime(aLoadTime),
|
||||
mSize(aSize),
|
||||
mURI(aURI),
|
||||
mElement(aElement) {
|
||||
mElement(aElement),
|
||||
mLCPImageEntryKey(aLCPImageEntryKey) {
|
||||
MOZ_ASSERT(mPerformance);
|
||||
MOZ_ASSERT(mElement);
|
||||
// The element could be a pseudo-element
|
||||
@ -58,6 +88,187 @@ Element* LargestContentfulPaint::GetElement() const {
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
void LargestContentfulPaint::BufferEntryIfNeeded() {
|
||||
mPerformance->BufferLargestContentfulPaintEntryIfNeeded(this);
|
||||
}
|
||||
|
||||
/* static*/
|
||||
bool LCPHelpers::IsQualifiedImageRequest(imgRequest* aRequest,
|
||||
Element* aContainingElement) {
|
||||
MOZ_ASSERT(aContainingElement);
|
||||
if (!aRequest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aRequest->IsChrome()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!aContainingElement->ChromeOnlyAccess()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exception: this is a poster image of video element
|
||||
if (nsIContent* parent = aContainingElement->GetParent()) {
|
||||
nsVideoFrame* videoFrame = do_QueryFrame(parent->GetPrimaryFrame());
|
||||
if (videoFrame && videoFrame->GetPosterImage() == aContainingElement) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Exception: CSS generated images
|
||||
if (aContainingElement->IsInNativeAnonymousSubtree()) {
|
||||
if (nsINode* rootParentOrHost =
|
||||
aContainingElement
|
||||
->GetClosestNativeAnonymousSubtreeRootParentOrHost()) {
|
||||
if (!rootParentOrHost->ChromeOnlyAccess()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void LargestContentfulPaint::MaybeProcessImageForElementTiming(
|
||||
imgRequestProxy* aRequest, Element* aElement) {
|
||||
if (!StaticPrefs::dom_enable_largest_contentful_paint()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aRequest);
|
||||
imgRequest* request = aRequest->GetOwner();
|
||||
if (!LCPHelpers::IsQualifiedImageRequest(request, aElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Document* document = aElement->GetComposedDoc();
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsPresContext* pc =
|
||||
aElement->GetPresContext(Element::PresContextFor::eForComposedDoc);
|
||||
if (!pc) {
|
||||
return;
|
||||
}
|
||||
|
||||
PerformanceMainThread* performance = pc->GetPerformanceMainThread();
|
||||
if (!performance) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (MOZ_UNLIKELY(MOZ_LOG_TEST(gLCPLogging, LogLevel::Debug))) {
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
aRequest->GetURI(getter_AddRefs(uri));
|
||||
LOG("MaybeProcessImageForElementTiming, Element=%p, URI=%s, "
|
||||
"performance=%p ",
|
||||
aElement, uri ? uri->GetSpecOrDefault().get() : "", performance);
|
||||
}
|
||||
|
||||
const LCPImageEntryKey entryKey = LCPImageEntryKey(aElement, aRequest);
|
||||
if (!document->ContentIdentifiersForLCP().EnsureInserted(entryKey)) {
|
||||
LOG(" The content identifier existed for element=%p and request=%p, "
|
||||
"return.",
|
||||
aElement, aRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
uint32_t status = imgIRequest::STATUS_NONE;
|
||||
aRequest->GetImageStatus(&status);
|
||||
MOZ_ASSERT(status & imgIRequest::STATUS_LOAD_COMPLETE);
|
||||
#endif
|
||||
|
||||
// Here we are exposing the load time of the image which could be
|
||||
// a privacy concern. The spec talks about it at
|
||||
// https://wicg.github.io/element-timing/#sec-security
|
||||
// TLDR: The similar metric can be obtained by ResourceTiming
|
||||
// API and onload handlers already, so this is not exposing anything
|
||||
// new.
|
||||
DOMHighResTimeStamp nowTime =
|
||||
performance->TimeStampToDOMHighResForRendering(TimeStamp::Now());
|
||||
|
||||
if (!request->IsData() && !request->ShouldReportRenderTimeForLCP()) {
|
||||
// https://wicg.github.io/element-timing/#report-image-element-timing
|
||||
LOG(" Added a pending image rendering (TAO FAILED)");
|
||||
// For TAO failed requests, the renderTime is exposed as 0 for
|
||||
// security reasons.
|
||||
LCPHelpers::CreateLCPEntryForImage(performance, aElement, aRequest, nowTime,
|
||||
0 /* aRenderTime */, entryKey);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, add the triple (element, imageRequest, now) to root’s images
|
||||
// pending rendering.
|
||||
//
|
||||
// At this point, the loadTime of the image is known, but
|
||||
// the renderTime is unknown, so it's added to ImagesPendingRendering
|
||||
// as a placeholder, and the corresponding LCP entry will be created
|
||||
// when the renderTime is known.
|
||||
LOG(" Added a pending image rendering (TAO PASSED)");
|
||||
performance->AddImagesPendingRendering(
|
||||
ImagePendingRendering{entryKey, nowTime});
|
||||
}
|
||||
|
||||
bool LCPHelpers::CanFinalizeLCPEntry(const nsIFrame* aFrame) {
|
||||
if (!StaticPrefs::dom_enable_largest_contentful_paint()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!aFrame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsPresContext* presContext = aFrame->PresContext();
|
||||
return !presContext->HasStoppedGeneratingLCP() &&
|
||||
presContext->GetPerformanceMainThread();
|
||||
}
|
||||
|
||||
void LCPHelpers::FinalizeLCPEntryForImage(
|
||||
Element* aContainingBlock, imgRequestProxy* aImgRequestProxy,
|
||||
const nsRect& aTargetRectRelativeToSelf) {
|
||||
LOG("FinalizeLCPEntryForImage element=%p", aContainingBlock);
|
||||
if (!aImgRequestProxy) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsQualifiedImageRequest(aImgRequestProxy->GetOwner(),
|
||||
aContainingBlock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsIFrame* frame = aContainingBlock->GetPrimaryFrame();
|
||||
|
||||
if (!CanFinalizeLCPEntry(frame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PerformanceMainThread* performance =
|
||||
frame->PresContext()->GetPerformanceMainThread();
|
||||
MOZ_ASSERT(performance);
|
||||
|
||||
RefPtr<LargestContentfulPaint> entry =
|
||||
performance->GetImageLCPEntry(aContainingBlock, aImgRequestProxy);
|
||||
if (!entry) {
|
||||
LOG(" No Image Entry");
|
||||
return;
|
||||
}
|
||||
entry->UpdateSize(aContainingBlock, aTargetRectRelativeToSelf, performance,
|
||||
true);
|
||||
// If area is less than or equal to document’s largest contentful paint size,
|
||||
// return.
|
||||
if (!performance->UpdateLargestContentfulPaintSize(entry->Size())) {
|
||||
LOG(
|
||||
|
||||
" This paint(%lu) is not greater than the largest paint (%lf)that "
|
||||
"we've "
|
||||
"reported so far, return",
|
||||
entry->Size(), performance->GetLargestContentfulPaintSize());
|
||||
return;
|
||||
}
|
||||
|
||||
entry->QueueEntry();
|
||||
}
|
||||
|
||||
DOMHighResTimeStamp LargestContentfulPaint::RenderTime() const {
|
||||
return nsRFPService::ReduceTimePrecisionAsMSecs(
|
||||
mRenderTime, mPerformance->GetRandomTimelineSeed(),
|
||||
@ -77,9 +288,228 @@ DOMHighResTimeStamp LargestContentfulPaint::StartTime() const {
|
||||
mPerformance->GetRTPCallerType());
|
||||
}
|
||||
|
||||
/* static */
|
||||
Element* LargestContentfulPaint::GetContainingBlockForTextFrame(
|
||||
const nsTextFrame* aTextFrame) {
|
||||
nsIFrame* containingFrame = aTextFrame->GetContainingBlock();
|
||||
MOZ_ASSERT(containingFrame);
|
||||
return Element::FromNodeOrNull(containingFrame->GetContent());
|
||||
}
|
||||
|
||||
void LargestContentfulPaint::QueueEntry() {
|
||||
LOG("QueueEntry entry=%p", this);
|
||||
mPerformance->QueueLargestContentfulPaintEntry(this);
|
||||
}
|
||||
|
||||
void LargestContentfulPaint::GetUrl(nsAString& aUrl) {
|
||||
if (mURI) {
|
||||
CopyUTF8toUTF16(mURI->GetSpecOrDefault(), aUrl);
|
||||
}
|
||||
}
|
||||
|
||||
void LargestContentfulPaint::UpdateSize(
|
||||
const Element* aContainingBlock, const nsRect& aTargetRectRelativeToSelf,
|
||||
const PerformanceMainThread* aPerformance, bool aIsImage) {
|
||||
nsIFrame* frame = aContainingBlock->GetPrimaryFrame();
|
||||
MOZ_ASSERT(frame);
|
||||
|
||||
nsIFrame* rootFrame = frame->PresShell()->GetRootFrame();
|
||||
if (!rootFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame->Style()->IsInOpacityZeroSubtree()) {
|
||||
LOG(" Opacity:0 return");
|
||||
return;
|
||||
}
|
||||
|
||||
// The following size computation is based on a pending pull request
|
||||
// https://github.com/w3c/largest-contentful-paint/pull/99
|
||||
|
||||
// Let visibleDimensions be concreteDimensions, adjusted for positioning
|
||||
// by object-position or background-position and element’s content box.
|
||||
const nsRect& visibleDimensions = aTargetRectRelativeToSelf;
|
||||
|
||||
// Let clientContentRect be the smallest DOMRectReadOnly containing
|
||||
// visibleDimensions with element’s transforms applied.
|
||||
nsRect clientContentRect = nsLayoutUtils::TransformFrameRectToAncestor(
|
||||
frame, visibleDimensions, rootFrame);
|
||||
|
||||
// Let intersectionRect be the value returned by the intersection rect
|
||||
// algorithm using element as the target and viewport as the root.
|
||||
// (From https://wicg.github.io/element-timing/#sec-report-image-element)
|
||||
IntersectionInput input = DOMIntersectionObserver::ComputeInput(
|
||||
*frame->PresContext()->Document(), rootFrame->GetContent(), nullptr);
|
||||
const IntersectionOutput output =
|
||||
DOMIntersectionObserver::Intersect(input, *aContainingBlock);
|
||||
|
||||
Maybe<nsRect> intersectionRect = output.mIntersectionRect;
|
||||
|
||||
if (intersectionRect.isNothing()) {
|
||||
LOG(" The intersectionRect is nothing for Element=%p. return.",
|
||||
aContainingBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
// Let intersectingClientContentRect be the intersection of clientContentRect
|
||||
// with intersectionRect.
|
||||
Maybe<nsRect> intersectionWithContentRect =
|
||||
clientContentRect.EdgeInclusiveIntersection(intersectionRect.value());
|
||||
|
||||
if (intersectionWithContentRect.isNothing()) {
|
||||
LOG(" The intersectionWithContentRect is nothing for Element=%p. return.",
|
||||
aContainingBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
nsRect renderedRect = intersectionWithContentRect.value();
|
||||
|
||||
double area = GetAreaInDoublePixelsFromAppUnits(renderedRect);
|
||||
|
||||
double viewport = GetAreaInDoublePixelsFromAppUnits(input.mRootRect);
|
||||
|
||||
LOG(" Viewport = %f, RenderRect = %f.", viewport, area);
|
||||
// We don't want to report things that take the entire viewport.
|
||||
if (area >= viewport) {
|
||||
LOG(" The renderedRect is at least same as the area of the "
|
||||
"viewport for Element=%p, return.",
|
||||
aContainingBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
Maybe<nsSize> intrinsicSize = frame->GetIntrinsicSize().ToSize();
|
||||
const bool hasIntrinsicSize = intrinsicSize && !intrinsicSize->IsEmpty();
|
||||
|
||||
if (aIsImage && hasIntrinsicSize) {
|
||||
// Let (naturalWidth, naturalHeight) be imageRequest’s natural dimension.
|
||||
// Let naturalArea be naturalWidth * naturalHeight.
|
||||
double naturalArea =
|
||||
GetAreaInDoublePixelsFromAppUnits(intrinsicSize.value());
|
||||
|
||||
LOG(" naturalArea = %f", naturalArea);
|
||||
|
||||
// Let boundingClientArea be clientContentRect’s width * clientContentRect’s
|
||||
// height.
|
||||
double boundingClientArea =
|
||||
NSAppUnitsToDoublePixels(clientContentRect.Width(),
|
||||
AppUnitsPerCSSPixel()) *
|
||||
NSAppUnitsToDoublePixels(clientContentRect.Height(),
|
||||
AppUnitsPerCSSPixel());
|
||||
LOG(" boundingClientArea = %f", boundingClientArea);
|
||||
|
||||
// Let scaleFactor be boundingClientArea / naturalArea.
|
||||
double scaleFactor = boundingClientArea / naturalArea;
|
||||
LOG(" scaleFactor = %f", scaleFactor);
|
||||
|
||||
// If scaleFactor is greater than 1, then divide area by scaleFactor.
|
||||
if (scaleFactor > 1) {
|
||||
LOG(" area before sacled doown %f", area);
|
||||
area = area / scaleFactor;
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!mSize);
|
||||
mSize = area;
|
||||
}
|
||||
|
||||
void LCPTextFrameHelper::MaybeUnionTextFrame(
|
||||
nsTextFrame* aTextFrame, const nsRect& aRelativeToSelfRect) {
|
||||
if (!StaticPrefs::dom_enable_largest_contentful_paint() ||
|
||||
aTextFrame->PresContext()->HasStoppedGeneratingLCP()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Element* containingBlock =
|
||||
LargestContentfulPaint::GetContainingBlockForTextFrame(aTextFrame);
|
||||
if (!containingBlock ||
|
||||
// If element is contained in doc’s set of elements with rendered text,
|
||||
// continue
|
||||
containingBlock->HasFlag(ELEMENT_PROCESSED_BY_LCP_FOR_TEXT) ||
|
||||
containingBlock->ChromeOnlyAccess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(containingBlock->GetPrimaryFrame());
|
||||
|
||||
PerformanceMainThread* perf =
|
||||
aTextFrame->PresContext()->GetPerformanceMainThread();
|
||||
if (!perf) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& unionRect = perf->GetTextFrameUnions().LookupOrInsert(containingBlock);
|
||||
unionRect = unionRect.Union(aRelativeToSelfRect);
|
||||
}
|
||||
|
||||
void LCPHelpers::CreateLCPEntryForImage(
|
||||
PerformanceMainThread* aPerformance, Element* aElement,
|
||||
imgRequestProxy* aRequestProxy, const DOMHighResTimeStamp aLoadTime,
|
||||
const DOMHighResTimeStamp aRenderTime,
|
||||
const LCPImageEntryKey& aImageEntryKey) {
|
||||
MOZ_ASSERT(StaticPrefs::dom_enable_largest_contentful_paint());
|
||||
MOZ_ASSERT(aRequestProxy);
|
||||
if (MOZ_UNLIKELY(MOZ_LOG_TEST(gLCPLogging, LogLevel::Debug))) {
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
aRequestProxy->GetURI(getter_AddRefs(uri));
|
||||
LOG("CreateLCPEntryForImage "
|
||||
"Element=%p, aRequestProxy=%p, URI=%s loadTime=%f, "
|
||||
"aRenderTime=%f\n",
|
||||
aElement, aRequestProxy, uri->GetSpecOrDefault().get(), aLoadTime,
|
||||
aRenderTime);
|
||||
}
|
||||
MOZ_ASSERT(aPerformance);
|
||||
MOZ_ASSERT_IF(aRenderTime < aLoadTime, aRenderTime == 0);
|
||||
if (aPerformance->HasDispatchedInputEvent() ||
|
||||
aPerformance->HasDispatchedScrollEvent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Let url be the empty string.
|
||||
// If imageRequest is not null, set url to be imageRequest’s request URL.
|
||||
nsCOMPtr<nsIURI> requestURI;
|
||||
aRequestProxy->GetURI(getter_AddRefs(requestURI));
|
||||
|
||||
// At this point, we have all the information about the entry
|
||||
// except the size.
|
||||
RefPtr<LargestContentfulPaint> entry =
|
||||
new LargestContentfulPaint(aPerformance, aRenderTime, aLoadTime, 0,
|
||||
requestURI, aElement, Some(aImageEntryKey));
|
||||
|
||||
LOG(" Upsert a LargestContentfulPaint entry=%p to LCPEntryMap.",
|
||||
entry.get());
|
||||
aPerformance->StoreImageLCPEntry(aElement, aRequestProxy, entry);
|
||||
}
|
||||
|
||||
void LCPHelpers::FinalizeLCPEntryForText(
|
||||
PerformanceMainThread* aPerformance, const DOMHighResTimeStamp aRenderTime,
|
||||
Element* aContainingBlock, const nsRect& aTargetRectRelativeToSelf,
|
||||
const nsPresContext* aPresContext) {
|
||||
MOZ_ASSERT(aPerformance);
|
||||
LOG("FinalizeLCPEntryForText element=%p", aContainingBlock);
|
||||
|
||||
if (!aContainingBlock->GetPrimaryFrame()) {
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(CanFinalizeLCPEntry(aContainingBlock->GetPrimaryFrame()));
|
||||
MOZ_ASSERT(!aContainingBlock->HasFlag(ELEMENT_PROCESSED_BY_LCP_FOR_TEXT));
|
||||
MOZ_ASSERT(!aContainingBlock->ChromeOnlyAccess());
|
||||
|
||||
aContainingBlock->SetFlags(ELEMENT_PROCESSED_BY_LCP_FOR_TEXT);
|
||||
|
||||
RefPtr<LargestContentfulPaint> entry = new LargestContentfulPaint(
|
||||
aPerformance, aRenderTime, 0, 0, nullptr, aContainingBlock, Nothing());
|
||||
|
||||
entry->UpdateSize(aContainingBlock, aTargetRectRelativeToSelf, aPerformance,
|
||||
false);
|
||||
// If area is less than or equal to document’s largest contentful paint size,
|
||||
// return.
|
||||
if (!aPerformance->UpdateLargestContentfulPaintSize(entry->Size())) {
|
||||
LOG(" This paint(%lu) is not greater than the largest paint (%lf)that "
|
||||
"we've "
|
||||
"reported so far, return",
|
||||
entry->Size(), aPerformance->GetLargestContentfulPaintSize());
|
||||
return;
|
||||
}
|
||||
entry->QueueEntry();
|
||||
}
|
||||
} // namespace mozilla::dom
|
||||
|
@ -10,7 +10,8 @@
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "mozilla/dom/PerformanceEntry.h"
|
||||
#include "mozilla/dom/PerformanceLargestContentfulPaintBinding.h"
|
||||
#include "nsIWeakReferenceUtils.h"
|
||||
|
||||
#include "imgRequestProxy.h"
|
||||
|
||||
class nsTextFrame;
|
||||
namespace mozilla::dom {
|
||||
@ -21,6 +22,143 @@ static constexpr nsLiteralString kLargestContentfulPaintName =
|
||||
class Performance;
|
||||
class PerformanceMainThread;
|
||||
|
||||
struct LCPImageEntryKey {
|
||||
LCPImageEntryKey(Element* aElement, imgRequestProxy* aImgRequestProxy)
|
||||
: mElement(aElement), mImageRequestProxy(aImgRequestProxy) {
|
||||
MOZ_ASSERT(aElement);
|
||||
MOZ_ASSERT(aImgRequestProxy);
|
||||
}
|
||||
|
||||
LCPImageEntryKey(const LCPImageEntryKey& aLCPImageEntryKey) {
|
||||
mElement = aLCPImageEntryKey.mElement;
|
||||
mImageRequestProxy = aLCPImageEntryKey.mImageRequestProxy;
|
||||
}
|
||||
|
||||
bool operator==(const LCPImageEntryKey& aOther) const {
|
||||
return mElement == aOther.mElement &&
|
||||
mImageRequestProxy == aOther.mImageRequestProxy;
|
||||
}
|
||||
|
||||
bool Equals(const Element* aElement,
|
||||
const imgRequestProxy* aImgRequestProxy) const {
|
||||
return mElement == mElement && mImageRequestProxy == mImageRequestProxy;
|
||||
}
|
||||
|
||||
RefPtr<Element> mElement;
|
||||
RefPtr<imgRequestProxy> mImageRequestProxy;
|
||||
|
||||
~LCPImageEntryKey() = default;
|
||||
};
|
||||
|
||||
inline void ImplCycleCollectionUnlink(LCPImageEntryKey& aField) {
|
||||
aField.mElement = nullptr;
|
||||
aField.mImageRequestProxy = nullptr;
|
||||
}
|
||||
|
||||
inline void ImplCycleCollectionTraverse(
|
||||
nsCycleCollectionTraversalCallback& aCallback, LCPImageEntryKey& aField,
|
||||
const char* aName, uint32_t aFlags = 0) {
|
||||
ImplCycleCollectionTraverse(aCallback, aField.mElement,
|
||||
"LCPImageEntryKey.mElement", aFlags);
|
||||
ImplCycleCollectionTraverse(aCallback, aField.mImageRequestProxy,
|
||||
"LCPImageEntryKey.mImageRequestProxy", aFlags);
|
||||
}
|
||||
|
||||
struct LCPTextFrameHelper final {
|
||||
static void MaybeUnionTextFrame(nsTextFrame* aTextFrame,
|
||||
const nsRect& aRelativeToSelfRect);
|
||||
static void HasContainingElementBeenProcessed();
|
||||
};
|
||||
|
||||
class ImagePendingRendering final {
|
||||
public:
|
||||
ImagePendingRendering(const LCPImageEntryKey& aLCPImageEntryKey,
|
||||
DOMHighResTimeStamp aLoadTime);
|
||||
|
||||
Element* GetElement() const { return mLCPImageEntryKey.mElement; }
|
||||
|
||||
imgRequestProxy* GetImgRequestProxy() const {
|
||||
return mLCPImageEntryKey.mImageRequestProxy;
|
||||
}
|
||||
|
||||
LCPImageEntryKey mLCPImageEntryKey;
|
||||
DOMHighResTimeStamp mLoadTime;
|
||||
};
|
||||
|
||||
inline void ImplCycleCollectionTraverse(
|
||||
nsCycleCollectionTraversalCallback& aCallback,
|
||||
ImagePendingRendering& aField, const char* aName, uint32_t aFlags = 0) {
|
||||
ImplCycleCollectionTraverse(aCallback, aField.mLCPImageEntryKey,
|
||||
"ImagePendingRendering.mImageEntryKey",
|
||||
aCallback.Flags());
|
||||
}
|
||||
|
||||
class LCPEntryHashEntry : public PLDHashEntryHdr {
|
||||
public:
|
||||
typedef const LCPImageEntryKey& KeyType;
|
||||
typedef const LCPImageEntryKey* KeyTypePointer;
|
||||
|
||||
explicit LCPEntryHashEntry(KeyTypePointer aKey) : mKey(*aKey) {}
|
||||
LCPEntryHashEntry(LCPEntryHashEntry&&) = default;
|
||||
|
||||
~LCPEntryHashEntry() = default;
|
||||
|
||||
bool KeyEquals(KeyTypePointer aKey) const { return mKey == *aKey; }
|
||||
|
||||
KeyType GetKey() const { return mKey; }
|
||||
|
||||
static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; }
|
||||
|
||||
static PLDHashNumber HashKey(KeyTypePointer aKey) {
|
||||
if (!aKey) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return mozilla::HashGeneric(
|
||||
reinterpret_cast<uintptr_t>(aKey->mElement.get()),
|
||||
reinterpret_cast<uintptr_t>(aKey->mImageRequestProxy.get()));
|
||||
}
|
||||
enum { ALLOW_MEMMOVE = true };
|
||||
|
||||
LCPImageEntryKey mKey;
|
||||
};
|
||||
|
||||
inline void ImplCycleCollectionTraverse(
|
||||
nsCycleCollectionTraversalCallback& aCallback, LCPEntryHashEntry& aField,
|
||||
const char* aName, uint32_t aFlags = 0) {
|
||||
ImplCycleCollectionTraverse(aCallback, aField.mKey, "LCPEntryHashEntry.mKey",
|
||||
aFlags);
|
||||
}
|
||||
|
||||
class LCPHelpers final {
|
||||
public:
|
||||
// Creates the LCP Entry for images with all information except the size of
|
||||
// the element. The size of the image is unknown at the moment. The entry is
|
||||
// not going to be queued in this function.
|
||||
static void CreateLCPEntryForImage(
|
||||
PerformanceMainThread* aPerformance, Element* aElement,
|
||||
imgRequestProxy* aRequestProxy, const DOMHighResTimeStamp aLoadTime,
|
||||
const DOMHighResTimeStamp aRenderTime,
|
||||
const LCPImageEntryKey& aContentIdentifier);
|
||||
|
||||
// Called when the size of the image is known.
|
||||
static void FinalizeLCPEntryForImage(Element* aContainingBlock,
|
||||
imgRequestProxy* aImgRequestProxy,
|
||||
const nsRect& aTargetRectRelativeToSelf);
|
||||
|
||||
static void FinalizeLCPEntryForText(PerformanceMainThread* aPerformance,
|
||||
const DOMHighResTimeStamp aRenderTime,
|
||||
Element* aContainingBlock,
|
||||
const nsRect& aTargetRectRelativeToSelf,
|
||||
const nsPresContext* aPresContext);
|
||||
|
||||
static bool IsQualifiedImageRequest(imgRequest* aRequest,
|
||||
Element* aContainingElement);
|
||||
|
||||
private:
|
||||
static bool CanFinalizeLCPEntry(const nsIFrame* aFrame);
|
||||
};
|
||||
|
||||
// https://w3c.github.io/largest-contentful-paint/
|
||||
class LargestContentfulPaint final : public PerformanceEntry {
|
||||
public:
|
||||
@ -29,10 +167,12 @@ class LargestContentfulPaint final : public PerformanceEntry {
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(LargestContentfulPaint,
|
||||
PerformanceEntry)
|
||||
|
||||
LargestContentfulPaint(Performance* aPerformance,
|
||||
DOMHighResTimeStamp aRenderTime,
|
||||
DOMHighResTimeStamp aLoadTime, unsigned long aSize,
|
||||
nsIURI* aURI, Element* aElement);
|
||||
LargestContentfulPaint(
|
||||
PerformanceMainThread* aPerformance,
|
||||
const DOMHighResTimeStamp aRenderTime,
|
||||
const DOMHighResTimeStamp aLoadTime, const unsigned long aSize,
|
||||
nsIURI* aURI, Element* aElement,
|
||||
const Maybe<const LCPImageEntryKey>& aLCPImageEntryKey);
|
||||
|
||||
JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
@ -51,6 +191,23 @@ class LargestContentfulPaint final : public PerformanceEntry {
|
||||
|
||||
Element* GetElement() const;
|
||||
|
||||
static Element* GetContainingBlockForTextFrame(const nsTextFrame* aTextFrame);
|
||||
|
||||
void UpdateSize(const Element* aContainingBlock,
|
||||
const nsRect& aTargetRectRelativeToSelf,
|
||||
const PerformanceMainThread* aPerformance, bool aIsImage);
|
||||
|
||||
void BufferEntryIfNeeded() override;
|
||||
|
||||
static void MaybeProcessImageForElementTiming(imgRequestProxy* aRequest,
|
||||
Element* aElement);
|
||||
|
||||
void QueueEntry();
|
||||
|
||||
const Maybe<LCPImageEntryKey>& GetLCPImageEntryKey() const {
|
||||
return mLCPImageEntryKey;
|
||||
}
|
||||
|
||||
private:
|
||||
~LargestContentfulPaint() = default;
|
||||
|
||||
@ -63,6 +220,8 @@ class LargestContentfulPaint final : public PerformanceEntry {
|
||||
|
||||
RefPtr<Element> mElement;
|
||||
RefPtr<nsAtom> mId;
|
||||
|
||||
Maybe<LCPImageEntryKey> mLCPImageEntryKey;
|
||||
};
|
||||
} // namespace mozilla::dom
|
||||
#endif
|
||||
|
@ -12,6 +12,7 @@
|
||||
#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"
|
||||
@ -25,9 +26,13 @@
|
||||
#include "nsIChannel.h"
|
||||
#include "nsIHttpChannel.h"
|
||||
#include "nsIDocShell.h"
|
||||
#include "nsTextFrame.h"
|
||||
#include "nsContainerFrame.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
extern mozilla::LazyLogModule gLCPLogging;
|
||||
|
||||
namespace {
|
||||
|
||||
void GetURLSpecFromChannel(nsITimedChannel* aChannel, nsAString& aSpec) {
|
||||
@ -59,19 +64,24 @@ 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, mFirstInputEvent,
|
||||
mPendingPointerDown,
|
||||
mPendingEventTimingEntries, mEventCounts)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(
|
||||
mTiming, mNavigation, mDocEntry, mFCPTiming, mEventTimingEntries,
|
||||
mLargestContentfulPaintEntries, mFirstInputEvent, mPendingPointerDown,
|
||||
mPendingEventTimingEntries, mEventCounts)
|
||||
tmp->mImageLCPEntryMap.Clear();
|
||||
tmp->mTextFrameUnions.Clear();
|
||||
tmp->mImagesPendingRendering.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, mFirstInputEvent,
|
||||
mPendingPointerDown,
|
||||
mPendingEventTimingEntries, mEventCounts)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
|
||||
mTiming, mNavigation, mDocEntry, mFCPTiming, mEventTimingEntries,
|
||||
mLargestContentfulPaintEntries, mFirstInputEvent, mPendingPointerDown,
|
||||
mPendingEventTimingEntries, mEventCounts, mImagesPendingRendering,
|
||||
mImageLCPEntryMap, mTextFrameUnions)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceMainThread,
|
||||
@ -241,6 +251,15 @@ void PerformanceMainThread::BufferEventTimingEntryIfNeeded(
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@ -267,7 +286,7 @@ void PerformanceMainThread::DispatchPendingEventTimingEntries() {
|
||||
MOZ_ASSERT(!mFirstInputEvent);
|
||||
mFirstInputEvent = mPendingPointerDown.forget();
|
||||
QueueEntry(mFirstInputEvent);
|
||||
mHasDispatchedInputEvent = true;
|
||||
SetHasDispatchedInputEvent();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -277,7 +296,7 @@ void PerformanceMainThread::DispatchPendingEventTimingEntries() {
|
||||
mFirstInputEvent = entry->Clone();
|
||||
mFirstInputEvent->SetEntryType(u"first-input"_ns);
|
||||
QueueEntry(mFirstInputEvent);
|
||||
mHasDispatchedInputEvent = true;
|
||||
SetHasDispatchedInputEvent();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -449,6 +468,12 @@ void PerformanceMainThread::QueueNavigationTimingEntry() {
|
||||
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;
|
||||
@ -501,6 +526,14 @@ void PerformanceMainThread::GetEntriesByTypeForObserver(
|
||||
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);
|
||||
}
|
||||
|
||||
@ -562,4 +595,129 @@ size_t PerformanceMainThread::SizeOfEventEntries(
|
||||
}
|
||||
return eventEntries;
|
||||
}
|
||||
|
||||
void PerformanceMainThread::ProcessElementTiming() {
|
||||
if (!StaticPrefs::dom_enable_largest_contentful_paint()) {
|
||||
return;
|
||||
}
|
||||
const bool shouldLCPDataEmpty =
|
||||
HasDispatchedInputEvent() || HasDispatchedScrollEvent();
|
||||
MOZ_ASSERT_IF(shouldLCPDataEmpty,
|
||||
mTextFrameUnions.IsEmpty() && mImageLCPEntryMap.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.
|
||||
DOMHighResTimeStamp rawNowTime =
|
||||
TimeStampToDOMHighResForRendering(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()) {
|
||||
LCPHelpers::CreateLCPEntryForImage(
|
||||
this, element, requestProxy, imagePendingRendering.mLoadTime,
|
||||
rawNowTime, imagePendingRendering.mLCPImageEntryKey);
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mImagesPendingRendering.IsEmpty());
|
||||
}
|
||||
|
||||
void PerformanceMainThread::FinalizeLCPEntriesForText() {
|
||||
nsPresContext* presContext = GetPresShell()->GetPresContext();
|
||||
MOZ_ASSERT(presContext);
|
||||
|
||||
DOMHighResTimeStamp renderTime =
|
||||
TimeStampToDOMHighResForRendering(presContext->GetMarkPaintTimingStart());
|
||||
|
||||
bool canFinalize = StaticPrefs::dom_enable_largest_contentful_paint() &&
|
||||
!presContext->HasStoppedGeneratingLCP();
|
||||
if (canFinalize) {
|
||||
for (const auto& textFrameUnion : GetTextFrameUnions()) {
|
||||
LCPHelpers::FinalizeLCPEntryForText(
|
||||
this, renderTime, textFrameUnion.GetKey(), textFrameUnion.GetData(),
|
||||
presContext);
|
||||
}
|
||||
}
|
||||
|
||||
ClearTextFrameUnions();
|
||||
}
|
||||
|
||||
void PerformanceMainThread::StoreImageLCPEntry(
|
||||
Element* aElement, imgRequestProxy* aImgRequestProxy,
|
||||
LargestContentfulPaint* aEntry) {
|
||||
mImageLCPEntryMap.InsertOrUpdate({aElement, aImgRequestProxy}, aEntry);
|
||||
}
|
||||
|
||||
already_AddRefed<LargestContentfulPaint>
|
||||
PerformanceMainThread::GetImageLCPEntry(Element* aElement,
|
||||
imgRequestProxy* aImgRequestProxy) {
|
||||
Maybe<RefPtr<LargestContentfulPaint>> entry =
|
||||
mImageLCPEntryMap.Extract({aElement, aImgRequestProxy});
|
||||
if (entry.isNothing()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Document* doc = aElement->GetComposedDoc();
|
||||
MOZ_ASSERT(doc, "Element should be connected when it's painted");
|
||||
|
||||
const Maybe<const LCPImageEntryKey>& contentIdentifier =
|
||||
entry.value()->GetLCPImageEntryKey();
|
||||
if (contentIdentifier.isSome()) {
|
||||
doc->ContentIdentifiersForLCP().EnsureRemoved(contentIdentifier.value());
|
||||
}
|
||||
|
||||
return entry.value().forget();
|
||||
}
|
||||
|
||||
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;
|
||||
mImageLCPEntryMap.Clear();
|
||||
ClearGeneratedTempDataForLCP();
|
||||
}
|
||||
|
||||
void PerformanceMainThread::ClearTextFrameUnions() { mTextFrameUnions.Clear(); }
|
||||
|
||||
void PerformanceMainThread::ClearGeneratedTempDataForLCP() {
|
||||
ClearTextFrameUnions();
|
||||
mImageLCPEntryMap.Clear();
|
||||
}
|
||||
} // namespace mozilla::dom
|
||||
|
@ -9,12 +9,19 @@
|
||||
|
||||
#include "Performance.h"
|
||||
#include "PerformanceStorage.h"
|
||||
#include "LargestContentfulPaint.h"
|
||||
#include "nsTextFrame.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
class PerformanceNavigationTiming;
|
||||
class PerformanceEventTiming;
|
||||
|
||||
using ImageLCPEntryMap =
|
||||
nsTHashMap<LCPEntryHashEntry, RefPtr<LargestContentfulPaint>>;
|
||||
|
||||
using TextFrameUnions = nsTHashMap<nsRefPtrHashKey<Element>, nsRect>;
|
||||
|
||||
class PerformanceMainThread final : public Performance,
|
||||
public PerformanceStorage {
|
||||
public:
|
||||
@ -45,11 +52,14 @@ class PerformanceMainThread final : public Performance,
|
||||
const nsAString& aInitiatorType,
|
||||
const nsAString& aEntryName);
|
||||
virtual void SetFCPTimingEntry(PerformancePaintTiming* aEntry) override;
|
||||
bool HadFCPTimingEntry() const { return mFCPTiming; }
|
||||
|
||||
void InsertEventTimingEntry(PerformanceEventTiming*) override;
|
||||
void BufferEventTimingEntryIfNeeded(PerformanceEventTiming*) override;
|
||||
void DispatchPendingEventTimingEntries() override;
|
||||
|
||||
void BufferLargestContentfulPaintEntryIfNeeded(LargestContentfulPaint*);
|
||||
|
||||
TimeStamp CreationTimeStamp() const override;
|
||||
|
||||
DOMHighResTimeStamp CreationTime() const override;
|
||||
@ -87,6 +97,7 @@ class PerformanceMainThread final : public Performance,
|
||||
|
||||
void UpdateNavigationTimingEntry() override;
|
||||
void QueueNavigationTimingEntry() override;
|
||||
void QueueLargestContentfulPaintEntry(LargestContentfulPaint* aEntry);
|
||||
|
||||
size_t SizeOfEventEntries(mozilla::MallocSizeOf aMallocSizeOf) const override;
|
||||
|
||||
@ -94,10 +105,40 @@ class PerformanceMainThread final : public Performance,
|
||||
static constexpr uint32_t kDefaultEventTimingDurationThreshold = 104;
|
||||
static constexpr double kDefaultEventTimingMinDuration = 16.0;
|
||||
|
||||
static constexpr uint32_t kMaxLargestContentfulPaintBufferSize = 150;
|
||||
|
||||
class EventCounts* EventCounts() override;
|
||||
|
||||
bool IsGlobalObjectWindow() const override { return true; };
|
||||
|
||||
bool HasDispatchedInputEvent() const { return mHasDispatchedInputEvent; }
|
||||
|
||||
void SetHasDispatchedScrollEvent();
|
||||
bool HasDispatchedScrollEvent() const { return mHasDispatchedScrollEvent; }
|
||||
|
||||
void ProcessElementTiming();
|
||||
|
||||
void AddImagesPendingRendering(ImagePendingRendering aImagePendingRendering) {
|
||||
mImagesPendingRendering.AppendElement(aImagePendingRendering);
|
||||
}
|
||||
|
||||
void StoreImageLCPEntry(Element* aElement, imgRequestProxy* aImgRequestProxy,
|
||||
LargestContentfulPaint* aEntry);
|
||||
|
||||
already_AddRefed<LargestContentfulPaint> GetImageLCPEntry(
|
||||
Element* aElement, imgRequestProxy* aImgRequestProxy);
|
||||
|
||||
bool UpdateLargestContentfulPaintSize(double aSize);
|
||||
double GetLargestContentfulPaintSize() const {
|
||||
return mLargestContentfulPaintSize;
|
||||
}
|
||||
|
||||
nsTHashMap<nsRefPtrHashKey<Element>, nsRect>& GetTextFrameUnions() {
|
||||
return mTextFrameUnions;
|
||||
}
|
||||
|
||||
void FinalizeLCPEntriesForText();
|
||||
|
||||
protected:
|
||||
~PerformanceMainThread();
|
||||
|
||||
@ -119,23 +160,76 @@ class PerformanceMainThread final : public Performance,
|
||||
JS::Heap<JSObject*> mMozMemory;
|
||||
|
||||
nsTArray<RefPtr<PerformanceEventTiming>> mEventTimingEntries;
|
||||
nsTArray<RefPtr<LargestContentfulPaint>> mLargestContentfulPaintEntries;
|
||||
|
||||
AutoCleanLinkedList<RefPtr<PerformanceEventTiming>>
|
||||
mPendingEventTimingEntries;
|
||||
bool mHasDispatchedInputEvent = false;
|
||||
bool mHasDispatchedScrollEvent = false;
|
||||
|
||||
RefPtr<PerformanceEventTiming> mFirstInputEvent;
|
||||
RefPtr<PerformanceEventTiming> mPendingPointerDown;
|
||||
|
||||
private:
|
||||
void ClearTextFrameUnions();
|
||||
void ClearGeneratedTempDataForLCP();
|
||||
|
||||
void SetHasDispatchedInputEvent();
|
||||
|
||||
bool mHasQueuedRefreshdriverObserver = false;
|
||||
|
||||
RefPtr<class EventCounts> mEventCounts;
|
||||
void IncEventCount(const nsAtom* aType);
|
||||
|
||||
PresShell* GetPresShell();
|
||||
|
||||
nsTArray<ImagePendingRendering> mImagesPendingRendering;
|
||||
|
||||
// The key is the pair of the element initiates the image loading
|
||||
// and the imgRequestProxy of the image, and the value is
|
||||
// the LCP entry for this image. When the image is
|
||||
// completely loaded, we add it to mImageLCPEntryMap.
|
||||
// Later, when the image is painted, we get the LCP entry from it
|
||||
// to update the size and queue the entry if needed.
|
||||
//
|
||||
// When the initiating element is disconnected from the document,
|
||||
// we keep the orphan entry because if the same memory address is
|
||||
// reused by a different LCP candidate, it'll update
|
||||
// mImageLCPEntryMap precedes before it tries to get the LCP entry.
|
||||
ImageLCPEntryMap mImageLCPEntryMap;
|
||||
|
||||
// Keeps track of the rendered size of the largest contentful paint that
|
||||
// we have processed so far.
|
||||
double mLargestContentfulPaintSize = 0.0;
|
||||
|
||||
// When a text frame is painted, its area (relative to the
|
||||
// containing block) is unioned with other text frames that
|
||||
// belong to the same containing block.
|
||||
// mTextFrameUnions's key is the containing block, and
|
||||
// the value is the unioned area.
|
||||
TextFrameUnions mTextFrameUnions;
|
||||
};
|
||||
|
||||
inline void ImplCycleCollectionTraverse(
|
||||
nsCycleCollectionTraversalCallback& aCallback, ImageLCPEntryMap& aField,
|
||||
const char* aName, uint32_t aFlags = 0) {
|
||||
for (auto& entry : aField) {
|
||||
ImplCycleCollectionTraverse(aCallback, entry.mKey, "ImageLCPEntryMap.mKey", aCallback.Flags());
|
||||
RefPtr<LargestContentfulPaint>* lcpEntry = entry.GetModifiableData();
|
||||
ImplCycleCollectionTraverse(aCallback, *lcpEntry, "ImageLCPEntryMap.mData", aCallback.Flags());
|
||||
}
|
||||
}
|
||||
|
||||
inline void ImplCycleCollectionTraverse(
|
||||
nsCycleCollectionTraversalCallback& aCallback, TextFrameUnions& aField,
|
||||
const char* aName, uint32_t aFlags = 0) {
|
||||
for (auto& entry : aField) {
|
||||
ImplCycleCollectionTraverse(
|
||||
aCallback, entry, "TextFrameUnions's key (nsRefPtrHashKey<Element>)",
|
||||
aFlags);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // mozilla_dom_PerformanceMainThread_h
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "nsQueryObject.h"
|
||||
#include "nsString.h"
|
||||
#include "PerformanceEntry.h"
|
||||
#include "LargestContentfulPaint.h"
|
||||
#include "PerformanceObserverEntryList.h"
|
||||
|
||||
using namespace mozilla;
|
||||
@ -215,6 +216,12 @@ void PerformanceObserver::Observe(const PerformanceObserverInit& aOptions,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (StaticPrefs::dom_enable_largest_contentful_paint()) {
|
||||
if (entryTypes.Contains(kLargestContentfulPaintName) &&
|
||||
!validEntryTypes.Contains(kLargestContentfulPaintName)) {
|
||||
validEntryTypes.AppendElement(kLargestContentfulPaintName);
|
||||
}
|
||||
}
|
||||
for (const nsLiteralString& name : kValidTypeNames) {
|
||||
if (entryTypes.Contains(name) && !validEntryTypes.Contains(name)) {
|
||||
validEntryTypes.AppendElement(name);
|
||||
@ -275,6 +282,12 @@ void PerformanceObserver::Observe(const PerformanceObserverInit& aOptions,
|
||||
}
|
||||
}
|
||||
|
||||
if (StaticPrefs::dom_enable_largest_contentful_paint()) {
|
||||
if (type == kLargestContentfulPaintName) {
|
||||
typeValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!typeValid) {
|
||||
ReportUnsupportedTypesErrorToConsole(
|
||||
NS_IsMainThread(), UnsupportedEntryTypesIgnoredMsgId, type);
|
||||
@ -328,6 +341,10 @@ void PerformanceObserver::GetSupportedEntryTypes(
|
||||
validTypes.AppendElement(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (StaticPrefs::dom_enable_largest_contentful_paint()) {
|
||||
validTypes.AppendElement(u"largest-contentful-paint"_ns);
|
||||
}
|
||||
for (const nsLiteralString& name : kValidTypeNames) {
|
||||
validTypes.AppendElement(name);
|
||||
}
|
||||
|
@ -776,6 +776,8 @@ let interfaceNamesInGlobalScope = [
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "KeyframeEffect", insecureContext: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "LargestContentfulPaint", insecureContext: true, nightly: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "Location", insecureContext: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"Lock",
|
||||
|
@ -63,6 +63,7 @@ imgRequest::imgRequest(imgLoader* aLoader, const ImageCacheKey& aCacheKey)
|
||||
mImageAvailable(false),
|
||||
mIsDeniedCrossSiteCORSRequest(false),
|
||||
mIsCrossSiteNoCORSRequest(false),
|
||||
mShouldReportRenderTimeForLCP(false),
|
||||
mMutex("imgRequest"),
|
||||
mProgressTracker(new ProgressTracker()),
|
||||
mIsMultiPartChannel(false),
|
||||
@ -641,6 +642,7 @@ imgRequest::OnStartRequest(nsIRequest* aRequest) {
|
||||
mIsCrossSiteNoCORSRequest = loadInfo->GetTainting() == LoadTainting::Opaque;
|
||||
}
|
||||
|
||||
UpdateShouldReportRenderTimeForLCP();
|
||||
// Figure out if we're multipart.
|
||||
nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
|
||||
{
|
||||
@ -1232,3 +1234,13 @@ imgRequest::OnRedirectVerifyCallback(nsresult result) {
|
||||
mRedirectCallback = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void imgRequest::UpdateShouldReportRenderTimeForLCP() {
|
||||
if (mTimedChannel) {
|
||||
bool allRedirectPassTAO = false;
|
||||
mTimedChannel->GetAllRedirectsPassTimingAllowCheck(&allRedirectPassTAO);
|
||||
mShouldReportRenderTimeForLCP =
|
||||
mTimedChannel->TimingAllowCheck(mTriggeringPrincipal) &&
|
||||
allRedirectPassTAO;
|
||||
}
|
||||
}
|
||||
|
@ -202,6 +202,10 @@ class imgRequest final : public nsIThreadRetargetableStreamListener,
|
||||
|
||||
bool IsCrossSiteNoCORSRequest() const { return mIsCrossSiteNoCORSRequest; }
|
||||
|
||||
bool ShouldReportRenderTimeForLCP() const {
|
||||
return mShouldReportRenderTimeForLCP;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class FinishPreparingForNewPartRunnable;
|
||||
|
||||
@ -209,6 +213,8 @@ class imgRequest final : public nsIThreadRetargetableStreamListener,
|
||||
|
||||
void FinishPreparingForNewPart(const NewPartResult& aResult);
|
||||
|
||||
void UpdateShouldReportRenderTimeForLCP();
|
||||
|
||||
void Cancel(nsresult aStatus);
|
||||
|
||||
// Update the cache entry size based on the image container.
|
||||
@ -276,6 +282,8 @@ class imgRequest final : public nsIThreadRetargetableStreamListener,
|
||||
bool mIsDeniedCrossSiteCORSRequest;
|
||||
bool mIsCrossSiteNoCORSRequest;
|
||||
|
||||
bool mShouldReportRenderTimeForLCP;
|
||||
|
||||
mutable mozilla::Mutex mMutex;
|
||||
|
||||
// Member variables protected by mMutex. Note that *all* flags in our bitfield
|
||||
|
@ -225,6 +225,10 @@ class imgRequestProxy : public mozilla::PreloaderBase,
|
||||
bool mHadListener : 1;
|
||||
};
|
||||
|
||||
inline nsISupports* ToSupports(imgRequestProxy* p) {
|
||||
return NS_ISUPPORTS_CAST(imgIRequest*, p);
|
||||
}
|
||||
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(imgRequestProxy, NS_IMGREQUESTPROXY_CID)
|
||||
|
||||
// Used for static image proxies for which no requests are available, so
|
||||
|
@ -11,6 +11,8 @@
|
||||
#include "Units.h"
|
||||
#include "mozilla/dom/FontFaceSet.h"
|
||||
#include "mozilla/dom/ElementBinding.h"
|
||||
#include "mozilla/dom/LargestContentfulPaint.h"
|
||||
#include "mozilla/dom/PerformanceMainThread.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/AutoRestore.h"
|
||||
@ -11839,6 +11841,13 @@ void PresShell::EndPaint() {
|
||||
}
|
||||
return CallState::Continue;
|
||||
});
|
||||
|
||||
if (nsPresContext* presContext = GetPresContext()) {
|
||||
if (PerformanceMainThread* perf =
|
||||
presContext->GetPerformanceMainThread()) {
|
||||
perf->FinalizeLCPEntriesForText();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,6 +85,7 @@ struct RangePaintInfo;
|
||||
class ReflowCountMgr;
|
||||
#endif
|
||||
class WeakFrame;
|
||||
class nsTextFrame;
|
||||
class ZoomConstraintsClient;
|
||||
|
||||
struct nsCallbackEventRequest;
|
||||
@ -113,6 +114,7 @@ class Element;
|
||||
class Event;
|
||||
class HTMLSlotElement;
|
||||
class Selection;
|
||||
class PerformanceMainThread;
|
||||
} // namespace dom
|
||||
|
||||
namespace gfx {
|
||||
|
@ -89,6 +89,7 @@
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/TimelineManager.h"
|
||||
#include "mozilla/dom/Performance.h"
|
||||
#include "mozilla/dom/PerformanceMainThread.h"
|
||||
#include "mozilla/dom/PerformanceTiming.h"
|
||||
#include "mozilla/dom/PerformancePaintTiming.h"
|
||||
#include "mozilla/layers/APZThreadUtils.h"
|
||||
@ -2781,8 +2782,16 @@ void nsPresContext::NotifyNonBlankPaint() {
|
||||
}
|
||||
}
|
||||
|
||||
bool nsPresContext::HasStoppedGeneratingLCP() const {
|
||||
if (auto* perf = GetPerformanceMainThread()) {
|
||||
return perf->HasDispatchedInputEvent() || perf->HasDispatchedScrollEvent();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void nsPresContext::NotifyContentfulPaint() {
|
||||
if (mHadFirstContentfulPaint) {
|
||||
if (mHadFirstContentfulPaint && HasStoppedGeneratingLCP()) {
|
||||
return;
|
||||
}
|
||||
nsRootPresContext* rootPresContext = GetRootPresContext();
|
||||
@ -2804,19 +2813,24 @@ void nsPresContext::NotifyContentfulPaint() {
|
||||
}
|
||||
return;
|
||||
}
|
||||
mHadFirstContentfulPaint = true;
|
||||
mFirstContentfulPaintTransactionId =
|
||||
Some(rootPresContext->mRefreshDriver->LastTransactionId().Next());
|
||||
if (nsPIDOMWindowInner* innerWindow = mDocument->GetInnerWindow()) {
|
||||
if (Performance* perf = innerWindow->GetPerformance()) {
|
||||
|
||||
if (!mHadFirstContentfulPaint) {
|
||||
mHadFirstContentfulPaint = true;
|
||||
mFirstContentfulPaintTransactionId =
|
||||
Some(rootPresContext->mRefreshDriver->LastTransactionId().Next());
|
||||
}
|
||||
|
||||
if (auto* perf = GetPerformanceMainThread()) {
|
||||
mMarkPaintTimingStart = TimeStamp::Now();
|
||||
MOZ_ASSERT(rootPresContext->RefreshDriver()->IsInRefresh(),
|
||||
"We should only notify contentful paint during refresh "
|
||||
"driver ticks");
|
||||
if (!perf->HadFCPTimingEntry()) {
|
||||
TimeStamp nowTime = rootPresContext->RefreshDriver()->MostRecentRefresh(
|
||||
/* aEnsureTimerStarted */ false);
|
||||
MOZ_ASSERT(!nowTime.IsNull(),
|
||||
"Most recent refresh timestamp should exist since we are in "
|
||||
"a refresh driver tick");
|
||||
MOZ_ASSERT(rootPresContext->RefreshDriver()->IsInRefresh(),
|
||||
"We should only notify contentful paint during refresh "
|
||||
"driver ticks");
|
||||
RefPtr<PerformancePaintTiming> paintTiming = new PerformancePaintTiming(
|
||||
perf, u"first-contentful-paint"_ns, nowTime);
|
||||
perf->SetFCPTimingEntry(paintTiming);
|
||||
@ -2833,12 +2847,15 @@ void nsPresContext::NotifyContentfulPaint() {
|
||||
nsContentUtils::TruncatedURLForDisplay(docURI).get());
|
||||
PROFILER_MARKER_TEXT(
|
||||
"FirstContentfulPaint", DOM,
|
||||
MarkerOptions(MarkerTiming::Interval(navigationStart, nowTime),
|
||||
MarkerInnerWindowId(innerWindow->WindowID())),
|
||||
MarkerOptions(
|
||||
MarkerTiming::Interval(navigationStart, nowTime),
|
||||
MarkerInnerWindowId(mDocument->GetInnerWindow()->WindowID())),
|
||||
marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
perf->ProcessElementTiming();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3050,6 +3067,16 @@ void nsPresContext::SetSafeAreaInsets(const ScreenIntMargin& aSafeAreaInsets) {
|
||||
RestyleHint::RecascadeSubtree());
|
||||
}
|
||||
|
||||
PerformanceMainThread* nsPresContext::GetPerformanceMainThread() const {
|
||||
if (nsPIDOMWindowInner* innerWindow = mDocument->GetInnerWindow()) {
|
||||
if (auto* perf = static_cast<PerformanceMainThread*>(
|
||||
innerWindow->GetPerformance())) {
|
||||
return perf;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
void nsPresContext::ValidatePresShellAndDocumentReleation() const {
|
||||
|
@ -92,6 +92,7 @@ class LayerManager;
|
||||
namespace dom {
|
||||
class Document;
|
||||
class Element;
|
||||
class PerformanceMainThread;
|
||||
enum class PrefersColorSchemeOverride : uint8_t;
|
||||
} // namespace dom
|
||||
namespace gfx {
|
||||
@ -214,6 +215,7 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
|
||||
|
||||
void DocumentCharSetChanged(NotNull<const Encoding*> aCharSet);
|
||||
|
||||
mozilla::dom::PerformanceMainThread* GetPerformanceMainThread() const;
|
||||
/**
|
||||
* Returns the parent prescontext for this one. Returns null if this is a
|
||||
* root.
|
||||
@ -1040,6 +1042,7 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
|
||||
|
||||
bool HadNonBlankPaint() const { return mHadNonBlankPaint; }
|
||||
bool HadFirstContentfulPaint() const { return mHadFirstContentfulPaint; }
|
||||
bool HasStoppedGeneratingLCP() const;
|
||||
void NotifyNonBlankPaint();
|
||||
void NotifyContentfulPaint();
|
||||
void NotifyPaintStatusReset();
|
||||
@ -1122,6 +1125,10 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
|
||||
}
|
||||
}
|
||||
|
||||
mozilla::TimeStamp GetMarkPaintTimingStart() const {
|
||||
return mMarkPaintTimingStart;
|
||||
}
|
||||
|
||||
protected:
|
||||
// May be called multiple times (unlink, destructor)
|
||||
void Destroy();
|
||||
@ -1245,6 +1252,9 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
|
||||
|
||||
mozilla::TimeStamp mReflowStartTime;
|
||||
|
||||
// Defined in https://w3c.github.io/paint-timing/#mark-paint-timing step 2.
|
||||
mozilla::TimeStamp mMarkPaintTimingStart;
|
||||
|
||||
Maybe<TransactionId> mFirstContentfulPaintTransactionId;
|
||||
|
||||
mozilla::UniquePtr<mozilla::MediaFeatureChange>
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include "nsITimer.h"
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsPresContext.h"
|
||||
#include "imgRequest.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/dom/Document.h"
|
||||
@ -52,6 +53,7 @@
|
||||
#include "nsIXULRuntime.h"
|
||||
#include "jsapi.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsTextFrame.h"
|
||||
#include "mozilla/PendingAnimationTracker.h"
|
||||
#include "mozilla/PendingFullscreenEvent.h"
|
||||
#include "mozilla/dom/PerformanceMainThread.h"
|
||||
@ -71,6 +73,7 @@
|
||||
#include "mozilla/dom/Selection.h"
|
||||
#include "mozilla/dom/VsyncMainChild.h"
|
||||
#include "mozilla/dom/WindowBinding.h"
|
||||
#include "mozilla/dom/LargestContentfulPaint.h"
|
||||
#include "mozilla/layers/WebRenderLayerManager.h"
|
||||
#include "mozilla/RestyleManager.h"
|
||||
#include "mozilla/TaskController.h"
|
||||
|
@ -143,6 +143,7 @@ EXPORTS.mozilla += [
|
||||
"CSSAlignUtils.h",
|
||||
"CSSOrderAwareFrameIterator.h",
|
||||
"LayoutMessageUtils.h",
|
||||
"nsVideoFrame.h",
|
||||
"PrintedSheetFrame.h",
|
||||
"ReflowInput.h",
|
||||
"ReflowOutput.h",
|
||||
|
@ -5582,6 +5582,8 @@ class MOZ_HEAP_CLASS WeakFrame {
|
||||
nsIFrame* operator->() { return mFrame; }
|
||||
operator nsIFrame*() { return mFrame; }
|
||||
|
||||
bool operator==(nsIFrame* const aOther) const { return mFrame == aOther; }
|
||||
|
||||
void Clear(mozilla::PresShell* aPresShell);
|
||||
|
||||
bool IsAlive() const { return !!mFrame; }
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "mozilla/dom/HTMLImageElement.h"
|
||||
#include "mozilla/dom/ReferrerInfo.h"
|
||||
#include "mozilla/dom/ResponsiveImageSelector.h"
|
||||
#include "mozilla/dom/LargestContentfulPaint.h"
|
||||
#include "mozilla/image/WebRenderImageProvider.h"
|
||||
#include "mozilla/layers/RenderRootStateManager.h"
|
||||
#include "mozilla/layers/WebRenderLayerManager.h"
|
||||
@ -1115,6 +1116,8 @@ void nsImageFrame::Notify(imgIRequest* aRequest, int32_t aType,
|
||||
}
|
||||
|
||||
if (aType == imgINotificationObserver::LOAD_COMPLETE) {
|
||||
LargestContentfulPaint::MaybeProcessImageForElementTiming(
|
||||
static_cast<imgRequestProxy*>(aRequest), GetContent()->AsElement());
|
||||
uint32_t imgStatus;
|
||||
aRequest->GetImageStatus(&imgStatus);
|
||||
nsresult status =
|
||||
@ -2365,6 +2368,13 @@ bool nsDisplayImage::CreateWebRenderCommands(
|
||||
mImage->GetImageProvider(aManager->LayerManager(), decodeSize, svgContext,
|
||||
region, flags, getter_AddRefs(provider));
|
||||
|
||||
if (nsCOMPtr<imgIRequest> currentRequest = frame->GetCurrentRequest()) {
|
||||
LCPHelpers::FinalizeLCPEntryForImage(
|
||||
frame->GetContent()->AsElement(),
|
||||
static_cast<imgRequestProxy*>(currentRequest.get()),
|
||||
GetDestRect() - ToReferenceFrame());
|
||||
}
|
||||
|
||||
// While we got a container, it may not contain a fully decoded surface. If
|
||||
// that is the case, and we have an image we were previously displaying which
|
||||
// has a fully decoded surface, then we should prefer the previous image.
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "mozilla/IntegerRange.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "mozilla/PodOperations.h"
|
||||
#include "mozilla/dom/PerformanceMainThread.h"
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsBlockFrame.h"
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "mozilla/dom/ServiceWorkerRegistration.h"
|
||||
#include "mozilla/dom/SVGElement.h"
|
||||
#include "mozilla/dom/TouchEvent.h"
|
||||
#include "mozilla/dom/PerformanceMainThread.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/PresShell.h"
|
||||
#include "mozilla/ShapeUtils.h"
|
||||
@ -1220,7 +1221,7 @@ void nsDisplayListBuilder::LeavePresShell(const nsIFrame* aReferenceFrame,
|
||||
}
|
||||
}
|
||||
nsRootPresContext* rootPresContext = pc->GetRootPresContext();
|
||||
if (!pc->HadFirstContentfulPaint() && rootPresContext) {
|
||||
if (!pc->HasStoppedGeneratingLCP() && rootPresContext) {
|
||||
if (!CurrentPresShellState()->mIsBackgroundOnly) {
|
||||
if (pc->HasEverBuiltInvisibleText() ||
|
||||
DisplayListIsContentful(this, aPaintedContents)) {
|
||||
@ -3429,6 +3430,17 @@ bool nsDisplayBackgroundImage::CreateWebRenderCommands(
|
||||
if (result == ImgDrawResult::NOT_SUPPORTED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nsIContent* content = StyleFrame()->GetContent()) {
|
||||
if (imgRequestProxy* requestProxy = mBackgroundStyle->StyleBackground()
|
||||
->mImage.mLayers[mLayer]
|
||||
.mImage.GetImageRequest()) {
|
||||
// LCP don't consider gradient backgrounds.
|
||||
LCPHelpers::FinalizeLCPEntryForImage(content->AsElement(), requestProxy,
|
||||
mBounds - ToReferenceFrame());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -7483,6 +7495,10 @@ void nsDisplayText::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
|
||||
// the WebRender fallback painting path, and we don't want to issue
|
||||
// recorded commands that are dependent on the visible/building rect.
|
||||
RenderToContext(aCtx, aBuilder, GetPaintRect(aBuilder, aCtx));
|
||||
|
||||
auto* textFrame = static_cast<nsTextFrame*>(mFrame);
|
||||
LCPTextFrameHelper::MaybeUnionTextFrame(textFrame,
|
||||
mBounds - ToReferenceFrame());
|
||||
}
|
||||
|
||||
bool nsDisplayText::CreateWebRenderCommands(
|
||||
@ -7568,6 +7584,8 @@ bool nsDisplayText::CreateWebRenderCommands(
|
||||
gfxContext* textDrawer = aBuilder.GetTextContext(aResources, aSc, aManager,
|
||||
this, bounds, deviceOffset);
|
||||
|
||||
LCPTextFrameHelper::MaybeUnionTextFrame(f, bounds - ToReferenceFrame());
|
||||
|
||||
aBuilder.StartGroup(this);
|
||||
|
||||
RenderToContext(textDrawer, aDisplayListBuilder, mVisibleRect,
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/DocumentInlines.h"
|
||||
#include "mozilla/dom/LargestContentfulPaint.h"
|
||||
#include "mozilla/dom/ImageTracker.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIReflowCallback.h"
|
||||
@ -818,10 +819,16 @@ void ImageLoader::OnLoadComplete(imgIRequest* aRequest) {
|
||||
// may happen during other network events.
|
||||
UnblockOnloadIfNeeded(fwf);
|
||||
}
|
||||
if (fwf.mFrame->StyleVisibility()->IsVisible()) {
|
||||
fwf.mFrame->SchedulePaint();
|
||||
nsIFrame* frame = fwf.mFrame;
|
||||
if (frame->StyleVisibility()->IsVisible()) {
|
||||
frame->SchedulePaint();
|
||||
}
|
||||
|
||||
if (StaticPrefs::dom_enable_largest_contentful_paint()) {
|
||||
LargestContentfulPaint::MaybeProcessImageForElementTiming(
|
||||
static_cast<imgRequestProxy*>(aRequest),
|
||||
frame->GetContent()->AsElement());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla::css
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "mozilla/SVGUtils.h"
|
||||
#include "mozilla/dom/MutationEventBinding.h"
|
||||
#include "mozilla/dom/SVGImageElement.h"
|
||||
#include "mozilla/dom/LargestContentfulPaint.h"
|
||||
#include "nsIReflowCallback.h"
|
||||
|
||||
using namespace mozilla::dom;
|
||||
@ -391,6 +392,12 @@ void SVGImageFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
|
||||
LayoutDeviceSize devPxSize(width, height);
|
||||
nsRect destRect(nsPoint(), LayoutDevicePixel::ToAppUnits(
|
||||
devPxSize, appUnitsPerDevPx));
|
||||
nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
|
||||
if (currentRequest) {
|
||||
LCPHelpers::FinalizeLCPEntryForImage(
|
||||
GetContent()->AsElement(),
|
||||
static_cast<imgRequestProxy*>(currentRequest.get()), destRect);
|
||||
}
|
||||
|
||||
// Note: Can't use DrawSingleUnscaledImage for the TYPE_VECTOR case.
|
||||
// That method needs our image to have a fixed native width & height,
|
||||
@ -467,14 +474,7 @@ bool SVGImageFrame::CreateWebRenderCommands(
|
||||
|
||||
// try to setup the image
|
||||
if (!mImageContainer) {
|
||||
nsCOMPtr<imgIRequest> currentRequest;
|
||||
nsCOMPtr<nsIImageLoadingContent> imageLoader =
|
||||
do_QueryInterface(GetContent());
|
||||
if (imageLoader) {
|
||||
imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
|
||||
getter_AddRefs(currentRequest));
|
||||
}
|
||||
|
||||
nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
|
||||
if (currentRequest) {
|
||||
currentRequest->GetImage(getter_AddRefs(mImageContainer));
|
||||
}
|
||||
@ -633,6 +633,14 @@ bool SVGImageFrame::CreateWebRenderCommands(
|
||||
mImageContainer, this, destRect, clipRect, aSc, flags, svgContext,
|
||||
region);
|
||||
|
||||
if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
|
||||
LCPHelpers::FinalizeLCPEntryForImage(
|
||||
GetContent()->AsElement(),
|
||||
static_cast<imgRequestProxy*>(currentRequest.get()),
|
||||
LayoutDeviceRect::ToAppUnits(destRect, appUnitsPerDevPx) -
|
||||
toReferenceFrame);
|
||||
}
|
||||
|
||||
RefPtr<image::WebRenderImageProvider> provider;
|
||||
ImgDrawResult drawResult = mImageContainer->GetImageProvider(
|
||||
aManager->LayerManager(), decodeSize, svgContext, region, flags,
|
||||
@ -783,6 +791,17 @@ bool SVGImageFrame::ReflowFinished() {
|
||||
|
||||
void SVGImageFrame::ReflowCallbackCanceled() { mReflowCallbackPosted = false; }
|
||||
|
||||
already_AddRefed<imgIRequest> SVGImageFrame::GetCurrentRequest() const {
|
||||
nsCOMPtr<imgIRequest> request;
|
||||
nsCOMPtr<nsIImageLoadingContent> imageLoader =
|
||||
do_QueryInterface(GetContent());
|
||||
if (imageLoader) {
|
||||
imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
|
||||
getter_AddRefs(request));
|
||||
}
|
||||
return request.forget();
|
||||
}
|
||||
|
||||
bool SVGImageFrame::IgnoreHitTest() const {
|
||||
switch (Style()->PointerEvents()) {
|
||||
case StylePointerEvents::None:
|
||||
|
@ -118,6 +118,8 @@ class SVGImageFrame final : public nsIFrame,
|
||||
private:
|
||||
bool IgnoreHitTest() const;
|
||||
|
||||
already_AddRefed<imgIRequest> GetCurrentRequest() const;
|
||||
|
||||
gfx::Matrix GetRasterImageTransform(int32_t aNativeWidth,
|
||||
int32_t aNativeHeight);
|
||||
gfx::Matrix GetVectorImageTransform();
|
||||
|
@ -2392,7 +2392,7 @@
|
||||
# Whether the LargestContentfulPaint API will be gathered and returned by performance observer*
|
||||
- name: dom.enable_largest_contentful_paint
|
||||
type: RelaxedAtomicBool
|
||||
value: false
|
||||
value: @IS_NIGHTLY_BUILD@
|
||||
mirror: always
|
||||
|
||||
# Whether performance.GetEntries* will contain an entry for the active document
|
||||
|
Loading…
Reference in New Issue
Block a user