Skip to main content

ImageViewer default export downloads cross-origin images via blob (DES-133)

Version: 0.4.1 · Type: 🐛 Bug Fix

DES-133 (sub-task under DES-129)

Problem

When no onExport prop is provided, ImageViewer's default export uses <a download> + the original src URL. However, the download attribute only works for same-origin, blob:, or data: URLs. In practice most images are served from cross-origin CDN hosts; browsers silently ignore the download attribute for cross-origin resources (security restriction) and navigate to the URL instead — resulting in "export opens a new tab" rather than triggering a download. The design-site's use of picsum.photos (cross-origin) reliably reproduced the issue.

Changed Files

  • src/components/ImageViewer/ImageViewer.tsx
  • src/components/ImageViewer/__tests__/ImageViewer.test.tsx
  • src/components/ImageViewer/ImageViewerDesignSpec.md
  • packages/design-site/docs/components/patterns/image-viewer.mdx

Changes

Blob-based download in ImageViewer.tsx

Default export changed to: fetch(src)blob()URL.createObjectURL to produce a same-origin objectURL, then trigger <a download>. Cross-origin CDN images can now be genuinely downloaded (requires the CDN to return appropriate CORS headers).

  • Falls back to the original URL when fetch fails (CORS denied / network error) or HTTP non-2xx (response.ok is false) — ensures silent failure never occurs and avoids saving an error-page body as a corrupted image file.
  • objectURL revocation deferred by 1000 ms (OBJECT_URL_REVOKE_DELAY) after the download is triggered, preventing a synchronous revokeObjectURL from releasing the blob before the browser reads it.
  • Download filename inferred from the final path segment of src; falls back to browser-determined filename if no valid name is found.
  • New module-level helpers: getFileNameFromSrc / triggerAnchorDownload / downloadImageBySrc.
  • Component JSDoc and onExport prop comment updated to reflect the blob-download behavior.

Tests in ImageViewer.test.tsx

  • Original "fallback anchor download" test case converted to blob-download assertions (mocks fetch / URL.createObjectURL / revokeObjectURL; verifies fetch arguments, anchor click, and delayed objectURL revocation).
  • New test: fetch failure falls back to direct original-URL download.
  • New test: HTTP non-2xx (404) fallback — verifies no objectURL is created and the download falls back to the original URL directly.

Documentation

ImageViewerDesignSpec.md and image-viewer.mdx export descriptions updated to reflect the blob-download approach.