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

1 view
Skip to first unread message

Stefan Zager (Gerrit)

unread,
Dec 20, 2025, 1:46:56 AM12/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 PM12/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 PM12/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 PM12/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 AM12/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

    Stefan Zager (Gerrit)

    unread,
    Jan 5, 2026, 10:29:12 AMJan 5
    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

    Patchset-level comments
    File-level comment, Patchset 8 (Latest):
    Stefan Zager . resolved

    Ping

    Gerrit-Comment-Date: Mon, 05 Jan 2026 15:28:58 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    satisfied_requirement
    unsatisfied_requirement
    open
    diffy

    Mason Freed (Gerrit)

    unread,
    Jan 5, 2026, 12:00:17 PMJan 5
    to Stefan Zager, AyeAye, Dominic Farolino, 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 Stefan Zager

    Mason Freed voted and added 2 comments

    Votes added by Mason Freed

    Code-Review+1

    2 comments

    Patchset-level comments
    Mason Freed . resolved

    Still LGTM from my point of view. Needs resolution of the open comment though.

    File third_party/blink/renderer/core/style/style_image.h
    Line 184, Patchset 8 (Latest): // TODO: This can never be true after:
    Mason Freed . unresolved

    nit: maybe file a bug to do this cleanup?

    Open in Gerrit

    Related details

    Attention is currently required from:
    • Dominic Farolino
    • Stefan Zager
    Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement 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: Dominic Farolino <d...@chromium.org>
      Gerrit-Attention: Stefan Zager <sza...@chromium.org>
      Gerrit-Comment-Date: Mon, 05 Jan 2026 17:00:03 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: Yes
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Stefan Zager (Gerrit)

      unread,
      Jan 5, 2026, 12:44:13 PMJan 5
      to Mason Freed, AyeAye, Dominic Farolino, 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

      Stefan Zager added 1 comment

      File third_party/blink/renderer/core/style/style_image.h
      Line 184, Patchset 8: // TODO: This can never be true after:
      Mason Freed . resolved

      nit: maybe file a bug to do this cleanup?

      Stefan Zager

      Done

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Dominic Farolino
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement 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: Dominic Farolino <d...@chromium.org>
      Gerrit-Comment-Date: Mon, 05 Jan 2026 17:44:03 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      Comment-In-Reply-To: Mason Freed <mas...@chromium.org>
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Dominic Farolino (Gerrit)

      unread,
      Jan 6, 2026, 11:19:21 AMJan 6
      to Stefan Zager, Mason Freed, AyeAye, 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 Stefan Zager

      Dominic Farolino 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()`.

      Dominic Farolino

      OK I think I get what you're saying, but let me make sure I fully understand. You're saying that prior to this patch, a hung lazy loaded image could have its `ImageNotifyFinished()` method called via a totally separate (side-loaded, we're calling it) image request for the same underlying resource. When that second side-loaded image request fulfills, the hung lazy loaded image's `ImageNotifyFinished()` is called due to:

      ```
      ImageResourceContent::UpdateImage() <-- the side-loaded image
      ImageResourceContent::NotifyObservers(kShouldNotifyFinish)
      ImageResourceContent::HandleObserverFinished()
      ImageLoader::ImageNotifyFinished() <-- Deferred ImageLoader gets "finished"
      ```

      To restate it, the deferred ImageLoader gets notify-finished through the process of loading a non-deferred image that targets the same blink::Resource that the deferred image loader is an observer of. Before this CL, `ImageNotifyFinished()` has no early-return, so the deferred image's load event would mysteriously fire due to the side-loading. Is this correct? If so, can you please add these details to the CL description?

      ----

      All of the above makes sense to me, and it explains the changes in `ImageNotifyFinished()`, but I'm still not totally seeing the need for `EnqueueImageLoadingMicroTask()` here. Maybe it's something like this:

      When you try and lazy-load an image that's already been loaded via other means, we go through this path:

      ```
      ImageLoader::UpdateFromElement()
      ImageLoader::DoUpdateFromElement() <-- syncly, bc of `ShouldLoadImmediately()`
      * Set new image content to content whose `IsLoaded()` is true
      ImageLoader::UpdateImageState(new_image_content)
      * This NOW queues a microtask to re-enter DoUpdateFromElement() later
      ImageResourceContent::AddObserver() <-- i.e., new_image_content->AddObserver()
      ImageResourceContent::HandleObserverFinished()
      ImageLoader::ImageNotifyFinished()
      * This now early-returns, which is why we had to re-queue the
      * microtask earlier
      ```

      Is this why we have to re-queue the image loading steps here? In anticipation of `ImageNotifyFinished()` (which gets called shortly after this method) now early returning instead of proceeding to fire the load event? Actually, this doesn't make sense to me, since the call to `ImageNotifyFinished()` that happens synchronously after `DoUpdateFromElement() -> UpdateImageState()` shouldn't hit the early-return path anyways, since `UpdateImageState()` sets `lazy_image_load_state_ = LazyImageLoadState::kFullImage`, thus avoiding the early-return path. I'm confused.

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Stefan Zager
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement 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: 9
      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: Stefan Zager <sza...@chromium.org>
      Gerrit-Comment-Date: Tue, 06 Jan 2026 16:19:11 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Stefan Zager (Gerrit)

      unread,
      Jan 6, 2026, 2:51:43 PMJan 6
      to Mason Freed, AyeAye, Dominic Farolino, 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

      Stefan Zager added 1 comment

      File third_party/blink/renderer/core/loader/image_loader.cc
      Stefan Zager

      That's the right mental model for this change, and I updated the commit message with a description.

      After `lazy_image_load_state_` is modified, we need a call to `DoUpdateFromElement()` to fully propagate the new state. The reason for calling `EnqueueImageLoadingMicroTask()` is to avoid recursion into `DoUpdateFromElement()`, which I think would add risk and complexity.

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Dominic Farolino
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement 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: 11
      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: Dominic Farolino <d...@chromium.org>
      Gerrit-Comment-Date: Tue, 06 Jan 2026 19:51:32 +0000
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Dominic Farolino (Gerrit)

      unread,
      Jan 6, 2026, 3:20:28 PMJan 6
      to Stefan Zager, Mason Freed, AyeAye, 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 Stefan Zager

      Dominic Farolino added 1 comment

      File third_party/blink/renderer/core/loader/image_loader.cc
      Dominic Farolino

      What does "fully propagate the new state" mean exactly though? Shouldn't the call to `AddObserver() -> ImageNotifyFinished()` that happens after `UpdateImageState()` change the status from deferred to fully loaded result in the load event being called, and the new image being loaded? If not, what happens instead? (I agree we don't want to recurse directly into `DoUpdateFromElement()`).

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Stefan Zager
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement 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: 11
      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: Stefan Zager <sza...@chromium.org>
      Gerrit-Comment-Date: Tue, 06 Jan 2026 20:20:18 +0000
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Stefan Zager (Gerrit)

      unread,
      Jan 6, 2026, 4:43:21 PMJan 6
      to Mason Freed, AyeAye, Dominic Farolino, 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

      Stefan Zager added 1 comment

      File third_party/blink/renderer/core/loader/image_loader.cc
      Stefan Zager

      Currently, when a lazy-loaded element enters the viewport, `lazy_image_load_state_` is updated and then `DoUpdateFromElement` is scheduled via micro-task:

      https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;drc=5f5478c22e63ec0a3effbdc82c9fac2615690334;l=1015

      It's not clear to me what the effect would be if `lazy_image_load_state_` were to transition to `kFullyLoaded` in the *middle* of an invocation of `DoUpdateFromElement()`. `lazy_image_load_state_` is referenced in various places of the `ImageLoader` code, and I don't know exactly what parts of that code are reachable from `DoUpdateFromElement()`, which enapsulates a lot of hairy logic with large fanout.

      My mental model for this change is basically "do the same thing that LazyLoadImageObserver would do if the element entered the viewport." That seems like the safest thing to do.

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Dominic Farolino
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement 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: 11
      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: Dominic Farolino <d...@chromium.org>
      Gerrit-Comment-Date: Tue, 06 Jan 2026 21:43:09 +0000
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Dominic Farolino (Gerrit)

      unread,
      Jan 6, 2026, 5:20:48 PMJan 6
      to Stefan Zager, Mason Freed, AyeAye, 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 Stefan Zager

      Dominic Farolino added 1 comment

      File third_party/blink/renderer/core/loader/image_loader.cc
      Dominic Farolino

      It's not clear to me what the effect would be if `lazy_image_load_state_` were to transition to `kFullyLoaded` in the _middle_ of an invocation of `DoUpdateFromElement()`.

      That's a little scary to me, since that's exactly what L418 below does. I'm not sure that we need to re-enter the loading process (asyncly) from here, especially since we continue normally just after this method. What happens if we avoid the microtask here (delete L419) and keep L418 where we transition to kFullImage? When `DoUpdateFromElement()` calls `AddObserver()`, we'd still hit `ImageNotifyFinished()` synchronously with `DoUpdateFromElement()` on the stack, and we'd skip the early-return you added there, and we'd still queue the load event, right? Does everything pass with that change? If so, I'd be more comfortable with that.

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Stefan Zager
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement 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: 11
      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: Stefan Zager <sza...@chromium.org>
      Gerrit-Comment-Date: Tue, 06 Jan 2026 22:20:39 +0000
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Stefan Zager (Gerrit)

      unread,
      Jan 6, 2026, 6:47:25 PMJan 6
      to Mason Freed, AyeAye, Dominic Farolino, 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

      Stefan Zager added 1 comment

      File third_party/blink/renderer/core/loader/image_loader.cc
      Stefan Zager

      I'll try your suggestion, but to offer a counterpoint:

      `UpdateFromElement()` is designed to run as an isolated task and fully update everything to a consistent state. There is no harm in running it multiple times; and in fact, it is called redundantly/unnecessarily many times during normal operation. The conventional wisdom seems to be: when in doubt, run `UpdateFromElement()` and everything will be OK. From that perspective, scheduling `UpdateFromElement()` after modifying `lazy_image_load_state_` is the conservative and safe thing to do.

      It is possible that updating `lazy_image_load_state_` here could leave things in an inconsistent state. However, we may not have test coverage for such a situation. Or the inconsistent state may be hidden/camouflaged because we spam `UpdateFromElement()` so much from elsewhere in the code.

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Dominic Farolino
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement 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: 11
      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: Dominic Farolino <d...@chromium.org>
      Gerrit-Comment-Date: Tue, 06 Jan 2026 23:47:12 +0000
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Hiroshige Hayashizaki (Gerrit)

      unread,
      Jan 7, 2026, 5:08:46 AMJan 7
      to Stefan Zager, Mason Freed, AyeAye, Dominic Farolino, 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 Stefan Zager

      Hiroshige Hayashizaki added 9 comments

      Commit Message
      Line 32, Patchset 12 (Latest):steps.
      Hiroshige Hayashizaki . unresolved

      IIUC, This CL is to make
      `lazy_image_load_state_ == LazyImageLoadState::kDeferred`
      to correspond with the spec state where the image element is waiting for intersection observer at Step 25 of
      https://html.spec.whatwg.org/#update-the-image-data, and fixes multiple behavior bugs (where I commented separately).

      Am I understanding correctly? If so, we should:

      • Update the comments at `LazyImageLoadState`,
      • Update the commit message to mention the multiple behavior changes.
      File third_party/blink/renderer/core/css/resolver/element_style_resources.cc
      Line 435, Patchset 12 (Latest): CHECK(!new_image || !new_image->IsLazyloadPossiblyDeferred());
      Hiroshige Hayashizaki . unresolved

      Just to check: Is this part (and `third_party/blink/renderer/core/style/style_image.h`) related to this change?
      Should we do these separately?

      (Because this change is not guarded by the feature so I suspect this is separate)

      File third_party/blink/renderer/core/loader/image_loader.cc
      Line 414, Patchset 12 (Latest): if (lazy_image_load_state_ == LazyImageLoadState::kDeferred) {
      Hiroshige Hayashizaki . unresolved

      Fix #4. Exempt the list-of-available-images hit cases from Fix #3.

      (I comment the details separately)

      Is this covered by a WPT? If not, could you add one?

      Hiroshige Hayashizaki

      I'm a little concerned with re-triggering the loading process (e.g. calling `EnqueueImageLoadingMicroTask()` here in the previous patch set) because of risk of infinite loop, so the current patch set 12 looks good to me.

      Also, similar code is already around Line 410 (while Line 410 is anyway for error cases and thus I don't expect errorenous behavior at Line 410 is caught by real users though).

      As far as I briefly looked at, changing `lazy_image_load_state_` to `kNone` as I suggested below (or `kFullImage` as-is in the patch set 12) is probably fine:
      It's more like we first entering `LazyImageLoadState::kDeferred` and then reverting it back to `kNone`, more like as if the image has been staying at `kNone` state.
      The primary (or only?) side effect of temporarily setting to `kDeferred` is calling `params.SetLazyImageDeferred()` at
      https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;l=559;drc=64b417264ff2f93e61931ef67b439687923c44df;bpv=1;bpt=1
      but this flag should be no-op after we hit an already-loaded image on MemoryCache.

      Line 415, Patchset 12 (Latest): if (new_image_content->IsLoaded() &&
      Hiroshige Hayashizaki . unresolved

      `IsLoaded()` can't distinguish if the cache hit is due to the list-of-available-images or not (e.g. general MemoryCache hit). Is this OK? (I suppose perhals this is practically OK -- at least better than not fixing at all)

      Line 418, Patchset 12 (Latest): lazy_image_load_state_ = LazyImageLoadState::kFullImage;
      Hiroshige Hayashizaki . unresolved

      Maybe this should be

      `lazy_image_load_state_ = LazyImageLoadState::kNone;`

      • To align with Line 410, and
      • To align with my view of `lazy_image_load_state_ == LazyImageLoadState::kDeferred` == Step 25 of the spec. When hitting the list of available images, the lazy loading logic in the spec isn't triggered at all.

      I expect this doesn't impact the behavior though.

      Line 778, Patchset 12 (Latest): if (lazy_image_load_state_ != LazyImageLoadState::kDeferred ||
      Hiroshige Hayashizaki . unresolved

      Fix #1: Do not `delay the load event` by a lazy-loaded <img> while pending at Step 25.

      Analysis:

      Spec: `delay the load event` is executed only at Step 26, not during Step 25.
      Impl: Previously, a lazy-loaded <img> delays the load event once the loading
      is started BEFORE coming into the viewport (i.e. while in `LazyImageLoadState::kDeferred`).
      Fix: This CL (`ImageLoader::ImageChanged()` part here) fixes this, which I feel reasonable.
      Still there can be corner cases where such lazy loading images might delay the load event (not confirmed, e.g. when the underlying ImageResource is used from another Document which triggers loading start), but it would be anyway complicated hard-to-fix case.

      So:

      Could you add a WPT (and see how other browsers are behaving)?

      Could you add a comment to explain this, including how this corresponds to the spec?

      Line 780, Patchset 12 (Latest): delay_until_image_notify_finished_ =
      Hiroshige Hayashizaki . unresolved

      Possible Fix #2. Do not `delay the load event` by a lazy-loaded <img> after unblocked from Step 25.

      (Similar to but separate from Fix #1. But I haven't confirmed the actual behavior though, so I might be misunderstanding)

      Analysis:

      Spec: Step 24 of https://html.spec.whatwg.org/#update-the-image-data
      sets `delay load event` to false, and thus even after coming into the viewport, the image shouldn't delay the load event.
      Impl: Previously, a lazy-loaded <img> delays the load event once the loading
      is started AFTER coming into the viewport (i.e. while in `LazyImageLoadState::kFullImage`).
      A partial fix is done at
      https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;drc=64b417264ff2f93e61931ef67b439687923c44df;l=564
      but perhaps still delaying the load event due to `delay_until_image_notify_finished_`.

      Fix: Not fixed by this CL. Changing the code here (changing to `lazy_image_load_state_ == LazyImageLoadState::kNone || ...`) would probably fix this, while I'm not super sure we should fix this altogether here.
      Test: missing? not sure.

      Line 800, Patchset 12 (Latest): if (RuntimeEnabledFeatures::LazyImageConformantLoadEventTimingEnabled()) {
      Hiroshige Hayashizaki . unresolved

      Fix #3. Do not fire the <img> load event until a lazy loading image proceed to Step 26 of the spec.

      Analysis:

      Spec: unless the image hits the list of available images, the load event is fired only after Step 26.
      Impl: The load event is fired once the underlying ImageResourceContent is loaded.
      Fix: This CL (`ImageLoader::ImageNotifyFinished()` part) tries to fix this by not firing the load event.
      This relies on `LazyLoadImageObserver` to eventually call `ImageLoader::LoadDeferredImage()`, restarting the image loading from `UpdateFromElement()`.
      (Restarting itself has spec-conformance issues but should be orthogonal to this CL, and anyway it is how lazy image loading is implemented right now.)

      Probably this should work. I was a little concerned if there are corner cases where `LazyLoadImageObserver` is not activated even if `LazyImageLoadState::kDeferred`, while I couldn't figure out actual examples.

      Test: wpt/html/semantics/embedded-content/the-img-element/update-the-image-data/lazy-out-of-band-load.html
      (Also, do we have a WPT to confirm the load event is fired after coming into viewport?)

      However, note that this only fixes the <img> load/error event, and the image loading is still happening (e.g. updates to img's width/height is observable via JavaScript).
      Probably this is intended, but it's better to explicitly state in the comment here.

      e.g.

      ```
      // Some other content may have triggered the load of this image, but that
      // shouldn't fire this <img loading="lazy">'s load event at this time, because
      // in the spec this <img> is still in Step 25 of
      // https://html.spec.whatwg.org/#update-the-image-data.
      // When LazyLoadImageObserver reports it to be intersecting (or close to) the
      // viewport later (i.e. this <img> proceeds to Step 26 of the spec), actual
      // load/error event will be fired, by going through the loading process again
      // from `UpdateFromElement()`.
      // Note that in Chromium implementation (unlike in the spec), the image content
      // itself can be still loaded/updated even in this case, which can be observed
      // via e.g. <img>'s width/height.
      ```

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Dominic Farolino
      • Stefan Zager
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement 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: 12
      Gerrit-Owner: Stefan Zager <sza...@chromium.org>
      Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
      Gerrit-Reviewer: Hiroshige Hayashizaki <hiro...@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: Dominic Farolino <d...@chromium.org>
      Gerrit-Attention: Stefan Zager <sza...@chromium.org>
      Gerrit-Comment-Date: Wed, 07 Jan 2026 10:08:12 +0000
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Hiroshige Hayashizaki (Gerrit)

      unread,
      Jan 7, 2026, 5:09:04 AMJan 7
      to Stefan Zager, Mason Freed, AyeAye, Dominic Farolino, 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 Stefan Zager

      Hiroshige Hayashizaki added 1 comment

      Patchset-level comments
      File-level comment, Patchset 12 (Latest):
      Hiroshige Hayashizaki . resolved

      Basically looks good, while more tests/clarification might be needed.

      Gerrit-Comment-Date: Wed, 07 Jan 2026 10:08:40 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Stefan Zager (Gerrit)

      unread,
      Jan 7, 2026, 1:55:46 PMJan 7
      to Hiroshige Hayashizaki, Mason Freed, AyeAye, Dominic Farolino, 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 Hiroshige Hayashizaki

      Stefan Zager added 9 comments

      Commit Message
      Line 32, Patchset 12:steps.
      Hiroshige Hayashizaki . resolved

      IIUC, This CL is to make
      `lazy_image_load_state_ == LazyImageLoadState::kDeferred`
      to correspond with the spec state where the image element is waiting for intersection observer at Step 25 of
      https://html.spec.whatwg.org/#update-the-image-data, and fixes multiple behavior bugs (where I commented separately).

      Am I understanding correctly? If so, we should:

      • Update the comments at `LazyImageLoadState`,
      • Update the commit message to mention the multiple behavior changes.
      Stefan Zager

      The purpose of this CL is to enact the resolution of this spec issue:

      https://github.com/whatwg/html/issues/10671

      It is not my intention to do anything beyond that. If this CL fixes other incorrect behaviors, that's a bonus.

      File third_party/blink/renderer/core/css/resolver/element_style_resources.cc
      Line 435, Patchset 12: CHECK(!new_image || !new_image->IsLazyloadPossiblyDeferred());
      Hiroshige Hayashizaki . resolved

      Just to check: Is this part (and `third_party/blink/renderer/core/style/style_image.h`) related to this change?
      Should we do these separately?

      (Because this change is not guarded by the feature so I suspect this is separate)

      Stefan Zager

      domfarolino@ commented earlier that `IsLazyLoadPossibleDeferred()` can never be `true` here. I agree with that assessment, so I added the `CHECK` to enforce it. The next CL in the dependency chain removes the method `IsLazyLoadPossiblyDeferred()` entirely along with this `CHECK`.

      File third_party/blink/renderer/core/loader/image_loader.cc
      Line 414, Patchset 12: if (lazy_image_load_state_ == LazyImageLoadState::kDeferred) {
      Hiroshige Hayashizaki . unresolved

      Fix #4. Exempt the list-of-available-images hit cases from Fix #3.

      (I comment the details separately)

      Is this covered by a WPT? If not, could you add one?

      Stefan Zager

      This may be true, but it's beyond the scope of what I'm trying to do here, and it's not clear to me how to write such a WPT.

      Stefan Zager

      I don't understand the "infinite loop" concern. This code block is only reached when `lazy_image_load_state_==kDeferred`, and once `lazy_image_load_state_` transitions away from `kDeferred` it can never return to that value unless the element state changes in a way that triggers a new fetch and gets a new `ImageResourceContent`.

      To reiterate my prior points:

      • `UpdateFromElement()` is already called redundantly *many* times during normal operation.
      • `EnqueueImageLoadingMicroTask()` is *already* the code path used by `LazyLoadImageObserver()` to trigger a fetch after modifying `lazy_image_load_state_`.

      PS12, which removes the call to `EnqueueImageLoadingMicroTask()`, causes a browser test failure. For all these reasons, I would argue that `EnqueueImageLoadingMicroTask()` is the *safe and conservative thing to do.* I've restored the call in PS13, but changed the `behavior` argument to `kUpdateNormal`, which should have the same behavior as `kUpdateFromMicrotask` here but hopefully will alleviate your concern about infinite looping.

      Line 415, Patchset 12: if (new_image_content->IsLoaded() &&
      Hiroshige Hayashizaki . resolved

      `IsLoaded()` can't distinguish if the cache hit is due to the list-of-available-images or not (e.g. general MemoryCache hit). Is this OK? (I suppose perhals this is practically OK -- at least better than not fixing at all)

      Stefan Zager

      Acknowledged

      Line 418, Patchset 12: lazy_image_load_state_ = LazyImageLoadState::kFullImage;
      Hiroshige Hayashizaki . resolved

      Maybe this should be

      `lazy_image_load_state_ = LazyImageLoadState::kNone;`

      • To align with Line 410, and
      • To align with my view of `lazy_image_load_state_ == LazyImageLoadState::kDeferred` == Step 25 of the spec. When hitting the list of available images, the lazy loading logic in the spec isn't triggered at all.

      I expect this doesn't impact the behavior though.

      Stefan Zager

      Done

      Line 778, Patchset 12: if (lazy_image_load_state_ != LazyImageLoadState::kDeferred ||
      Hiroshige Hayashizaki . unresolved

      Fix #1: Do not `delay the load event` by a lazy-loaded <img> while pending at Step 25.

      Analysis:

      Spec: `delay the load event` is executed only at Step 26, not during Step 25.
      Impl: Previously, a lazy-loaded <img> delays the load event once the loading
      is started BEFORE coming into the viewport (i.e. while in `LazyImageLoadState::kDeferred`).
      Fix: This CL (`ImageLoader::ImageChanged()` part here) fixes this, which I feel reasonable.
      Still there can be corner cases where such lazy loading images might delay the load event (not confirmed, e.g. when the underlying ImageResource is used from another Document which triggers loading start), but it would be anyway complicated hard-to-fix case.

      So:

      Could you add a WPT (and see how other browsers are behaving)?

      Could you add a comment to explain this, including how this corresponds to the spec?

      Stefan Zager

      I'm not sure how to write such a test, and it's beyond the scope of what I'm trying to do here. If this CL fixes the narrow issue described in the linked bug and doesn't regress any existing tests, that's all I'm trying to do.

      Line 780, Patchset 12: delay_until_image_notify_finished_ =
      Hiroshige Hayashizaki . unresolved

      Possible Fix #2. Do not `delay the load event` by a lazy-loaded <img> after unblocked from Step 25.

      (Similar to but separate from Fix #1. But I haven't confirmed the actual behavior though, so I might be misunderstanding)

      Analysis:

      Spec: Step 24 of https://html.spec.whatwg.org/#update-the-image-data
      sets `delay load event` to false, and thus even after coming into the viewport, the image shouldn't delay the load event.
      Impl: Previously, a lazy-loaded <img> delays the load event once the loading
      is started AFTER coming into the viewport (i.e. while in `LazyImageLoadState::kFullImage`).
      A partial fix is done at
      https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;drc=64b417264ff2f93e61931ef67b439687923c44df;l=564
      but perhaps still delaying the load event due to `delay_until_image_notify_finished_`.

      Fix: Not fixed by this CL. Changing the code here (changing to `lazy_image_load_state_ == LazyImageLoadState::kNone || ...`) would probably fix this, while I'm not super sure we should fix this altogether here.
      Test: missing? not sure.

      Stefan Zager

      This is also beyond the scope of what I'm trying to do here.

      Line 800, Patchset 12: if (RuntimeEnabledFeatures::LazyImageConformantLoadEventTimingEnabled()) {
      Hiroshige Hayashizaki . resolved

      Fix #3. Do not fire the <img> load event until a lazy loading image proceed to Step 26 of the spec.

      Analysis:

      Spec: unless the image hits the list of available images, the load event is fired only after Step 26.
      Impl: The load event is fired once the underlying ImageResourceContent is loaded.
      Fix: This CL (`ImageLoader::ImageNotifyFinished()` part) tries to fix this by not firing the load event.
      This relies on `LazyLoadImageObserver` to eventually call `ImageLoader::LoadDeferredImage()`, restarting the image loading from `UpdateFromElement()`.
      (Restarting itself has spec-conformance issues but should be orthogonal to this CL, and anyway it is how lazy image loading is implemented right now.)

      Probably this should work. I was a little concerned if there are corner cases where `LazyLoadImageObserver` is not activated even if `LazyImageLoadState::kDeferred`, while I couldn't figure out actual examples.

      Test: wpt/html/semantics/embedded-content/the-img-element/update-the-image-data/lazy-out-of-band-load.html
      (Also, do we have a WPT to confirm the load event is fired after coming into viewport?)

      However, note that this only fixes the <img> load/error event, and the image loading is still happening (e.g. updates to img's width/height is observable via JavaScript).
      Probably this is intended, but it's better to explicitly state in the comment here.

      e.g.

      ```
      // Some other content may have triggered the load of this image, but that
      // shouldn't fire this <img loading="lazy">'s load event at this time, because
      // in the spec this <img> is still in Step 25 of
      // https://html.spec.whatwg.org/#update-the-image-data.
      // When LazyLoadImageObserver reports it to be intersecting (or close to) the
      // viewport later (i.e. this <img> proceeds to Step 26 of the spec), actual
      // load/error event will be fired, by going through the loading process again
      // from `UpdateFromElement()`.
      // Note that in Chromium implementation (unlike in the spec), the image content
      // itself can be still loaded/updated even in this case, which can be observed
      // via e.g. <img>'s width/height.
      ```

      Stefan Zager

      I've added your code comment here to the patch. Not sure about the test coverage issue.

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Dominic Farolino
      • Hiroshige Hayashizaki
      Gerrit-Attention: Hiroshige Hayashizaki <hiro...@chromium.org>
      Gerrit-Comment-Date: Wed, 07 Jan 2026 18:55:36 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      Comment-In-Reply-To: Dominic Farolino <d...@chromium.org>
      Comment-In-Reply-To: Hiroshige Hayashizaki <hiro...@chromium.org>
      Comment-In-Reply-To: Stefan Zager <sza...@chromium.org>
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Stefan Zager (Gerrit)

      unread,
      Jan 7, 2026, 4:39:18 PMJan 7
      to Hiroshige Hayashizaki, Mason Freed, AyeAye, Dominic Farolino, 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 Hiroshige Hayashizaki

      Stefan Zager added 1 comment

      File third_party/blink/renderer/core/loader/image_loader.cc
      Stefan Zager

      Failed tests on PS13 show that setting the state to `kNone` rather than `kFullImage` here doesn't work. Note that `LazyLoadImageObserver` changes the value to `kFullImage` as soon as the fetch is initiated, so using `kFullImage` here smells correct to me.

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Dominic Farolino
      • Hiroshige Hayashizaki
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement 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: 13
      Gerrit-Owner: Stefan Zager <sza...@chromium.org>
      Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
      Gerrit-Reviewer: Hiroshige Hayashizaki <hiro...@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: Dominic Farolino <d...@chromium.org>
      Gerrit-Attention: Hiroshige Hayashizaki <hiro...@chromium.org>
      Gerrit-Comment-Date: Wed, 07 Jan 2026 21:39:04 +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>
      Comment-In-Reply-To: Hiroshige Hayashizaki <hiro...@chromium.org>
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Stefan Zager (Gerrit)

      unread,
      Jan 7, 2026, 6:37:19 PMJan 7
      to Hiroshige Hayashizaki, Mason Freed, AyeAye, Dominic Farolino, 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 Hiroshige Hayashizaki

      Stefan Zager voted and added 1 comment

      Votes added by Stefan Zager

      Commit-Queue+1

      1 comment

      Patchset-level comments
      File-level comment, Patchset 14 (Latest):
      Stefan Zager . resolved

      PTAL. The latest patchset is essentially the same as PS9. The following suggestions from review comments were tried but abandoned due to test failures:

      • Remove call to `EnqueueImageLoadingMicroTask()`
      • Set `lazy_load_image_state_` to `kNone` rather than `kFullImage`

      The one actual change is that the argument to `EnqueueImageLoadingMicroTask()` is now `kUpdateNormal` rather than `kUpdateFromMicroTask`. This has no functional effect but hopefully alleviates concerns about a potential infinite loop.

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Dominic Farolino
      • Hiroshige Hayashizaki
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement 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: 14
      Gerrit-Owner: Stefan Zager <sza...@chromium.org>
      Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
      Gerrit-Reviewer: Hiroshige Hayashizaki <hiro...@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: Dominic Farolino <d...@chromium.org>
      Gerrit-Attention: Hiroshige Hayashizaki <hiro...@chromium.org>
      Gerrit-Comment-Date: Wed, 07 Jan 2026 23:37:10 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: Yes
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Stefan Zager (Gerrit)

      unread,
      Jan 14, 2026, 10:34:08 AM (9 days ago) Jan 14
      to Philip Rogers, Hiroshige Hayashizaki, Mason Freed, AyeAye, Dominic Farolino, 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 Hiroshige Hayashizaki

      Stefan Zager added 5 comments

      Patchset-level comments
      Stefan Zager . resolved

      This one has marinated long enough; let's land it and move on.

      File third_party/blink/renderer/core/loader/image_loader.cc
      Line 414, Patchset 12: if (lazy_image_load_state_ == LazyImageLoadState::kDeferred) {
      Hiroshige Hayashizaki . resolved

      Fix #4. Exempt the list-of-available-images hit cases from Fix #3.

      (I comment the details separately)

      Is this covered by a WPT? If not, could you add one?

      Stefan Zager

      This may be true, but it's beyond the scope of what I'm trying to do here, and it's not clear to me how to write such a WPT.

      Stefan Zager

      Marked as resolved.

      Line 415, Patchset 5: if (new_image_content->IsLoaded() &&
      Dominic Farolino . resolved
      Stefan Zager

      Marked as resolved.

      Line 778, Patchset 12: if (lazy_image_load_state_ != LazyImageLoadState::kDeferred ||
      Hiroshige Hayashizaki . resolved

      Fix #1: Do not `delay the load event` by a lazy-loaded <img> while pending at Step 25.

      Analysis:

      Spec: `delay the load event` is executed only at Step 26, not during Step 25.
      Impl: Previously, a lazy-loaded <img> delays the load event once the loading
      is started BEFORE coming into the viewport (i.e. while in `LazyImageLoadState::kDeferred`).
      Fix: This CL (`ImageLoader::ImageChanged()` part here) fixes this, which I feel reasonable.
      Still there can be corner cases where such lazy loading images might delay the load event (not confirmed, e.g. when the underlying ImageResource is used from another Document which triggers loading start), but it would be anyway complicated hard-to-fix case.

      So:

      Could you add a WPT (and see how other browsers are behaving)?

      Could you add a comment to explain this, including how this corresponds to the spec?

      Stefan Zager

      I'm not sure how to write such a test, and it's beyond the scope of what I'm trying to do here. If this CL fixes the narrow issue described in the linked bug and doesn't regress any existing tests, that's all I'm trying to do.

      Stefan Zager

      Marked as resolved.

      Line 780, Patchset 12: delay_until_image_notify_finished_ =
      Hiroshige Hayashizaki . resolved

      Possible Fix #2. Do not `delay the load event` by a lazy-loaded <img> after unblocked from Step 25.

      (Similar to but separate from Fix #1. But I haven't confirmed the actual behavior though, so I might be misunderstanding)

      Analysis:

      Spec: Step 24 of https://html.spec.whatwg.org/#update-the-image-data
      sets `delay load event` to false, and thus even after coming into the viewport, the image shouldn't delay the load event.
      Impl: Previously, a lazy-loaded <img> delays the load event once the loading
      is started AFTER coming into the viewport (i.e. while in `LazyImageLoadState::kFullImage`).
      A partial fix is done at
      https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/image_loader.cc;drc=64b417264ff2f93e61931ef67b439687923c44df;l=564
      but perhaps still delaying the load event due to `delay_until_image_notify_finished_`.

      Fix: Not fixed by this CL. Changing the code here (changing to `lazy_image_load_state_ == LazyImageLoadState::kNone || ...`) would probably fix this, while I'm not super sure we should fix this altogether here.
      Test: missing? not sure.

      Stefan Zager

      This is also beyond the scope of what I'm trying to do here.

      Stefan Zager

      Marked as resolved.

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Dominic Farolino
      • Hiroshige Hayashizaki
      Submit Requirements:
        • requirement satisfiedCode-Coverage
        • requirement satisfiedCode-Owners
        • requirement satisfiedCode-Review
        • requirement 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: 14
        Gerrit-Owner: Stefan Zager <sza...@chromium.org>
        Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
        Gerrit-Reviewer: Hiroshige Hayashizaki <hiro...@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-CC: Philip Rogers <p...@chromium.org>
        Gerrit-Attention: Dominic Farolino <d...@chromium.org>
        Gerrit-Attention: Hiroshige Hayashizaki <hiro...@chromium.org>
        Gerrit-Comment-Date: Wed, 14 Jan 2026 15:33:51 +0000
        satisfied_requirement
        open
        diffy

        Stefan Zager (Gerrit)

        unread,
        Jan 14, 2026, 10:34:11 AM (9 days ago) Jan 14
        to Philip Rogers, Hiroshige Hayashizaki, Mason Freed, AyeAye, Dominic Farolino, 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 Hiroshige Hayashizaki

        Stefan Zager voted Commit-Queue+2

        Commit-Queue+2
        Gerrit-Comment-Date: Wed, 14 Jan 2026 15:33:56 +0000
        Gerrit-HasComments: No
        Gerrit-Has-Labels: Yes
        satisfied_requirement
        open
        diffy

        Chromium LUCI CQ (Gerrit)

        unread,
        Jan 14, 2026, 12:35:19 PM (9 days ago) Jan 14
        to Stefan Zager, Philip Rogers, Hiroshige Hayashizaki, Mason Freed, AyeAye, Dominic Farolino, 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

        Chromium LUCI CQ submitted the change with unreviewed changes

        Unreviewed changes

        8 is the latest approved patch-set.
        The change was submitted with unreviewed changes in the following files:

        ```
        The name of the file: third_party/blink/renderer/core/style/style_image.h
        Insertions: 1, Deletions: 3.

        The diff is too large to show. Please review the diff.
        ```
        ```
        The name of the file: third_party/blink/renderer/core/loader/image_loader.cc
        Insertions: 11, Deletions: 4.

        The diff is too large to show. Please review the diff.
        ```

        Change information

        Commit message:
        Preserve lazy loading behavior when an image is side-loaded, take two

        Re-landing after revert:

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

        Patchset 1 is the previously-landed change, patchset 2 contains the fix
        for crbug.com/469002488. Root cause: an <img> was loading="lazy" and
        outside the viewport; then the underlying resource was loaded anyway
        (due to being fetched by other means); and the lazy <img> did not fire
        its load event but it blocked the document from being marked as fully
        loaded.

        When two elements use the same underlying resource, each element gets
        its own ImageLoader, but they point to the same underlying
        ImageResourceContent. Information about the loading lifecycle is
        propagated from ImageResourceContent to ImageLoader(s) via the
        ImageResourceObserver interface, which ImageLoader implements. Thus,
        ImageNotifyFinished will be propagated to all observing elements,
        regardless of whether they are loading="lazy" or not. This CL causes
        ImageLoader to recognize when this happens and early-return rather than
        running the "image loaded" steps. Later, when the element enters the
        viewport and the lazy-loading machinery requests the image,
        ResourceFetcher will get a cache hit and trigger ImageNotifyFinished
        again; but this time the element will proceed with the "image loaded"
        steps.
        Bug: 375209498,469002488
        Change-Id: I7b2513dd9943fbbea2094138716d1ea98400665c
        Commit-Queue: Stefan Zager <sza...@chromium.org>
        Reviewed-by: Mason Freed <mas...@chromium.org>
        Cr-Commit-Position: refs/heads/main@{#1569164}
        Files:
        • M third_party/blink/renderer/core/css/resolver/element_style_resources.cc
        • M third_party/blink/renderer/core/loader/image_loader.cc
        • M third_party/blink/renderer/core/style/style_image.h
        • M third_party/blink/renderer/platform/runtime_enabled_features.json5
        • D third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/update-the-image-data/lazy-out-of-band-load-expected.txt
        Change size: M
        Delta: 5 files changed, 43 insertions(+), 22 deletions(-)
        Branch: refs/heads/main
        Submit Requirements:
        • requirement satisfiedCode-Review: +1 by Mason Freed
        Open in Gerrit
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: merged
        Gerrit-Project: chromium/src
        Gerrit-Branch: main
        Gerrit-Change-Id: I7b2513dd9943fbbea2094138716d1ea98400665c
        Gerrit-Change-Number: 7274640
        Gerrit-PatchSet: 15
        Gerrit-Owner: Stefan Zager <sza...@chromium.org>
        Gerrit-Reviewer: Chromium LUCI CQ <chromiu...@luci-project-accounts.iam.gserviceaccount.com>
        Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
        Gerrit-Reviewer: Hiroshige Hayashizaki <hiro...@chromium.org>
        Gerrit-Reviewer: Mason Freed <mas...@chromium.org>
        Gerrit-Reviewer: Stefan Zager <sza...@chromium.org>
        open
        diffy
        satisfied_requirement
        Reply all
        Reply to author
        Forward
        0 new messages