From 4bbaeb4d94cf959aadbb63693c07e85c25db0ba5 Mon Sep 17 00:00:00 2001 From: Vitalii Date: Sun, 11 Oct 2026 20:57:45 +0300 Subject: [PATCH] A long capture succeeded, then the popup reported it failed --- apps/extension/src/background/index.ts | 29 +++++++++++++++++++++++--- apps/extension/src/popup/popup.ts | 4 ++-- apps/extension/src/types.ts | 6 +++++- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/apps/extension/src/background/index.ts b/apps/extension/src/background/index.ts index 69398df..b85a731 100644 --- a/apps/extension/src/background/index.ts +++ b/apps/extension/src/background/index.ts @@ -30,6 +30,7 @@ import { getLastCaptureImages, testScrollTargets, } from "./orchestrator"; +import { loadShotCore } from "./wasm-loader"; function bytesToBase64(bytes: Uint8Array): string { let binary = ""; @@ -83,6 +84,28 @@ function pngPixelSize(bytes: Uint8Array): { width: number; height: number } | nu return { width, height }; } +/** + * Small PNG preview of a capture for the popup's thumbnail. The popup's + * preview used to ride in the capture response as full-size base64 data URLs, + * which at a long page crossed chrome.runtime.sendMessage's ~64MiB message + * cap — the response died after the capture had already succeeded, so the + * popup showed an error and no preview over a healthy capture (saves and the + * editor, which read blob-store, kept working). A ~320px-wide re-encode is a + * few KB and cannot hit the cap; the full-size bytes stay in blob-store for + * everything that actually needs them. + */ +const PREVIEW_WIDTH_PX = 320; + +async function previewDataUrl(image: Uint8Array): Promise { + const size = pngPixelSize(image); + if (!size) return undefined; + const shotCore = await loadShotCore(); + const width = Math.min(size.width, PREVIEW_WIDTH_PX); + const height = Math.max(1, Math.round((size.height * width) / size.width)); + const thumb = shotCore.cropAndEncode(image, 0, 0, size.width, size.height, 1, width, height) as Uint8Array; + return bytesToDataUrl(thumb, "image/png"); +} + /** * `source` is what the browser-frame tool stamps into the address bar. It is * carried here rather than read in the editor because the editor runs in its @@ -366,7 +389,7 @@ async function handleRequest(request: PopupRequest): Promise { await setLastCaptureUi({ report, openedEditor }); await rememberInHistory(tab, report, images[0]!); await bumpUsageCount(); - return { ok: true, report, pngDataUrls: images.map((img) => bytesToDataUrl(img, "image/png")), openedEditor }; + return { ok: true, report, previewDataUrl: await previewDataUrl(images[0]!), openedEditor }; } case "captureVisible": { const tab = await getActiveTab(); @@ -375,7 +398,7 @@ async function handleRequest(request: PopupRequest): Promise { await setLastCaptureUi({ report, openedEditor: true }); await rememberInHistory(tab, report, images[0]!); await bumpUsageCount(); - return { ok: true, report, pngDataUrls: images.map((img) => bytesToDataUrl(img, "image/png")), openedEditor: true }; + return { ok: true, report, previewDataUrl: await previewDataUrl(images[0]!), openedEditor: true }; } case "captureSelectedArea": { // The popup closes as soon as the user clicks into the page to start @@ -398,7 +421,7 @@ async function handleRequest(request: PopupRequest): Promise { return { ok: true, report: outcome.report, - pngDataUrls: outcome.images.map((img) => bytesToDataUrl(img, "image/png")), + previewDataUrl: await previewDataUrl(outcome.images[0]!), openedEditor: true, }; } diff --git a/apps/extension/src/popup/popup.ts b/apps/extension/src/popup/popup.ts index cde6362..8722fee 100644 --- a/apps/extension/src/popup/popup.ts +++ b/apps/extension/src/popup/popup.ts @@ -575,9 +575,9 @@ async function showCaptureResult(response: PopupResponse): Promise { if ("report" in response) { reportEl.style.display = "block"; reportEl.textContent = JSON.stringify(response.report, null, 2); - if (response.pngDataUrls[0]) { + if (response.previewDataUrl) { previewEl.style.display = "block"; - previewEl.src = response.pngDataUrls[0]; + previewEl.src = response.previewDataUrl; } // background/index.ts already persisted this (see chrome/last-capture-ui.ts) // by the time this response reaches here, if it reaches here at all — diff --git a/apps/extension/src/types.ts b/apps/extension/src/types.ts index 2ba2bbe..d402059 100644 --- a/apps/extension/src/types.ts +++ b/apps/extension/src/types.ts @@ -102,7 +102,11 @@ export type PopupRequest = export interface CaptureResult { ok: true; report: CaptureReport; - pngDataUrls: string[]; + // Small downscaled PNG of the capture's first output image, for the popup's + // preview thumbnail. The full-size bytes stay in blob-store: putting them in + // this response crossed chrome.runtime.sendMessage's ~64MiB message cap and + // failed captures that had already succeeded. + previewDataUrl?: string; // True when the capture was routed to the editor for crop/annotate/format // review. False when it was too long for that (see capture-size.ts's // needsFormatChoice), in which case nothing has been written or opened at