Problem/Motivation
In FN.loadImage() (js/src/base/io/bio.media.js), the following early-exit is taken whenever the currently displayed placeholder is already considered decoded by the browser:
// Bail out early if already loaded. if (!isBg && $.isDecoded(el)) { load(el, true); return; }
$.isDecoded() checks el.naturalWidth !== 0 on el itself — i.e. on the placeholder image that is still assigned to [src], not on the real target image referenced by [data-src]. An inline data:image/svg+xml placeholder is synchronously available, so depending on the placeholder's viewBox/dimensions and the browser's default-object-size algorithm, naturalWidth can already be non-zero the instant the element enters the viewport.
When that happens, load(el, true) is called immediately. This marks the element as successfully loaded (b-loaded class via $.status()), but the actual swap logic — $.mapAttr(el, ['srcset', 'src'], false) inside preload() — is never reached, because that code only runs in the else branch (the new Image() / $.decode() path). The result: [data-src] is silently dropped, [src] keeps showing the placeholder forever, and the element is nevertheless flagged as loaded.
This is most reliably reproducible with plain <img class="b-lazy" data-src="…"> elements whose placeholder is a generic viewBox="0 0 1 1" SVG (e.g. icons rendered without going through the responsive-image/thumbnail pipeline, so Placeholder::generate() falls back to 1×1). We noticed it visibly on manually-lazyloaded SVG icons (social/payment logos), but the underlying condition is not SVG-specific — any placeholder that the browser happens to report as already "decoded" for its current [src] before the real image was ever assigned will trigger it.
Steps to reproduce
- Render an
<img class="b-lazy" data-src="/path/to/real-image.svg" src="data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%201%201'%2F%3E">(noloadingattribute, so it goes through the IO path, not the native-loading shortcut). - Scroll it into view.
- Inspect the element after Blazy has processed it: class list contains
b-loaded, but[src]is unchanged (still the placeholder),[data-src]has been removed.
Proposed resolution
Only take the "already decoded" shortcut when there is no pending [data-src]/[data-srcset] swap:
- if (!isBg && $.isDecoded(el)) { + if (!isBg && !isDataset && $.isDecoded(el)) { load(el, true); return; }
(isDataset is already computed a few lines above via $.hasAttr(el, VARS.dataSrc).)
Patch attached against 3.0.19 for both js/src/base/io/bio.media.js and the shipped js/base/io/bio.media.min.js.
Remaining tasks
- Maintainer review of the proposed condition, in case there is a reason the "already decoded" shortcut was meant to also cover elements with a pending
[data-src]. - No automated test currently covers this path (placeholder reported as decoded before the real image is assigned); adding one would help prevent a regression.
User interface changes
None.
API changes
None.
Data model changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| svg-icon-isdecoded-shortcut.patch | 5.89 KB | hammerslammer |
Comments
Comment #2
hammerslammer commented