Bug 1709577 - Check list of available images before deciding to defer a lazy load. r=edgar
☠☠ backed out by 1dee84afd395 ☠ ☠
authorEmilio Cobos Álvarez <emilio@crisal.io>
Fri, 07 May 2021 00:11:06 +0000
changeset 578817 bab974107b3b417bf3dc231e8d98345fb7f5b20f
parent 578816 27c129b20b9371eb03f1e4412ebb14dbaca80846
child 578818 0da1dba1749cffc285321331d3cbbedfa30de4f4
push id142729
push userealvarez@mozilla.com
push dateFri, 07 May 2021 00:32:56 +0000
treeherderautoland@0da1dba1749c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersedgar
bugs1709577
milestone90.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
dom/base/DOMIntersectionObserver.cpp
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/html/HTMLImageElement.cpp
dom/html/HTMLImageElement.h
image/imgLoader.cpp
image/imgLoader.h
testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-available.html
--- a/dom/base/DOMIntersectionObserver.cpp
+++ b/dom/base/DOMIntersectionObserver.cpp
@@ -147,17 +147,18 @@ already_AddRefed<DOMIntersectionObserver
 }
 
 static void LazyLoadCallback(
     const Sequence<OwningNonNull<DOMIntersectionObserverEntry>>& aEntries) {
   for (const auto& entry : aEntries) {
     MOZ_ASSERT(entry->Target()->IsHTMLElement(nsGkAtoms::img));
     if (entry->IsIntersecting()) {
       static_cast<HTMLImageElement*>(entry->Target())
-          ->StopLazyLoadingAndStartLoadIfNeeded(true);
+          ->StopLazyLoading(HTMLImageElement::FromIntersectionObserver::Yes,
+                            HTMLImageElement::StartLoading::Yes);
     }
   }
 }
 
 static void LazyLoadCallbackReachViewport(
     const Sequence<OwningNonNull<DOMIntersectionObserverEntry>>& aEntries) {
   for (const auto& entry : aEntries) {
     MOZ_ASSERT(entry->Target()->IsHTMLElement(nsGkAtoms::img));
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -9712,16 +9712,29 @@ void nsContentUtils::AppendNativeAnonymo
   // The root scroll frame is not the primary frame of the root element.
   // Detect and handle this case.
   if (!(aFlags & nsIContent::eSkipDocumentLevelNativeAnonymousContent) &&
       aContent == aContent->OwnerDoc()->GetRootElement()) {
     AppendDocumentLevelNativeAnonymousContentTo(aContent->OwnerDoc(), aKids);
   }
 }
 
+bool nsContentUtils::IsImageAvailable(nsIContent* aLoadingNode, nsIURI* aURI,
+                                      nsIPrincipal* aDefaultTriggeringPrincipal,
+                                      CORSMode aCORSMode) {
+  nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+  QueryTriggeringPrincipal(aLoadingNode, aDefaultTriggeringPrincipal,
+                           getter_AddRefs(triggeringPrincipal));
+  MOZ_ASSERT(triggeringPrincipal);
+
+  Document* doc = aLoadingNode->OwnerDoc();
+  imgLoader* imgLoader = GetImgLoaderForDocument(doc);
+  return imgLoader->IsImageAvailable(aURI, triggeringPrincipal, aCORSMode, doc);
+}
+
 /* static */
 bool nsContentUtils::QueryTriggeringPrincipal(
     nsIContent* aLoadingNode, nsIPrincipal* aDefaultPrincipal,
     nsIPrincipal** aTriggeringPrincipal) {
   MOZ_ASSERT(aLoadingNode);
   MOZ_ASSERT(aTriggeringPrincipal);
 
   bool result = false;
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -3080,16 +3080,24 @@ class nsContentUtils {
                                        nsIPrincipal** aTriggeringPrincipal);
 
   static bool QueryTriggeringPrincipal(nsIContent* aLoadingNode,
                                        nsIPrincipal** aTriggeringPrincipal) {
     return QueryTriggeringPrincipal(aLoadingNode, nullptr,
                                     aTriggeringPrincipal);
   }
 
+  // Returns whether the image for the given URI and triggering principal is
+  // already available. Ideally this should exactly match the "list of available
+  // images" in the HTML spec, but our implementation of that at best only
+  // resembles it.
+  static bool IsImageAvailable(nsIContent*, nsIURI*,
+                               nsIPrincipal* aDefaultTriggeringPrincipal,
+                               mozilla::CORSMode);
+
   /**
    * Returns the content policy type that should be used for loading images
    * for displaying in the UI.  The sources of such images can be <xul:image>,
    * <xul:menuitem> on OSX where we load the image through nsMenuItemIconX, etc.
    */
   static void GetContentPolicyTypeForUIImageLoading(
       nsIContent* aLoadingNode, nsIPrincipal** aTriggeringPrincipal,
       nsContentPolicyType& aContentPolicyType, uint64_t* aRequestContextID);
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -83,25 +83,17 @@ class ImageLoadTask final : public Micro
     mDocument = aElement->OwnerDoc();
     mDocument->BlockOnload();
   }
 
   void Run(AutoSlowOperation& aAso) override {
     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);
   }
 
   bool Suppressed() override {
     nsIGlobalObject* global = mElement->GetOwnerGlobal();
     return global && global->IsInSyncOperation();
   }
