feat(electron): restore bmp/avif background image support

This commit is contained in:
Johannes Millan 2026-06-10 17:04:26 +02:00
parent 9b58b883af
commit 0286cd3b5d
4 changed files with 45 additions and 2 deletions

View file

@ -70,6 +70,39 @@ test('importImage copies a valid image and returns an opaque id', async () => {
}
});
test('importImage accepts avif and bmp (restored legacy formats)', async () => {
const cache = load();
const sourceDir = fsModule.realpathSync.native(
await fs.mkdtemp(path.join(os.tmpdir(), 'sp-src-')),
);
try {
const avif = await mkPng(sourceDir, 'bg.avif', Buffer.from('fakeavif'));
const avifResult = await cache.importImage(avif);
assert.ok(avifResult);
assert.equal(avifResult.mimeType, 'image/avif');
const bmp = await mkPng(sourceDir, 'bg.bmp', Buffer.from('fakebmp'));
const bmpResult = await cache.importImage(bmp);
assert.ok(bmpResult);
assert.equal(bmpResult.mimeType, 'image/bmp');
} finally {
await fs.rm(sourceDir, { recursive: true, force: true });
}
});
test('importImage rejects svg (scriptable format deliberately excluded)', async () => {
const cache = load();
const sourceDir = fsModule.realpathSync.native(
await fs.mkdtemp(path.join(os.tmpdir(), 'sp-src-')),
);
try {
const svg = await mkPng(sourceDir, 'bg.svg', Buffer.from('<svg></svg>'));
assert.equal(await cache.importImage(svg), null);
} finally {
await fs.rm(sourceDir, { recursive: true, force: true });
}
});
test('importImage returns null for a path inside userData (no laundering)', async () => {
const cache = load();
const evilPath = await mkPng(userDataDir, 'planted.png');

View file

@ -33,12 +33,17 @@ import { assertPathOutside } from './file-path-guard';
const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
// SVG is deliberately excluded: it is a scriptable format (can embed
// <script>/event handlers) and inlining it as a data URL would reintroduce an
// XSS surface, so the cache only accepts raster formats.
const MIME_BY_EXT: Record<string, string> = {
png: 'image/png',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
gif: 'image/gif',
webp: 'image/webp',
bmp: 'image/bmp',
avif: 'image/avif',
};
const ID_RE = /^[a-f0-9]{32}$/;

View file

@ -272,7 +272,12 @@ export const initLocalFileSyncAdapter = (): void => {
buttonLabel: 'Select',
properties: ['openFile'],
filters: [
{ name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp'] },
{
name: 'Images',
// Keep in sync with MIME_BY_EXT in image-cache.ts (svg excluded
// on purpose — scriptable format).
extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'avif'],
},
],
})) as unknown as { canceled: boolean; filePaths: string[] };
if (canceled || !filePaths[0]) {

View file

@ -2,7 +2,7 @@
<input
#fileInput
type="file"
accept="image/png,image/jpeg,image/gif,image/webp"
accept="image/png,image/jpeg,image/gif,image/webp,image/bmp,image/avif"
hidden
(change)="onFileSelected($event)"
/>