"chrome.tabs.captureVisibleTab이 tab 전체를 dataURL로 넘겨주고, 선택 영역의 좌표가 그중 어느 픽셀이 중요한지 알려 줘. 이 lesson에서 그 둘을 이어서, ClipDeck의 clip 마다 자기가 어디서 왔는지를 딱 맞게 오려낸 그림으로 지니고 다니게 만들 거야."
chrome.tabs.captureVisibleTab
API가 작아. 두 인자:
windowId— optional. 주어진 window의 active tab capture. 생략하면 현재 window capture.options— 안 줘도 돼.{ format: 'png' | 'jpeg', quality: 0..100 }를 받고 기본은 PNG 야. 글자가 많은 페이지를 찍을 땐 quality 80 짜리 JPEG가 보통 5 배쯤 작아.
data: URL을 돌려줘. 찍히는 건 지금 화면에 보이는 부분, 그러니까 user가 실제로 보고 있는 만큼이야. 화면 밖에 있는 건 안 찍혀.
권한은 셋 중 하나면 돼. user가 뭘 누른 직후의 activeTab 이든, 그 주소에 대한 host_permissions 든, tabs 든. ClipDeck은 이 시점에 셋 다 갖고 있고.
Crop workflow
찍힌 그림은 tab 전체야. ClipDeck이 원하는 건 user가 고른 영역만이고. 계산은 이렇게 해:
- Content script에서 selection의
getBoundingClientRect()얻기 — viewport-relative 좌표. - Device pixel ratio (
window.devicePixelRatio) 얻기. Captured 이미지가 native 해상도. rect는 CSS pixel. - SW가 dataURL 이랑 좌표, 화면 배율을 받아. 화면 밖 canvas에 그림을 올리고, 계산해 둔 픽셀 좌표대로 잘라서 다시 그려.
- Cropped canvas를 또 다른 dataURL로 export. Clip과 저장.
Canvas 위한 offscreen document
service worker는 DOM을 못 쓰니까 평범한 canvas를 만들 수가 없어. 그래서 MV3가 chrome.offscreen을 들여왔지. SW가 DOM API가 필요할 때 — canvas 든, Blob을 다루는 fetch 든, 오디오든 — 몰래 띄우는 문서야. 모양은 이래:
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['BLOBS'],
justification: 'Crop captured tab screenshots for ClipDeck clips',
});
offscreen.html이 SW에서 메시지 받고, canvas work 하고, 결과 다시 post 하는 offscreen.js 가진 작은 page. 사용 후 chrome.offscreen.closeDocument() 호출 — 열어 두는 게 약간 메모리 낭비.
Crop 코드
Offscreen document 안:
const img = new Image();
img.src = dataURL;
await new Promise((r) => (img.onload = r));
const canvas = new OffscreenCanvas(
Math.round(rect.width * dpr),
Math.round(rect.height * dpr),
);
const ctx = canvas.getContext('2d');
ctx.drawImage(
img,
rect.left * dpr, rect.top * dpr,
rect.width * dpr, rect.height * dpr,
0, 0,
rect.width * dpr, rect.height * dpr,
);
const blob = await canvas.convertToBlob({ type: 'image/png' });
const reader = new FileReader();
reader.readAsDataURL(blob);
await new Promise((r) => (reader.onload = r));
return reader.result; // cropped dataURL
OffscreenCanvas가 offscreen document에서 지원되고 Worker-friendly canvas 제공. 9-인자 drawImage form: sx, sy, sw, sh, dx, dy, dw, dh. Source 좌표가 rect에서. destination이 0,0에서 시작.
Size 예산
Typical 텍스트 selection의 각 PNG screenshot이 ~10–50 KB. PNG이 텍스트엔 fine. JPEG quality 80이 또 30–50% cut. dataURL을 chrome.storage.local에 직접 저장 — quota가 약 10 MB, cropped 유지하면 몇 백 screenshot에 plenty. Clip 카운트 자라면 공격적 압축, JPEG 전환, 또는 user가 clip 별 screenshot opt-out 고려.
Clip에 들어가는 것
Clip schema 확장:
{ id, text, url, title, savedAt, screenshot?: string /* dataURL */ }
Side panel이 있으면 screenshot inline 렌더 (작은 thumbnail. click으로 확대). Export flow가 포함. clipboard copy는 여전히 text만.
Capture scale이 devicePixelRatio과 같다고 가정하지 마. Zoom과 display 설정 때문에 bitmap 크기가 달라질 수 있어. Selection rect와 CSS viewport width/height를 같이 보내고, image를 load한 뒤 scaleX = img.width / viewport.width, scaleY = img.height / viewport.height를 계산해. Rect를 visible viewport에 clamp한 다음 그 scale로 crop해.