@@ -337,27 +329,23 @@ nsresult HTMLImageElement::AfterSetAttr(
     MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom,
                "Expected atom value for name/id");
     mForm->AddImageElementToTable(
         this, nsDependentAtomString(aValue->GetAtomValue()));
   }
 
   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
     // this only needs to handle unsetting the src attribute.
     // Mark channel as urgent-start before load image if the image load is
     // initaiated by a user interaction.
     mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
 
@@ -484,18 +472,18 @@ void HTMLImageElement::AfterMaybeChangeA
     // network if it's set to be not cacheable.
     // Potentially, false could be passed here rather than aNotify since
     // UpdateState will be called by SetAttrAndNotify, but there are two
     // obstacles to this: 1) LoadImage will end up calling
     // UpdateState(aNotify), and we do not want it to call UpdateState(false)
     // 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;
   }
 }
 
 void HTMLImageElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
   // We handle image element with attribute ismap in its corresponding frame
   // element. Set mMultipleActionsPrevented here to prevent the click event
@@ -551,18 +539,17 @@ nsresult HTMLImageElement::BindToTree(Bi
     // Mark channel as urgent-start before load image if the image load is
     // initaiated by a user interaction.
     mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
 
     // Run selection algorithm when an img element is inserted into a document
     // 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
     // image load task is asynchronous we don't need to take special
     // care to avoid doing so when being filled by the parser.
 
     // Mark channel as urgent-start before load image if the image load is
     // initaiated by a user interaction.
@@ -744,18 +731,17 @@ nsresult HTMLImageElement::CopyInnerTo(H
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // In SetAttr (called from nsGenericHTMLElement::CopyInnerTo), aDest skipped
   // 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.
     mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
 
     nsContentUtils::AddScriptRunner(
         NewRunnableMethod<bool>("dom::HTMLImageElement::MaybeLoadImage", aDest,
                                 &HTMLImageElement::MaybeLoadImage, false));
@@ -830,22 +816,22 @@ void HTMLImageElement::QueueImageLoadTas
       new ImageLoadTask(this, alwaysLoad, mUseUrgentStartForChannel);
   // The task checks this to determine if it was the last
   // queued event, and so earlier tasks are implicitly canceled.
   mPendingImageLoadTask = task;
   CycleCollectedJSContext::Get()->DispatchToMicroTask(task.forget());
 }
 
 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() {
   // When we lose srcset or leave a <picture> element, the fallback to img.src
   // will happen from the microtask, and we should behave responsively in the
   // interim
   return mResponsiveSelector || mPendingImageLoadTask ||
          HaveSrcsetOrInPicture();
@@ -899,50 +885,57 @@ nsresult HTMLImageElement::LoadSelectedI
       return NS_OK;
     }
   } else if (mResponsiveSelector) {
     currentDensity = mResponsiveSelector->GetSelectedImageDensity();
   }
 
   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;
       }
