diff --git a/apps/web-clipper/entrypoints/background/index.ts b/apps/web-clipper/entrypoints/background/index.ts index c36aa4899..a27571884 100644 --- a/apps/web-clipper/entrypoints/background/index.ts +++ b/apps/web-clipper/entrypoints/background/index.ts @@ -1,7 +1,6 @@ -import { randomString } from "../../utils"; -import TriliumServerFacade, { isDevEnv } from "./trilium_server_facade"; +import { randomString, Rect } from "@/utils"; -type Rect = { x: number, y: number, width: number, height: number }; +import TriliumServerFacade, { isDevEnv } from "./trilium_server_facade"; export default defineBackground(() => { const triliumServerFacade = new TriliumServerFacade(); @@ -23,38 +22,46 @@ export default defineBackground(() => { } }); - function cropImage(newArea: Rect, dataUrl: string) { - return new Promise((resolve) => { - const img = new Image(); - - img.onload = function () { - const canvas = document.createElement('canvas'); - canvas.width = newArea.width; - canvas.height = newArea.height; - - const ctx = canvas.getContext('2d'); - ctx?.drawImage(img, newArea.x, newArea.y, newArea.width, newArea.height, 0, 0, newArea.width, newArea.height); - - resolve(canvas.toDataURL()); - }; - - img.src = dataUrl; - }); - } - - async function takeCroppedScreenshot(cropRect: Rect) { + async function takeCroppedScreenshot(cropRect: Rect, devicePixelRatio: number = 1) { const activeTab = await getActiveTab(); - const zoom = await browser.tabs.getZoom(activeTab.id) * window.devicePixelRatio; + const zoom = await browser.tabs.getZoom(activeTab.id) * devicePixelRatio; - const newArea: Rect = Object.assign({}, cropRect); - newArea.x *= zoom; - newArea.y *= zoom; - newArea.width *= zoom; - newArea.height *= zoom; + const newArea: Rect = { + x: cropRect.x * zoom, + y: cropRect.y * zoom, + width: cropRect.width * zoom, + height: cropRect.height * zoom + }; const dataUrl = await browser.tabs.captureVisibleTab({ format: 'png' }); - return await cropImage(newArea, dataUrl); + // Create offscreen document if it doesn't exist + await ensureOffscreenDocument(); + + // Send cropping task to offscreen document + const croppedDataUrl = await browser.runtime.sendMessage({ + type: 'CROP_IMAGE', + dataUrl, + cropRect: newArea + }); + + return croppedDataUrl; + } + + async function ensureOffscreenDocument() { + const existingContexts = await browser.runtime.getContexts({ + contextTypes: ['OFFSCREEN_DOCUMENT'] + }); + + if (existingContexts.length > 0) { + return; // Already exists + } + + await browser.offscreen.createDocument({ + url: browser.runtime.getURL('/offscreen.html'), + reasons: ['DOM_SCRAPING'], // or 'DISPLAY_MEDIA' depending on browser support + justification: 'Image cropping requires canvas API' + }); } async function takeWholeScreenshot() { @@ -224,9 +231,9 @@ export default defineBackground(() => { } async function saveCroppedScreenshot(pageUrl) { - const cropRect = await sendMessageToActiveTab({name: 'trilium-get-rectangle-for-screenshot'}); + const { rect, devicePixelRatio } = await sendMessageToActiveTab({name: 'trilium-get-rectangle-for-screenshot'}); - const src = await takeCroppedScreenshot(cropRect); + const src = await takeCroppedScreenshot(rect, devicePixelRatio); const payload = await getImagePayloadFromSrc(src, pageUrl); diff --git a/apps/web-clipper/entrypoints/content/index.ts b/apps/web-clipper/entrypoints/content/index.ts index 0ee35ef0d..c085dd867 100644 --- a/apps/web-clipper/entrypoints/content/index.ts +++ b/apps/web-clipper/entrypoints/content/index.ts @@ -1,3 +1,5 @@ +import { Rect } from "@/utils.js"; + import Readability from "../../lib/Readability.js"; import { createLink, getBaseUrl, getPageLocationOrigin, randomString } from "../../utils.js"; @@ -69,7 +71,7 @@ export default defineContentScript({ } function getRectangleArea() { - return new Promise((resolve) => { + return new Promise((resolve) => { const overlay = document.createElement('div'); overlay.style.opacity = '0.6'; overlay.style.background = 'black'; @@ -120,7 +122,7 @@ export default defineContentScript({ let isDragging = false; let draggingStartPos: {x: number, y: number} | null = null; - let selectionArea: {x?: number, y?: number, width?: number, height?: number} = {}; + let selectionArea: Rect; function updateSelection() { selection.style.left = `${selectionArea.x}px`; @@ -300,7 +302,10 @@ export default defineContentScript({ } else if (message.name === 'trilium-get-rectangle-for-screenshot') { - return getRectangleArea(); + return { + rect: await getRectangleArea(), + devicePixelRatio: window.devicePixelRatio + }; } else if (message.name === "trilium-save-page") { const {title, body} = getReadableDocument(); diff --git a/apps/web-clipper/entrypoints/offscreen/index.html b/apps/web-clipper/entrypoints/offscreen/index.html new file mode 100644 index 000000000..b6a07c8d2 --- /dev/null +++ b/apps/web-clipper/entrypoints/offscreen/index.html @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/web-clipper/entrypoints/offscreen/index.ts b/apps/web-clipper/entrypoints/offscreen/index.ts new file mode 100644 index 000000000..6c63f5f4c --- /dev/null +++ b/apps/web-clipper/entrypoints/offscreen/index.ts @@ -0,0 +1,24 @@ +browser.runtime.onMessage.addListener((message, _sender, sendResponse) => { + if (message.type === 'CROP_IMAGE') { + cropImage(message.cropRect, message.dataUrl).then(sendResponse); + return true; // Keep channel open for async response + } +}); + +function cropImage(newArea: { x: number, y: number, width: number, height: number }, dataUrl: string) { + return new Promise((resolve) => { + const img = new Image(); + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = newArea.width; + canvas.height = newArea.height; + const ctx = canvas.getContext('2d'); + if (ctx) { + ctx.drawImage(img, newArea.x, newArea.y, newArea.width, newArea.height, + 0, 0, newArea.width, newArea.height); + } + resolve(canvas.toDataURL()); + }; + img.src = dataUrl; + }); +} diff --git a/apps/web-clipper/utils.ts b/apps/web-clipper/utils.ts index 5c1b3efd6..7dbbafd03 100644 --- a/apps/web-clipper/utils.ts +++ b/apps/web-clipper/utils.ts @@ -1,3 +1,5 @@ +export type Rect = { x: number, y: number, width: number, height: number }; + export function randomString(len: number) { let text = ""; const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; diff --git a/apps/web-clipper/wxt.config.ts b/apps/web-clipper/wxt.config.ts index 7a3ec383c..f824eb64c 100644 --- a/apps/web-clipper/wxt.config.ts +++ b/apps/web-clipper/wxt.config.ts @@ -12,7 +12,8 @@ export default defineConfig({ "https://*/", "", "storage", - "contextMenus" + "contextMenus", + "offscreen" ], browser_specific_settings: { gecko: {