fix(ext/popup): revoke object URLs in Document detail teardown
Two leaks from 705b171:
1. Lazy-load thumb for image-mime primary attachments created
URL.createObjectURL but never revoked. Now tracked in a
module-level registry, revoked on teardown.
2. 🔍 preview toggle's object URL same issue. Now tracked, revoked
on teardown + on toggle-off (when user clicks the preview button
to collapse).
Download button's URL (already self-cleaning via setTimeout) left
untracked — no change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,8 +17,23 @@ let activeKeyHandler: ((e: KeyboardEvent) => void) | null = null;
|
|||||||
let activeFormEscHandler: ((e: KeyboardEvent) => void) | null = null;
|
let activeFormEscHandler: ((e: KeyboardEvent) => void) | null = null;
|
||||||
let sectionsExpanded = false;
|
let sectionsExpanded = false;
|
||||||
|
|
||||||
|
const detailObjectUrls = new Set<string>();
|
||||||
|
|
||||||
|
function trackDetailUrl(url: string): string {
|
||||||
|
detailObjectUrls.add(url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function revokeAllDetailUrls(): void {
|
||||||
|
for (const url of detailObjectUrls) {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
detailObjectUrls.clear();
|
||||||
|
}
|
||||||
|
|
||||||
export function teardown(): void {
|
export function teardown(): void {
|
||||||
teardownAttachmentsDisclosure();
|
teardownAttachmentsDisclosure();
|
||||||
|
revokeAllDetailUrls();
|
||||||
if (activeKeyHandler) {
|
if (activeKeyHandler) {
|
||||||
document.removeEventListener('keydown', activeKeyHandler);
|
document.removeEventListener('keydown', activeKeyHandler);
|
||||||
activeKeyHandler = null;
|
activeKeyHandler = null;
|
||||||
@@ -296,7 +311,7 @@ export async function renderDetail(app: HTMLElement, item: Item): Promise<void>
|
|||||||
if (!resp || !resp.ok) return;
|
if (!resp || !resp.ok) return;
|
||||||
const data = resp.data as { bytes: ArrayBuffer };
|
const data = resp.data as { bytes: ArrayBuffer };
|
||||||
const blob = new Blob([data.bytes], { type: primaryRef.mime_type });
|
const blob = new Blob([data.bytes], { type: primaryRef.mime_type });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = trackDetailUrl(URL.createObjectURL(blob));
|
||||||
if (thumb) thumb.innerHTML = `<img src="${url}" alt="" />`;
|
if (thumb) thumb.innerHTML = `<img src="${url}" alt="" />`;
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
@@ -316,12 +331,20 @@ export async function renderDetail(app: HTMLElement, item: Item): Promise<void>
|
|||||||
document.getElementById('doc-preview')?.addEventListener('click', async () => {
|
document.getElementById('doc-preview')?.addEventListener('click', async () => {
|
||||||
const sigblock = document.getElementById('doc-sigblock')!;
|
const sigblock = document.getElementById('doc-sigblock')!;
|
||||||
const existingPreview = sigblock.querySelector('.document-signature-block__preview');
|
const existingPreview = sigblock.querySelector('.document-signature-block__preview');
|
||||||
if (existingPreview) { existingPreview.remove(); return; }
|
if (existingPreview) {
|
||||||
|
const img = existingPreview.querySelector('img');
|
||||||
|
if (img?.src) {
|
||||||
|
URL.revokeObjectURL(img.src);
|
||||||
|
detailObjectUrls.delete(img.src);
|
||||||
|
}
|
||||||
|
existingPreview.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const resp = await sendMessage({ type: 'download_attachment', itemId: item.id, attachmentId: primaryRef.id });
|
const resp = await sendMessage({ type: 'download_attachment', itemId: item.id, attachmentId: primaryRef.id });
|
||||||
if (!resp || !resp.ok) return;
|
if (!resp || !resp.ok) return;
|
||||||
const data = resp.data as { bytes: ArrayBuffer };
|
const data = resp.data as { bytes: ArrayBuffer };
|
||||||
const blob = new Blob([data.bytes], { type: primaryRef.mime_type });
|
const blob = new Blob([data.bytes], { type: primaryRef.mime_type });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = trackDetailUrl(URL.createObjectURL(blob));
|
||||||
const preview = document.createElement('div');
|
const preview = document.createElement('div');
|
||||||
preview.className = 'document-signature-block__preview';
|
preview.className = 'document-signature-block__preview';
|
||||||
preview.innerHTML = `<img src="${url}" alt="" />`;
|
preview.innerHTML = `<img src="${url}" alt="" />`;
|
||||||
|
|||||||
Reference in New Issue
Block a user