Bug 1709577 - Check list of available images before deciding to defer a lazy load. r=edgar

As per https://html.spec.whatwg.org/#updating-the-image-data step 6.

Differential Revision: https://phabricator.services.mozilla.com/D114353
This commit is contained in:
Emilio Cobos Álvarez
2021-05-07 11:44:15 +00:00
parent f73f24cc0e
commit 6302e3acc0
10 changed files with 136 additions and 64 deletions

View File

@@ -88,15 +88,7 @@ class ImageLoadTask final : public MicroTaskRunnable {
if (mElement->mPendingImageLoadTask == this) {
mElement->mPendingImageLoadTask = nullptr;
mElement->mUseUrgentStartForChannel = mUseUrgentStartForChannel;
// Defer loading this image if loading="lazy" was set after this microtask
// was queued.
// NOTE: Using ShouldLoadImage() will violate the HTML standard spec
// because ShouldLoadImage() checks the document active state which should
// have done just once before this queue is created as per the spec, so
// we just check the lazy loading state here.
if (!mElement->IsLazyLoading()) {
mElement->LoadSelectedImage(true, true, mAlwaysLoad);
}
mElement->LoadSelectedImage(true, true, mAlwaysLoad);
}
mDocument->UnblockOnload(false);
}
@@ -342,17 +334,13 @@ nsresult HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
bool forceReload = false;
if (aName == nsGkAtoms::loading) {
if (aValue &&
static_cast<HTMLImageElement::Loading>(aValue->GetEnumValue()) ==
Loading::Lazy &&
!ImageState().HasState(NS_EVENT_STATE_LOADING)) {
if (aName == nsGkAtoms::loading &&
!ImageState().HasState(NS_EVENT_STATE_LOADING)) {
if (aValue && Loading(aValue->GetEnumValue()) == Loading::Lazy) {
SetLazyLoading();
} else if (aOldValue &&
static_cast<HTMLImageElement::Loading>(
aOldValue->GetEnumValue()) == Loading::Lazy &&
!ImageState().HasState(NS_EVENT_STATE_LOADING)) {
StopLazyLoadingAndStartLoadIfNeeded(false);
Loading(aOldValue->GetEnumValue()) == Loading::Lazy) {
StopLazyLoading(FromIntersectionObserver::No, StartLoading::Yes);
}
} else if (aName == nsGkAtoms::src && !aValue) {
// NOTE: regular src value changes are handled in AfterMaybeChangeAttr, so
@@ -489,8 +477,8 @@ void HTMLImageElement::AfterMaybeChangeAttr(
// when aNotify is true, and 2) When this function is called by
// OnAttrSetButNotChanged, SetAttrAndNotify will not subsequently call
// UpdateState.
LoadImage(aValue.String(), true, aNotify, eImageLoadType_Normal,
mSrcTriggeringPrincipal);
LoadSelectedImage(/* aForce = */ true, aNotify,
/* aAlwaysLoad = */ true);
mNewRequestsWillNeedAnimationReset = false;
}
@@ -556,8 +544,7 @@ nsresult HTMLImageElement::BindToTree(BindContext& aContext, nsINode& aParent) {
// in order to react to changes in the environment. See note of
// https://html.spec.whatwg.org/multipage/embedded-content.html#img-environment-changes
QueueImageLoadTask(false);
} else if (!InResponsiveMode() &&
HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
} else if (!InResponsiveMode() && HasAttr(nsGkAtoms::src)) {
// We skip loading when our attributes were set from parser land,
// so trigger a aForce=false load now to check if things changed.
// This isn't necessary for responsive mode, since creating the
@@ -749,8 +736,7 @@ nsresult HTMLImageElement::CopyInnerTo(HTMLImageElement* aDest) {
// doing the image load because we passed in false for aNotify. But we
// really do want it to do the load, so set it up to happen once the cloning
// reaches a stable state.
if (!aDest->InResponsiveMode() &&
aDest->HasAttr(kNameSpaceID_None, nsGkAtoms::src) &&
if (!aDest->InResponsiveMode() && aDest->HasAttr(nsGkAtoms::src) &&
aDest->ShouldLoadImage()) {
// Mark channel as urgent-start before load image if the image load is
// initaiated by a user interaction.
@@ -835,12 +821,12 @@ void HTMLImageElement::QueueImageLoadTask(bool aAlwaysLoad) {
}
bool HTMLImageElement::HaveSrcsetOrInPicture() {
if (HasAttr(kNameSpaceID_None, nsGkAtoms::srcset)) {
if (HasAttr(nsGkAtoms::srcset)) {
return true;
}
Element* parent = nsINode::GetParentElement();
return (parent && parent->IsHTMLElement(nsGkAtoms::picture));
Element* parent = GetParentElement();
return parent && parent->IsHTMLElement(nsGkAtoms::picture);
}
bool HTMLImageElement::InResponsiveMode() {
@@ -904,40 +890,47 @@ nsresult HTMLImageElement::LoadSelectedImage(bool aForce, bool aNotify,
nsresult rv = NS_ERROR_FAILURE;
nsCOMPtr<nsIURI> selectedSource;
nsCOMPtr<nsIPrincipal> triggeringPrincipal;
ImageLoadType type = eImageLoadType_Normal;
if (mResponsiveSelector) {
nsCOMPtr<nsIURI> url = mResponsiveSelector->GetSelectedImageURL();
nsCOMPtr<nsIPrincipal> triggeringPrincipal =
selectedSource = mResponsiveSelector->GetSelectedImageURL();
triggeringPrincipal =
mResponsiveSelector->GetSelectedImageTriggeringPrincipal();
selectedSource = url;
if (!aAlwaysLoad && SelectedSourceMatchesLast(selectedSource)) {
UpdateDensityOnly();
return NS_OK;
}
if (url) {
rv = LoadImage(url, aForce, aNotify, eImageLoadType_Imageset,
triggeringPrincipal);
}
type = eImageLoadType_Imageset;
} else {
nsAutoString src;
if (!GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
if (!GetAttr(nsGkAtoms::src, src) || src.IsEmpty()) {
CancelImageRequests(aNotify);
rv = NS_OK;
} else {
Document* doc = OwnerDoc();
StringToURI(src, doc, getter_AddRefs(selectedSource));
if (!aAlwaysLoad && SelectedSourceMatchesLast(selectedSource)) {
UpdateDensityOnly();
if (HaveSrcsetOrInPicture()) {
// If we have a srcset attribute or are in a <picture> element, we
// always use the Imageset load type, even if we parsed no valid
// responsive sources from either, per spec.
type = eImageLoadType_Imageset;
}
triggeringPrincipal = mSrcTriggeringPrincipal;
}
}
if (!aAlwaysLoad && SelectedSourceMatchesLast(selectedSource)) {
UpdateDensityOnly();
return NS_OK;
}
if (selectedSource) {
// Before we actually defer the lazy-loading
if (mLazyLoading) {
if (!nsContentUtils::IsImageAvailable(
this, selectedSource, triggeringPrincipal, GetCORSMode())) {
return NS_OK;
}
// If we have a srcset attribute or are in a <picture> element,
// we always use the Imageset load type, even if we parsed no
// valid responsive sources from either, per spec.
rv = LoadImage(src, aForce, aNotify,
HaveSrcsetOrInPicture() ? eImageLoadType_Imageset
: eImageLoadType_Normal,
mSrcTriggeringPrincipal);
StopLazyLoading(FromIntersectionObserver::No, StartLoading::No);
}
rv = LoadImage(selectedSource, aForce, aNotify, type, triggeringPrincipal);
}
mLastSelectedSource = selectedSource;
mCurrentDensity = currentDensity;
@@ -1147,7 +1140,7 @@ bool HTMLImageElement::TryCreateResponsiveSelector(Element* aSourceElement) {
// Skip if has no srcset or an empty srcset
nsString srcset;
if (!aSourceElement->GetAttr(kNameSpaceID_None, nsGkAtoms::srcset, srcset)) {
if (!aSourceElement->GetAttr(nsGkAtoms::srcset, srcset)) {
return false;
}
@@ -1164,14 +1157,14 @@ bool HTMLImageElement::TryCreateResponsiveSelector(Element* aSourceElement) {
}
nsAutoString sizes;
aSourceElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sizes, sizes);
aSourceElement->GetAttr(nsGkAtoms::sizes, sizes);
sel->SetSizesFromDescriptor(sizes);
// If this is the <img> tag, also pull in src as the default source
if (!isSourceTag) {
MOZ_ASSERT(aSourceElement == this);
nsAutoString src;
if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src) && !src.IsEmpty()) {
if (GetAttr(nsGkAtoms::src, src) && !src.IsEmpty()) {
sel->SetDefaultSource(src, mSrcTriggeringPrincipal);
}
}
@@ -1246,7 +1239,7 @@ void HTMLImageElement::MediaFeatureValuesChanged() {
}
bool HTMLImageElement::ShouldLoadImage() const {
return OwnerDoc()->ShouldLoadImages() && !mLazyLoading;
return OwnerDoc()->ShouldLoadImages();
}
void HTMLImageElement::SetLazyLoading() {
@@ -1279,7 +1272,7 @@ void HTMLImageElement::StartLoadingIfNeeded() {
// Use script runner for the case the adopt is from appendChild.
// Bug 1076583 - We still behave synchronously in the non-responsive case
nsContentUtils::AddScriptRunner(
(InResponsiveMode())
InResponsiveMode()
? NewRunnableMethod<bool>(
"dom::HTMLImageElement::QueueImageLoadTask", this,
&HTMLImageElement::QueueImageLoadTask, true)
@@ -1289,22 +1282,26 @@ void HTMLImageElement::StartLoadingIfNeeded() {
}
}
void HTMLImageElement::StopLazyLoadingAndStartLoadIfNeeded(
bool aFromIntersectionObserver) {
void HTMLImageElement::StopLazyLoading(
FromIntersectionObserver aFromIntersectionObserver,
StartLoading aStartLoading) {
if (!mLazyLoading) {
return;
}
mLazyLoading = false;
Document* doc = OwnerDoc();
doc->GetLazyLoadImageObserver()->Unobserve(*this);
StartLoadingIfNeeded();
if (aFromIntersectionObserver) {
if (bool(aFromIntersectionObserver)) {
doc->IncLazyLoadImageStarted();
} else {
doc->DecLazyLoadImageCount();
doc->GetLazyLoadImageObserverViewport()->Unobserve(*this);
}
if (bool(aStartLoading)) {
StartLoadingIfNeeded();
}
}
void HTMLImageElement::LazyLoadImageReachedViewport() {