+      StopLazyLoading(FromIntersectionObserver::No, StartLoading::No);
+    }
 
-      // 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);
-    }
+    rv = LoadImage(selectedSource, aForce, aNotify, type, triggeringPrincipal);
   }
   mLastSelectedSource = selectedSource;
   mCurrentDensity = currentDensity;
 
   if (NS_FAILED(rv)) {
     CancelImageRequests(aNotify);
   }
   return rv;
@@ -1142,41 +1135,41 @@ bool HTMLImageElement::TryCreateResponsi
   } else if (aSourceElement->IsHTMLElement(nsGkAtoms::img)) {
     // Otherwise this is the <img> tag itself
     MOZ_ASSERT(aSourceElement == this);
     principal = mSrcsetTriggeringPrincipal;
   }
 
   // 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;
   }
 
   if (srcset.IsEmpty()) {
     return false;
   }
 
   // Try to parse
   RefPtr<ResponsiveImageSelector> sel =
       new ResponsiveImageSelector(aSourceElement);
   if (!sel->SetCandidatesFromSourceSet(srcset, principal)) {
     // No possible candidates, don't need to bother parsing sizes
     return false;
   }
 
   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);
     }
   }
 
   mResponsiveSelector = sel;
   return true;
 }
 
@@ -1241,17 +1234,17 @@ void HTMLImageElement::DestroyContent() 
   nsGenericHTMLElement::DestroyContent();
 }
 
 void HTMLImageElement::MediaFeatureValuesChanged() {
   QueueImageLoadTask(false);
 }
 
 bool HTMLImageElement::ShouldLoadImage() const {
-  return OwnerDoc()->ShouldLoadImages() && !mLazyLoading;
+  return OwnerDoc()->ShouldLoadImages();
 }
 
 void HTMLImageElement::SetLazyLoading() {
   if (mLazyLoading) {
     return;
   }
 
   if (!StaticPrefs::dom_image_lazy_loading_enabled()) {
@@ -1274,42 +1267,46 @@ void HTMLImageElement::SetLazyLoading() 
   UpdateImageState(true);
 }
 
 void HTMLImageElement::StartLoadingIfNeeded() {
   if (LoadingEnabled() && ShouldLoadImage()) {
     // 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)
             : NewRunnableMethod<bool>("dom::HTMLImageElement::MaybeLoadImage",
                                       this, &HTMLImageElement::MaybeLoadImage,
                                       true));
   }
 }
 
-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() {
   Document* doc = OwnerDoc();
   doc->GetLazyLoadImageObserverViewport()->Unobserve(*this);
   doc->IncLazyLoadImageReachViewport(!Complete());
 }
 
--- a/dom/html/HTMLImageElement.h
+++ b/dom/html/HTMLImageElement.h
@@ -259,17 +259,20 @@ class HTMLImageElement final : public ns
    * further <source> or <img> tags would be considered.
    */
   static bool SelectSourceForTagWithAttrs(
       Document* aDocument, bool aIsSourceTag, const nsAString& aSrcAttr,
       const nsAString& aSrcsetAttr, const nsAString& aSizesAttr,
       const nsAString& aTypeAttr, const nsAString& aMediaAttr,
       nsAString& aResult);
 
-  void StopLazyLoadingAndStartLoadIfNeeded(bool aFromIntersectionObserver);
+  enum class FromIntersectionObserver : bool { No, Yes };
+  enum class StartLoading : bool { No, Yes };
+  void StopLazyLoading(FromIntersectionObserver, StartLoading);
+
   void LazyLoadImageReachedViewport();
 
  protected:
   virtual ~HTMLImageElement();
 
   // Queues a task to run LoadSelectedImage pending stable state.
   //
   // Pending Bug 1076583 this is only used by the responsive image
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -2095,16 +2095,33 @@ static void MakeRequestStaticIfNeeded(
       proxy->GetStaticRequest(aLoadingDocument);
   if (staticProxy != proxy) {
     proxy->CancelAndForgetObserver(NS_BINDING_ABORTED);
     proxy = std::move(staticProxy);
   }
   proxy.forget(aProxyAboutToGetReturned);
 }
 
