Preserve lazy loading behavior when an image is side-loaded, take two [chromium/src : main]

0 views
Skip to first unread message

Stefan Zager (Gerrit)

unread,
Dec 20, 2025, 1:46:56 AM (13 days ago) 12/20/25
to AyeAye, Dominic Farolino, Mason Freed, Chromium LUCI CQ, Menard, Alexis, chromium...@chromium.org, Nate Chapin, blink-rev...@chromium.org, apavlo...@chromium.org, blink-re...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, gavinp...@chromium.org, jmedle...@chromium.org, kinuko...@chromium.org, loading...@chromium.org
Attention needed from Dominic Farolino and Mason Freed

Stefan Zager added 4 comments

File third_party/blink/renderer/core/css/resolver/element_style_resources.cc
Line 435, Patchset 5: if (new_image && new_image->IsLazyloadPossiblyDeferred()) {
Dominic Farolino . resolved

Can you help me understand this code? When is `IsLazyloadPossiblyDeferred()` ever true here? I ask because right above, we manually set:

```
image_request_behavior = FetchParameters::ImageRequestBehavior::kNone;
```

... and pass it into `loader.Load(...)`. `StyleImageLoader::Load()` immediately delegates to `CSSImageValue::CacheImage()` which just passes the `kNone` image_request_behavior` into `CSSImageValue::PrepareFetch()`, which I guess *never* [sets the image to be lazy loaded](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/css/css_image_value.cc;l=78-81;drc=f0821f47fc30afab8eeae78ea1d689480e0d5f6a)—at least I cannot follow a path where this would ever result in a lazy loaded CSS image (I've gotta be missing something, right?).

Nevertheless, I can't see how we'd enter this block. But assuming that's possible, what are we trying to do here? It seems like we're trying to say: if synchronously after we tried to fetch this lazy loaded image, it's `IsLoaded()` is true, then that means it must've been already fetched by some other means, and we just need to finish the loading from our client's perspective (i.e., call `LoadDeferredImage()`). Is that right?

Stefan Zager

Indeed, it seems that lazy load behavior for CSS images was disabled entirely by:

https://chromium-review.googlesource.com/c/chromium/src/+/3316090

I've removed my diff from this section, added a CHECK to verify it, and added a TODO to clean up this dead code.

Line 440, Patchset 5: To<StyleFetchedImage>(new_image)->LoadDeferredImage(
Dominic Farolino . resolved

How do we know that `new_image` is a `StyleFetchedImage` specifically? Sorry, I'm just really not familiar with `element_style_resources` so this might be a dumb question.

Stefan Zager

Because `property == CSSPropertyID::kBackgroundImage` in the `case` statement above, which means that this condition will be `true`:

https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/css/resolver/element_style_resources.cc;drc=f0821f47fc30afab8eeae78ea1d689480e0d5f6a;l=109

File third_party/blink/renderer/core/loader/image_loader.cc
Line 415, Patchset 5: if (new_image_content->IsLoaded() &&
Dominic Farolino . resolved

Which caller of this method do we expect to pass in `new_image_content` that `IsLoaded()` already?

The only one that makes sense to me is the [callsite in `DoUpdateFromElement()`](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;l=619;drc=f0821f47fc30afab8eeae78ea1d689480e0d5f6a) which ordinarily will be called with an ImageResourceContent whose content status is `kPending`, not `kCached`, IIUC. My understanding is that this would only be `kCached` here (and thus `IsLoaded()`) if we were loading an image whose underlying resource was already fetched beforehand, and cached.

But that's a different case than what's described in the HTML Standard issue about an earlier-lazy-loaded `<img>` (that's currently paused) suddenly being freed up because the same resource happened to be loaded elsewhere. Can you help me understand how it relates?

Stefan Zager

Here is the [call stack](https://screenshot.googleplex.com/CT3fMajmVPYHQV9). A possible scenario is that an `<img>` has `loading="lazy"` and subsequently its `src` attribute is changed to a URL that is in the cache. Prior to this CL, we would proceed to (incorrectly) fire the `load` event for the `<img>` immediately.

Line 800, Patchset 5: delay_until_image_notify_finished_ = nullptr;
Dominic Farolino . resolved

I wonder if we should just prevent the creation of this object in https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;l=771-772;drc=f0821f47fc30afab8eeae78ea1d689480e0d5f6a specifically for lazyload-deferred images, since I don't think they should ever block the load event, right? In fact, today lazy loaded images don't block the load event when they eventually load, so why is this actually necessary? Is the edge case for lazy loaded images that were loaded out of band before the document load event is fired—then `ImageChanged()` is called because the original `<img>` and the side-loaded one share the same underlying resource / observer chain, and then we accidentally end up creating a `delay_until_image_notify_finished_` object for these images?

Stefan Zager

Yes, I assume that's how `delay_until_image_notify_finished_` gets vivified. I don't know this code well enough to be confident that modifying `ImageLoader::ImageChanged` is the right thing to do. so I opted instead for a more localized.

Open in Gerrit

Related details

Attention is currently required from:
  • Dominic Farolino
  • Mason Freed
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: I7b2513dd9943fbbea2094138716d1ea98400665c
Gerrit-Change-Number: 7274640
Gerrit-PatchSet: 7
Gerrit-Owner: Stefan Zager <sza...@chromium.org>
Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
Gerrit-Reviewer: Mason Freed <mas...@chromium.org>
Gerrit-Reviewer: Stefan Zager <sza...@chromium.org>
Gerrit-CC: Menard, Alexis <alexis...@intel.com>
Gerrit-CC: Nate Chapin <jap...@chromium.org>
Gerrit-Attention: Mason Freed <mas...@chromium.org>
Gerrit-Attention: Dominic Farolino <d...@chromium.org>
Gerrit-Comment-Date: Sat, 20 Dec 2025 06:46:46 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Dominic Farolino <d...@chromium.org>
satisfied_requirement
unsatisfied_requirement
open
diffy

Stefan Zager (Gerrit)

unread,
Dec 20, 2025, 5:37:54 PM (12 days ago) 12/20/25
to AyeAye, Dominic Farolino, Mason Freed, Chromium LUCI CQ, Menard, Alexis, chromium...@chromium.org, Nate Chapin, blink-rev...@chromium.org, apavlo...@chromium.org, blink-re...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, gavinp...@chromium.org, jmedle...@chromium.org, kinuko...@chromium.org, loading...@chromium.org
Attention needed from Dominic Farolino and Mason Freed

Stefan Zager added 1 comment

File third_party/blink/renderer/core/loader/image_loader.cc
Line 800, Patchset 5: delay_until_image_notify_finished_ = nullptr;
Dominic Farolino . resolved

I wonder if we should just prevent the creation of this object in https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;l=771-772;drc=f0821f47fc30afab8eeae78ea1d689480e0d5f6a specifically for lazyload-deferred images, since I don't think they should ever block the load event, right? In fact, today lazy loaded images don't block the load event when they eventually load, so why is this actually necessary? Is the edge case for lazy loaded images that were loaded out of band before the document load event is fired—then `ImageChanged()` is called because the original `<img>` and the side-loaded one share the same underlying resource / observer chain, and then we accidentally end up creating a `delay_until_image_notify_finished_` object for these images?

Stefan Zager

Yes, I assume that's how `delay_until_image_notify_finished_` gets vivified. I don't know this code well enough to be confident that modifying `ImageLoader::ImageChanged` is the right thing to do. so I opted instead for a more localized.

Stefan Zager

I went ahead and made the change you suggested, to not populate `delay_until_image_notify_finished_` for a lazy-loaded image.

Open in Gerrit

Related details

Attention is currently required from:
  • Dominic Farolino
  • Mason Freed
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: I7b2513dd9943fbbea2094138716d1ea98400665c
Gerrit-Change-Number: 7274640
Gerrit-PatchSet: 8
Gerrit-Owner: Stefan Zager <sza...@chromium.org>
Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
Gerrit-Reviewer: Mason Freed <mas...@chromium.org>
Gerrit-Reviewer: Stefan Zager <sza...@chromium.org>
Gerrit-CC: Menard, Alexis <alexis...@intel.com>
Gerrit-CC: Nate Chapin <jap...@chromium.org>
Gerrit-Attention: Mason Freed <mas...@chromium.org>
Gerrit-Attention: Dominic Farolino <d...@chromium.org>
Gerrit-Comment-Date: Sat, 20 Dec 2025 22:37:42 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Dominic Farolino <d...@chromium.org>
Comment-In-Reply-To: Stefan Zager <sza...@chromium.org>
satisfied_requirement
unsatisfied_requirement
open
diffy

Dominic Farolino (Gerrit)

unread,
Dec 20, 2025, 9:55:23 PM (12 days ago) 12/20/25
to Stefan Zager, AyeAye, Mason Freed, Chromium LUCI CQ, Menard, Alexis, chromium...@chromium.org, Nate Chapin, blink-rev...@chromium.org, apavlo...@chromium.org, blink-re...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, gavinp...@chromium.org, jmedle...@chromium.org, kinuko...@chromium.org, loading...@chromium.org
Attention needed from Mason Freed and Stefan Zager

Dominic Farolino added 2 comments

File third_party/blink/renderer/core/css/resolver/element_style_resources.cc
Line 435, Patchset 5: if (new_image && new_image->IsLazyloadPossiblyDeferred()) {
Dominic Farolino . resolved

Can you help me understand this code? When is `IsLazyloadPossiblyDeferred()` ever true here? I ask because right above, we manually set:

```
image_request_behavior = FetchParameters::ImageRequestBehavior::kNone;
```

... and pass it into `loader.Load(...)`. `StyleImageLoader::Load()` immediately delegates to `CSSImageValue::CacheImage()` which just passes the `kNone` image_request_behavior` into `CSSImageValue::PrepareFetch()`, which I guess *never* [sets the image to be lazy loaded](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/css/css_image_value.cc;l=78-81;drc=f0821f47fc30afab8eeae78ea1d689480e0d5f6a)—at least I cannot follow a path where this would ever result in a lazy loaded CSS image (I've gotta be missing something, right?).

Nevertheless, I can't see how we'd enter this block. But assuming that's possible, what are we trying to do here? It seems like we're trying to say: if synchronously after we tried to fetch this lazy loaded image, it's `IsLoaded()` is true, then that means it must've been already fetched by some other means, and we just need to finish the loading from our client's perspective (i.e., call `LoadDeferredImage()`). Is that right?

Stefan Zager

Indeed, it seems that lazy load behavior for CSS images was disabled entirely by:

https://chromium-review.googlesource.com/c/chromium/src/+/3316090

I've removed my diff from this section, added a CHECK to verify it, and added a TODO to clean up this dead code.

Dominic Farolino

Great, thanks a lot!

File third_party/blink/renderer/core/loader/image_loader.cc
Line 415, Patchset 5: if (new_image_content->IsLoaded() &&
Dominic Farolino . unresolved

Which caller of this method do we expect to pass in `new_image_content` that `IsLoaded()` already?

The only one that makes sense to me is the [callsite in `DoUpdateFromElement()`](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;l=619;drc=f0821f47fc30afab8eeae78ea1d689480e0d5f6a) which ordinarily will be called with an ImageResourceContent whose content status is `kPending`, not `kCached`, IIUC. My understanding is that this would only be `kCached` here (and thus `IsLoaded()`) if we were loading an image whose underlying resource was already fetched beforehand, and cached.

But that's a different case than what's described in the HTML Standard issue about an earlier-lazy-loaded `<img>` (that's currently paused) suddenly being freed up because the same resource happened to be loaded elsewhere. Can you help me understand how it relates?

Stefan Zager

Here is the [call stack](https://screenshot.googleplex.com/CT3fMajmVPYHQV9). A possible scenario is that an `<img>` has `loading="lazy"` and subsequently its `src` attribute is changed to a URL that is in the cache. Prior to this CL, we would proceed to (incorrectly) fire the `load` event for the `<img>` immediately.

Dominic Farolino

Prior to this CL, we would proceed to (incorrectly) fire the load event for the <img> immediately.

No I think that would be correct behavior. This is what I raised in https://chromium-review.googlesource.com/c/chromium/src/+/7240346/comment/2c7bedbe_3fcc964e/ (see scenario (2), and the very last sentence of that reply).

Per spec, the "list of available images" [is consulted](https://html.spec.whatwg.org/#updating-the-image-data:list-of-available-images) [*before* the `loading` attribute is considered](https://html.spec.whatwg.org/#updating-the-image-data:will-lazy-load-element-steps). Our implementation correctly does this via a [fast-path from `UpdateFromElement()` => `DoUpdateFromElement()`](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;l=684-690;drc=ec789581318d16a04973f11a08ddf2200140a3e0), IF `ShouldLoadImmediately()` is true, which contains [a check with the list of available images](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;l=747-751;drc=ec789581318d16a04973f11a08ddf2200140a3e0). I don't think we want to change that.

Open in Gerrit

Related details

Attention is currently required from:
  • Mason Freed
  • Stefan Zager
Submit Requirements:
    • requirement satisfiedCode-Coverage
    • requirement satisfiedCode-Owners
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: chromium/src
    Gerrit-Branch: main
    Gerrit-Change-Id: I7b2513dd9943fbbea2094138716d1ea98400665c
    Gerrit-Change-Number: 7274640
    Gerrit-PatchSet: 8
    Gerrit-Owner: Stefan Zager <sza...@chromium.org>
    Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
    Gerrit-Reviewer: Mason Freed <mas...@chromium.org>
    Gerrit-Reviewer: Stefan Zager <sza...@chromium.org>
    Gerrit-CC: Menard, Alexis <alexis...@intel.com>
    Gerrit-CC: Nate Chapin <jap...@chromium.org>
    Gerrit-Attention: Mason Freed <mas...@chromium.org>
    Gerrit-Attention: Stefan Zager <sza...@chromium.org>
    Gerrit-Comment-Date: Sun, 21 Dec 2025 02:55:16 +0000
    satisfied_requirement
    unsatisfied_requirement
    open
    diffy

    Stefan Zager (Gerrit)

    unread,
    Dec 20, 2025, 11:41:27 PM (12 days ago) 12/20/25
    to AyeAye, Dominic Farolino, Mason Freed, Chromium LUCI CQ, Menard, Alexis, chromium...@chromium.org, Nate Chapin, blink-rev...@chromium.org, apavlo...@chromium.org, blink-re...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, gavinp...@chromium.org, jmedle...@chromium.org, kinuko...@chromium.org, loading...@chromium.org
    Attention needed from Dominic Farolino and Mason Freed

    Stefan Zager added 1 comment

    File third_party/blink/renderer/core/loader/image_loader.cc
    Line 415, Patchset 5: if (new_image_content->IsLoaded() &&
    Dominic Farolino . unresolved

    Which caller of this method do we expect to pass in `new_image_content` that `IsLoaded()` already?

    The only one that makes sense to me is the [callsite in `DoUpdateFromElement()`](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;l=619;drc=f0821f47fc30afab8eeae78ea1d689480e0d5f6a) which ordinarily will be called with an ImageResourceContent whose content status is `kPending`, not `kCached`, IIUC. My understanding is that this would only be `kCached` here (and thus `IsLoaded()`) if we were loading an image whose underlying resource was already fetched beforehand, and cached.

    But that's a different case than what's described in the HTML Standard issue about an earlier-lazy-loaded `<img>` (that's currently paused) suddenly being freed up because the same resource happened to be loaded elsewhere. Can you help me understand how it relates?

    Stefan Zager

    Here is the [call stack](https://screenshot.googleplex.com/CT3fMajmVPYHQV9). A possible scenario is that an `<img>` has `loading="lazy"` and subsequently its `src` attribute is changed to a URL that is in the cache. Prior to this CL, we would proceed to (incorrectly) fire the `load` event for the `<img>` immediately.

    Dominic Farolino

    Prior to this CL, we would proceed to (incorrectly) fire the load event for the <img> immediately.

    No I think that would be correct behavior. This is what I raised in https://chromium-review.googlesource.com/c/chromium/src/+/7240346/comment/2c7bedbe_3fcc964e/ (see scenario (2), and the very last sentence of that reply).

    Per spec, the "list of available images" [is consulted](https://html.spec.whatwg.org/#updating-the-image-data:list-of-available-images) [*before* the `loading` attribute is considered](https://html.spec.whatwg.org/#updating-the-image-data:will-lazy-load-element-steps). Our implementation correctly does this via a [fast-path from `UpdateFromElement()` => `DoUpdateFromElement()`](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;l=684-690;drc=ec789581318d16a04973f11a08ddf2200140a3e0), IF `ShouldLoadImmediately()` is true, which contains [a check with the list of available images](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;l=747-751;drc=ec789581318d16a04973f11a08ddf2200140a3e0). I don't think we want to change that.

    Stefan Zager

    Sorry, I explained that incorrectly. This code path *does* fire the load event immediately, both before and after this CL.

    Because `IsLoaded()` is true, `NotifyFinished()` will be called synchronously from `DoUpdateElement()` in this call stack, via the call to `AddObserver()` ([stack](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;drc=ec789581318d16a04973f11a08ddf2200140a3e0;l=619)). Prior to this patch, `lazy_image_load_state_` would still be `kDeferred` at that time, but `ImageNotifyFinished()` didn't have an early return clause, so we would proceed through the rest of that method and fire the `load` event. With this patch, `lazy_image_load_state_` will be `kFullImage`, so we skip the early-return clause and fire the `load` event just as before.

    The early-return clause in `ImageNotifyFinished()` handles the case where it's called while `lazy_image_load_state_` is still `kDeferred`, which is the case for a `loading="lazy"` image that is being monitored for viewport intersection. In that case, we now early-return. When the `LazyLoadImageObserver` signals that the image should be loaded, it updates `lazy_load_image_state_` [here](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;drc=ec789581318d16a04973f11a08ddf2200140a3e0;l=1015), which ensures that a subsequent call to `ImageNotifyFinished()` will not early-return.

    Open in Gerrit

    Related details

    Attention is currently required from:
    • Dominic Farolino
    • Mason Freed
    Submit Requirements:
    • requirement satisfiedCode-Coverage
    • requirement satisfiedCode-Owners
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: chromium/src
    Gerrit-Branch: main
    Gerrit-Change-Id: I7b2513dd9943fbbea2094138716d1ea98400665c
    Gerrit-Change-Number: 7274640
    Gerrit-PatchSet: 8
    Gerrit-Owner: Stefan Zager <sza...@chromium.org>
    Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
    Gerrit-Reviewer: Mason Freed <mas...@chromium.org>
    Gerrit-Reviewer: Stefan Zager <sza...@chromium.org>
    Gerrit-CC: Menard, Alexis <alexis...@intel.com>
    Gerrit-CC: Nate Chapin <jap...@chromium.org>
    Gerrit-Attention: Mason Freed <mas...@chromium.org>
    Gerrit-Attention: Dominic Farolino <d...@chromium.org>
    Gerrit-Comment-Date: Sun, 21 Dec 2025 04:41:17 +0000
    satisfied_requirement
    unsatisfied_requirement
    open
    diffy

    Stefan Zager (Gerrit)

    unread,
    Dec 21, 2025, 12:07:02 AM (12 days ago) 12/21/25
    to AyeAye, Dominic Farolino, Mason Freed, Chromium LUCI CQ, Menard, Alexis, chromium...@chromium.org, Nate Chapin, blink-rev...@chromium.org, apavlo...@chromium.org, blink-re...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, gavinp...@chromium.org, jmedle...@chromium.org, kinuko...@chromium.org, loading...@chromium.org
    Attention needed from Dominic Farolino and Mason Freed

    Stefan Zager added 1 comment

    File third_party/blink/renderer/core/loader/image_loader.cc
    Line 415, Patchset 5: if (new_image_content->IsLoaded() &&
    Dominic Farolino . unresolved

    Which caller of this method do we expect to pass in `new_image_content` that `IsLoaded()` already?

    The only one that makes sense to me is the [callsite in `DoUpdateFromElement()`](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;l=619;drc=f0821f47fc30afab8eeae78ea1d689480e0d5f6a) which ordinarily will be called with an ImageResourceContent whose content status is `kPending`, not `kCached`, IIUC. My understanding is that this would only be `kCached` here (and thus `IsLoaded()`) if we were loading an image whose underlying resource was already fetched beforehand, and cached.

    But that's a different case than what's described in the HTML Standard issue about an earlier-lazy-loaded `<img>` (that's currently paused) suddenly being freed up because the same resource happened to be loaded elsewhere. Can you help me understand how it relates?

    Stefan Zager

    Here is the [call stack](https://screenshot.googleplex.com/CT3fMajmVPYHQV9). A possible scenario is that an `<img>` has `loading="lazy"` and subsequently its `src` attribute is changed to a URL that is in the cache. Prior to this CL, we would proceed to (incorrectly) fire the `load` event for the `<img>` immediately.

    Dominic Farolino

    Prior to this CL, we would proceed to (incorrectly) fire the load event for the <img> immediately.

    No I think that would be correct behavior. This is what I raised in https://chromium-review.googlesource.com/c/chromium/src/+/7240346/comment/2c7bedbe_3fcc964e/ (see scenario (2), and the very last sentence of that reply).

    Per spec, the "list of available images" [is consulted](https://html.spec.whatwg.org/#updating-the-image-data:list-of-available-images) [*before* the `loading` attribute is considered](https://html.spec.whatwg.org/#updating-the-image-data:will-lazy-load-element-steps). Our implementation correctly does this via a [fast-path from `UpdateFromElement()` => `DoUpdateFromElement()`](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;l=684-690;drc=ec789581318d16a04973f11a08ddf2200140a3e0), IF `ShouldLoadImmediately()` is true, which contains [a check with the list of available images](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;l=747-751;drc=ec789581318d16a04973f11a08ddf2200140a3e0). I don't think we want to change that.

    Stefan Zager

    Sorry, I explained that incorrectly. This code path *does* fire the load event immediately, both before and after this CL.

    Because `IsLoaded()` is true, `NotifyFinished()` will be called synchronously from `DoUpdateElement()` in this call stack, via the call to `AddObserver()` ([stack](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;drc=ec789581318d16a04973f11a08ddf2200140a3e0;l=619)). Prior to this patch, `lazy_image_load_state_` would still be `kDeferred` at that time, but `ImageNotifyFinished()` didn't have an early return clause, so we would proceed through the rest of that method and fire the `load` event. With this patch, `lazy_image_load_state_` will be `kFullImage`, so we skip the early-return clause and fire the `load` event just as before.

    The early-return clause in `ImageNotifyFinished()` handles the case where it's called while `lazy_image_load_state_` is still `kDeferred`, which is the case for a `loading="lazy"` image that is being monitored for viewport intersection. In that case, we now early-return. When the `LazyLoadImageObserver` signals that the image should be loaded, it updates `lazy_load_image_state_` [here](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;drc=ec789581318d16a04973f11a08ddf2200140a3e0;l=1015), which ensures that a subsequent call to `ImageNotifyFinished()` will not early-return.

    Stefan Zager

    Here's the [call stack](https://screenshot.googleplex.com/6hzv5shQREaNW8f) for `ImageNotifyFinished()` being called when the image resource finishes loading, without going through `DoUpdateElement()`.

    Gerrit-Comment-Date: Sun, 21 Dec 2025 05:06:52 +0000
    satisfied_requirement
    unsatisfied_requirement
    open
    diffy
    Reply all
    Reply to author
    Forward
    0 new messages