Skip to content

Commit b94f652

Browse files
committed
fix: resolve <picture> srcset via media-query matching, not currentSrc
currentSrc resolves eagerly/synchronously in WebKit but is deferred past the clone point in Chromium (confirmed: 5/5 runs froze the wrong, non-selected source under fast:true). Inside a <picture>, resolve the winning source explicitly via pictureResolver's own matching logic instead of trusting an unresolved currentSrc.
1 parent 23a3611 commit b94f652

2 files changed

Lines changed: 45 additions & 2 deletions

File tree

__tests__/core.clone.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,42 @@ describe('deepClone — <picture> sources', () => {
162162
})
163163
})
164164

165+
describe('freezeImgSrcset — <picture> currentSrc timing (#464-adjacent, bug-hunt finding)', () => {
166+
const OWN_PLACEHOLDER = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
167+
168+
it('resolves the matching <source> even when currentSrc has not resolved synchronously yet', async () => {
169+
// <picture>'s own source-selection algorithm runs eagerly in WebKit but is deferred past
170+
// this synchronous point in Chromium (confirmed empirically: still unresolved across
171+
// microtask/macrotask(0) checkpoints, only settling ~1 rAF later) — cloning immediately
172+
// after insertion, with no await in between, is exactly that race window.
173+
const picture = document.createElement('picture')
174+
picture.innerHTML = '<source media="(min-width: 0px)" srcset="https://example.com/selected.jpg">'
175+
const img = document.createElement('img')
176+
img.src = OWN_PLACEHOLDER
177+
picture.appendChild(img)
178+
document.body.appendChild(picture)
179+
try {
180+
const clone = await runClone(picture)
181+
const clonedImg = clone.querySelector('img')
182+
expect(clonedImg.getAttribute('src')).toBe('https://example.com/selected.jpg')
183+
} finally {
184+
picture.remove()
185+
}
186+
})
187+
188+
it('still freezes a plain (non-picture) <img> to its own src', async () => {
189+
const img = document.createElement('img')
190+
img.src = OWN_PLACEHOLDER
191+
document.body.appendChild(img)
192+
try {
193+
const clone = await runClone(img)
194+
expect(clone.getAttribute('src')).toBe(OWN_PLACEHOLDER)
195+
} finally {
196+
img.remove()
197+
}
198+
})
199+
})
200+
165201
describe('deepClone edge cases', () => {
166202
it('clones unsupported node (Comment) as a new Comment', async () => {
167203
const fake = document.createComment('not supported')

src/utils/clone.helpers.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { idle, debugWarn } from './index.js'
77
import { cache, EvictingMap } from '../core/cache.js'
88
import { snapFetch } from '../modules/snapFetch.js'
99
import { inlineAllStyles } from '../modules/styles.js'
10+
import { findRealUrlForPicture } from '../modules/pictureResolver.js'
1011

1112
/**
1213
* Schedule work across idle slices without relying on IdleDeadline constructor.
@@ -167,13 +168,19 @@ export function injectScopedStyle(hostClone, cssText, scopeId) {
167168
* Freeze the responsive selection of an <img> that has srcset/sizes.
168169
* Copies a concrete URL into `src` and removes `srcset`/`sizes` so the clone
169170
* doesn't need layout to resolve a candidate.
170-
* Works with <picture> because currentSrc reflects the chosen source.
171+
* Works with <picture> because currentSrc reflects the chosen source — except
172+
* currentSrc resolves eagerly/synchronously in WebKit but is deferred past this point in
173+
* Chromium (confirmed: 5/5 runs froze the wrong, non-selected source under fast:true — the
174+
* default). Inside a <picture>, resolve the winning source explicitly via the same
175+
* media-query matching logic pictureResolver already uses instead of trusting an
176+
* unresolved currentSrc.
171177
* @param {HTMLImageElement} original - Image in the live DOM.
172178
* @param {HTMLImageElement} cloned - Just-created cloned <img>.
173179
*/
174180
export function freezeImgSrcset(original, cloned) {
175181
try {
176-
const chosen = original.currentSrc || original.src || ''
182+
const picture = original.closest?.('picture')
183+
const chosen = (picture ? findRealUrlForPicture(original, picture) : original.currentSrc) || original.src || ''
177184
if (!chosen) return
178185
cloned.setAttribute('src', chosen)
179186
cloned.removeAttribute('srcset')

0 commit comments

Comments
 (0)