+bool imgLoader::IsImageAvailable(nsIURI* aURI,
+                                 nsIPrincipal* aTriggeringPrincipal,
+                                 CORSMode aCORSMode, Document* aDocument) {
+  ImageCacheKey key(aURI, aTriggeringPrincipal->OriginAttributesRef(),
+                    aDocument);
+  RefPtr<imgCacheEntry> entry;
+  imgCacheTable& cache = GetCache(key);
+  if (!cache.Get(key, getter_AddRefs(entry)) || !entry) {
+    return false;
+  }
+  RefPtr<imgRequest> request = entry->GetRequest();
+  if (!request) {
+    return false;
+  }
+  return ValidateCORSMode(request, false, aCORSMode, aTriggeringPrincipal);
+}
+
 nsresult imgLoader::LoadImage(
     nsIURI* aURI, nsIURI* aInitialDocumentURI, nsIReferrerInfo* aReferrerInfo,
     nsIPrincipal* aTriggeringPrincipal, uint64_t aRequestContextID,
     nsILoadGroup* aLoadGroup, imgINotificationObserver* aObserver,
     nsINode* aContext, Document* aLoadingDocument, nsLoadFlags aLoadFlags,
     nsISupports* aCacheKey, nsContentPolicyType aContentPolicyType,
     const nsAString& initiatorType, bool aUseUrgentStartForChannel,
     bool aLinkPreload, imgRequestProxy** _retval) {
--- a/image/imgLoader.h
+++ b/image/imgLoader.h
@@ -236,16 +236,19 @@ class imgLoader final : public imgILoade
    * All the same, even though what these add-ons are doing is a no-op,
    * removing the nsIServiceManager.getService method of creating/getting an
    * imgLoader objects would cause an exception in these add-ons that could
    * break things.
    */
   imgLoader();
   nsresult Init();
 
+  bool IsImageAvailable(nsIURI*, nsIPrincipal* aTriggeringPrincipal,
+                        mozilla::CORSMode, mozilla::dom::Document*);
+
   [[nodiscard]] nsresult LoadImage(
       nsIURI* aURI, nsIURI* aInitialDocumentURI, nsIReferrerInfo* aReferrerInfo,
       nsIPrincipal* aLoadingPrincipal, uint64_t aRequestContextID,
       nsILoadGroup* aLoadGroup, imgINotificationObserver* aObserver,
       nsINode* aContext, mozilla::dom::Document* aLoadingDocument,
       nsLoadFlags aLoadFlags, nsISupports* aCacheKey,
       nsContentPolicyType aContentPolicyType, const nsAString& initiatorType,
       bool aUseUrgentStartForChannel, bool aLinkPreload,
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-available.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<title>The list of available images gets checked before deciding to make a load lazy</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#update-the-image-data">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#will-lazy-load-image-steps">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1709577">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<img src="/images/green-256x256.png">
+<div style="height:1000vh;"></div>
+<script>
+promise_test(async t => {
+  await new Promise(resolve => {
+    window.addEventListener("load", resolve);
+  });
+  let nonLazy = document.querySelector("img");
+  assert_equals(nonLazy.width, 256);
+  assert_equals(nonLazy.height, 256);
+
+  let lazy = document.createElement("img");
+  lazy.loading = "lazy";
+  lazy.src = nonLazy.src;
+  document.body.appendChild(lazy);
+
+  await new Promise(resolve => setTimeout(resolve));
+
+  assert_equals(lazy.width, 256, "The list of available images should be checked before delaying the image load");
+  assert_equals(lazy.height, 256, "The list of available images should be checked before delaying the image load");
+});
+</script>