fix(gallery): release the canvas-mode decode, and drop a now-duplicate sanitizer
Two follow-ups to yesterday's merges. Both were already known; neither depends on the open question in #1287. 1. Canvas mode pinned every decoded image for the component's lifetime. `AuthenticatedImage` keeps a detached Image in `imageRef` so drawToCanvas can read it. The effect cleanup nulled onload/onerror and never cleared that ref, so the Image — and the decode behind it — stayed held by a live JS reference. A decoded <img> in the document is evictable under memory pressure; one held by a ref is not. That is not academic at gallery scale. The photo grid is NOT virtualised, so a 546-photo event mounts 546 of these and none ever unmount — nothing was ever released. The ref is cleared and the src dropped, so the browser can reclaim without waiting for GC. This is NOT presented as the fix for #1287. That investigation is still open: the reporter has since shown the backend idle during a stall and the renderer itself unresponsive for 45s, which rules out the theories tried so far. This is a real leak on the same path, worth fixing on its own terms while that question is settled. 2. newsletterService no longer carries its own remote-url() stripper. It was added because the shared sanitizeCSS "blocked" remote URLs with a CSS comment that parsers discard. #1290 replaced that with a lexer, so the local copy is dead weight — and two definitions of "disallowed" would drift apart. Verified the shared function covers every case the local one did, including the quoted-paren and CSS-escape forms found in review. Three tests on the release path, two of which fail without the fix: unmount clears the ref and drops the src, the blob URL is revoked, and a src change releases the previous image rather than accumulating one pinned decode per photo a recycled tile has shown.
This commit is contained in:
@@ -286,6 +286,20 @@ export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
|
||||
return () => {
|
||||
img.onload = null;
|
||||
img.onerror = null;
|
||||
// Release the decoded bitmap (#1287). `imageRef` is what drawToCanvas
|
||||
// reads, and it was never cleared — so a detached Image, and the
|
||||
// decode behind it, stayed pinned by a live JS reference for the
|
||||
// lifetime of the component. A decoded <img> in the document is
|
||||
// evictable under memory pressure; one held by a ref is not.
|
||||
//
|
||||
// This matters at gallery scale because the grid is not virtualised:
|
||||
// a 546-photo event mounts 546 of these and none of them ever unmount,
|
||||
// so nothing was ever released. Dropping the src first lets the
|
||||
// browser reclaim the decode without waiting for GC to notice.
|
||||
if (imageRef.current === img) {
|
||||
imageRef.current = null;
|
||||
}
|
||||
img.removeAttribute('src');
|
||||
};
|
||||
}, [imageSrc, useCanvasRendering, drawToCanvas, onLoad]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user