fix(clone): drop <picture> <source> elements so the inlined <img> src is not overridden (#462) - #465
Merged
tinchox5 merged 1 commit intoJul 23, 2026
Conversation
…not overridden A <picture>'s <source> out-ranks its <img>'s own src. The sources were cloned verbatim, so the exported SVG re-selected an external URL at rasterization time — and svg-as-image may not load external resources. The photo never painted, however correctly the <img> had been inlined, producing a large SVG that rasterized to a small blank PNG. Skip <source> children of a <picture> when cloning. The <img> clone is already frozen to the variant the live page chose (freezeImgSrcset sets src = original.currentSrc and strips srcset/sizes), so the sources carry nothing still needed and art direction is preserved. Verified against a WordPress hero carousel whose slides use <picture> with a catch-all <source media="(min-width: 0px)">, which made the failure deterministic. With the site's real assets and an identical 723 KB SVG: 48 KB PNG with 0% of the photo painted before, 2046 KB and 100% after. Capturing at a 1600px viewport embeds the 2200x1200 crops; at 500px it embeds the 600x1000 crops. Closes zumerlab#462 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Member
|
Merged, thanks. Good instinct scoping the fix to |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #462.
Problem
A
<picture>'s<source>elements out-rank the<img>'s ownsrc. They were cloned verbatim, so inside the exported SVG the picture selection algorithm re-selected an external URL — and SVG-as-image cannot load external resources. The photo never painted, however correctly the<img>had been inlined.The signature is a large SVG that rasterizes to a tiny PNG: everything embedded, nothing drawn.
Why the existing machinery doesn't cover it
freezeImgSrcset(src/utils/clone.helpers.js) already sets the cloned<img>'ssrc = original.currentSrcand stripssrcset/sizes— but only on the<img>. The sibling<source>elements go through the generic child-clone path with their externalsrcsets intact.pictureResolver(src/modules/pictureResolver.js) only acts on a<picture>whose<img src>is a lazy-load placeholder (isPlaceholderSrc); itcontinues for a normally-loaded picture, and runs against the live DOM before cloning.So a fully-loaded
<picture>reaches the SVG with its<source>list intact.Fix
Skip
<source>children of a<picture>indeepClone:Because
freezeImgSrcsethas already frozen the clone to the variant the live page chose, the sources carry nothing still needed. Art direction is preserved — the chosen crop comes fromcurrentSrc, not from re-evaluating the sources. Capturing the same carousel at a 1600px viewport embeds the 2200×1200 crops; at 500px it embeds the 600×1000 crops.The check is deliberately narrow:
<source>inside<audio>/<video>is untouched, and there's a test covering that.Results
Real WordPress hero carousel, real assets, identical 723 KB SVG both runs:
<source>in SVGTests
New
deepClone — <picture> sourcesblock in__tests__/core.clone.test.js:<source>children of a<picture>are dropped and the inlined<img>src survives — fails onmain, passes with the fix<source>outside a<picture>is kept — guards against over-reach; passes either way by designFull suite: 685 passed / 1 skipped, against 683 / 1 on
main— no regressions.npm run lintandnpm run test:typesclean. Verified on Chromium; I don't have the Firefox/WebKit Playwright binaries locally.