tabctl 0.1.4 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{cli → dist/cli}/lib/commands/index.js +4 -2
- package/dist/cli/lib/commands/meta.js +226 -0
- package/dist/cli/lib/commands/params-groups.js +40 -0
- package/dist/cli/lib/commands/params-move.js +44 -0
- package/{cli → dist/cli}/lib/commands/params.js +61 -125
- package/{cli/lib/commands/meta.js → dist/cli/lib/commands/setup.js} +26 -222
- package/{cli/lib/options.js → dist/cli/lib/options-commands.js} +3 -155
- package/dist/cli/lib/options-groups.js +41 -0
- package/dist/cli/lib/options.js +125 -0
- package/{cli → dist/cli}/lib/output.js +5 -4
- package/dist/cli/lib/policy-filter.js +202 -0
- package/dist/cli/lib/response.js +235 -0
- package/{cli → dist/cli}/lib/scope.js +3 -31
- package/dist/cli/tabctl.js +463 -0
- package/dist/extension/background.js +3398 -0
- package/dist/extension/lib/archive.js +444 -0
- package/dist/extension/lib/content.js +320 -0
- package/dist/extension/lib/deps.js +4 -0
- package/dist/extension/lib/groups.js +443 -0
- package/dist/extension/lib/inspect.js +316 -0
- package/dist/extension/lib/move.js +342 -0
- package/dist/extension/lib/screenshot.js +367 -0
- package/dist/extension/lib/tabs.js +395 -0
- package/dist/extension/lib/undo-handlers.js +439 -0
- package/{extension → dist/extension}/manifest.json +2 -2
- package/dist/host/host.js +124 -0
- package/{host/host.js → dist/host/lib/handlers.js} +84 -187
- package/{shared → dist/shared}/version.js +2 -2
- package/package.json +12 -10
- package/cli/tabctl.js +0 -841
- package/extension/background.js +0 -3372
- package/extension/manifest.template.json +0 -22
- /package/{cli → dist/cli}/lib/args.js +0 -0
- /package/{cli → dist/cli}/lib/client.js +0 -0
- /package/{cli → dist/cli}/lib/commands/list.js +0 -0
- /package/{cli → dist/cli}/lib/commands/profile.js +0 -0
- /package/{cli → dist/cli}/lib/constants.js +0 -0
- /package/{cli → dist/cli}/lib/help.js +0 -0
- /package/{cli → dist/cli}/lib/pagination.js +0 -0
- /package/{cli → dist/cli}/lib/policy.js +0 -0
- /package/{cli → dist/cli}/lib/report.js +0 -0
- /package/{cli → dist/cli}/lib/snapshot.js +0 -0
- /package/{cli → dist/cli}/lib/types.js +0 -0
- /package/{host → dist/host}/host.sh +0 -0
- /package/{host → dist/host}/lib/undo.js +0 -0
- /package/{shared → dist/shared}/config.js +0 -0
- /package/{shared → dist/shared}/extension-sync.js +0 -0
- /package/{shared → dist/shared}/profiles.js +0 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Screenshot pipeline — extracted from background.ts (pure structural refactor).
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.SCREENSHOT_PROCESS_TIMEOUT_MS = exports.SCREENSHOT_CAPTURE_DELAY_MS = exports.SCREENSHOT_SCROLL_DELAY_MS = exports.SCREENSHOT_QUALITY = exports.SCREENSHOT_MAX_BYTES = exports.SCREENSHOT_TILE_MAX_DIM = void 0;
|
|
5
|
+
exports.estimateDataUrlBytes = estimateDataUrlBytes;
|
|
6
|
+
exports.arrayBufferToBase64 = arrayBufferToBase64;
|
|
7
|
+
exports.resizeDataUrl = resizeDataUrl;
|
|
8
|
+
exports.resizeDataUrlToMaxDim = resizeDataUrlToMaxDim;
|
|
9
|
+
exports.cropDataUrl = cropDataUrl;
|
|
10
|
+
exports.ensureMaxBytes = ensureMaxBytes;
|
|
11
|
+
exports.constrainDataUrl = constrainDataUrl;
|
|
12
|
+
exports.captureVisible = captureVisible;
|
|
13
|
+
exports.getPageMetrics = getPageMetrics;
|
|
14
|
+
exports.scrollToPosition = scrollToPosition;
|
|
15
|
+
exports.captureTabTiles = captureTabTiles;
|
|
16
|
+
exports.screenshotTabs = screenshotTabs;
|
|
17
|
+
exports.SCREENSHOT_TILE_MAX_DIM = 2000;
|
|
18
|
+
exports.SCREENSHOT_MAX_BYTES = 2000000;
|
|
19
|
+
exports.SCREENSHOT_QUALITY = 80;
|
|
20
|
+
exports.SCREENSHOT_SCROLL_DELAY_MS = 150;
|
|
21
|
+
exports.SCREENSHOT_CAPTURE_DELAY_MS = 350;
|
|
22
|
+
exports.SCREENSHOT_PROCESS_TIMEOUT_MS = 8000;
|
|
23
|
+
function estimateDataUrlBytes(dataUrl) {
|
|
24
|
+
const commaIndex = dataUrl.indexOf(",");
|
|
25
|
+
if (commaIndex < 0) {
|
|
26
|
+
return dataUrl.length;
|
|
27
|
+
}
|
|
28
|
+
const base64 = dataUrl.slice(commaIndex + 1);
|
|
29
|
+
const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
|
|
30
|
+
return Math.max(0, Math.floor((base64.length * 3) / 4) - padding);
|
|
31
|
+
}
|
|
32
|
+
function arrayBufferToBase64(buffer) {
|
|
33
|
+
const bytes = new Uint8Array(buffer);
|
|
34
|
+
let binary = "";
|
|
35
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
36
|
+
binary += String.fromCharCode(bytes[i]);
|
|
37
|
+
}
|
|
38
|
+
return btoa(binary);
|
|
39
|
+
}
|
|
40
|
+
async function resizeDataUrl(dataUrl, format, quality, scale) {
|
|
41
|
+
if (!globalThis.OffscreenCanvas || !globalThis.createImageBitmap) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const response = await fetch(dataUrl);
|
|
45
|
+
const blob = await response.blob();
|
|
46
|
+
const bitmap = await createImageBitmap(blob);
|
|
47
|
+
const width = Math.max(1, Math.floor(bitmap.width * scale));
|
|
48
|
+
const height = Math.max(1, Math.floor(bitmap.height * scale));
|
|
49
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
50
|
+
const ctx = canvas.getContext("2d");
|
|
51
|
+
if (!ctx) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
ctx.drawImage(bitmap, 0, 0, width, height);
|
|
55
|
+
const type = format === "jpeg" ? "image/jpeg" : "image/png";
|
|
56
|
+
const blobOut = await canvas.convertToBlob({ type, quality: format === "jpeg" ? quality / 100 : undefined });
|
|
57
|
+
const buffer = await blobOut.arrayBuffer();
|
|
58
|
+
const base64 = arrayBufferToBase64(buffer);
|
|
59
|
+
return {
|
|
60
|
+
dataUrl: `data:${type};base64,${base64}`,
|
|
61
|
+
bytes: buffer.byteLength,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async function resizeDataUrlToMaxDim(dataUrl, format, quality, maxDim) {
|
|
65
|
+
if (!globalThis.OffscreenCanvas || !globalThis.createImageBitmap) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const response = await fetch(dataUrl);
|
|
69
|
+
const blob = await response.blob();
|
|
70
|
+
const bitmap = await createImageBitmap(blob);
|
|
71
|
+
const maxSize = Math.max(bitmap.width, bitmap.height);
|
|
72
|
+
if (!Number.isFinite(maxSize) || maxSize <= maxDim) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const scale = maxDim / maxSize;
|
|
76
|
+
return resizeDataUrl(dataUrl, format, quality, scale);
|
|
77
|
+
}
|
|
78
|
+
async function cropDataUrl(dataUrl, format, quality, width, height, devicePixelRatio) {
|
|
79
|
+
if (!globalThis.OffscreenCanvas || !globalThis.createImageBitmap) {
|
|
80
|
+
return dataUrl;
|
|
81
|
+
}
|
|
82
|
+
const response = await fetch(dataUrl);
|
|
83
|
+
const blob = await response.blob();
|
|
84
|
+
const bitmap = await createImageBitmap(blob);
|
|
85
|
+
const targetWidth = Math.min(bitmap.width, Math.max(1, Math.round(width * devicePixelRatio)));
|
|
86
|
+
const targetHeight = Math.min(bitmap.height, Math.max(1, Math.round(height * devicePixelRatio)));
|
|
87
|
+
if (targetWidth === bitmap.width && targetHeight === bitmap.height) {
|
|
88
|
+
return dataUrl;
|
|
89
|
+
}
|
|
90
|
+
const canvas = new OffscreenCanvas(targetWidth, targetHeight);
|
|
91
|
+
const ctx = canvas.getContext("2d");
|
|
92
|
+
if (!ctx) {
|
|
93
|
+
return dataUrl;
|
|
94
|
+
}
|
|
95
|
+
ctx.drawImage(bitmap, 0, 0, targetWidth, targetHeight, 0, 0, targetWidth, targetHeight);
|
|
96
|
+
const type = format === "jpeg" ? "image/jpeg" : "image/png";
|
|
97
|
+
const blobOut = await canvas.convertToBlob({ type, quality: format === "jpeg" ? quality / 100 : undefined });
|
|
98
|
+
const buffer = await blobOut.arrayBuffer();
|
|
99
|
+
const base64 = arrayBufferToBase64(buffer);
|
|
100
|
+
return `data:${type};base64,${base64}`;
|
|
101
|
+
}
|
|
102
|
+
async function ensureMaxBytes(dataUrl, format, quality, maxBytes) {
|
|
103
|
+
let currentUrl = dataUrl;
|
|
104
|
+
let currentBytes = estimateDataUrlBytes(currentUrl);
|
|
105
|
+
if (currentBytes <= maxBytes) {
|
|
106
|
+
return { dataUrl: currentUrl, bytes: currentBytes, scaled: false, oversized: false };
|
|
107
|
+
}
|
|
108
|
+
let scaled = false;
|
|
109
|
+
let attempts = 0;
|
|
110
|
+
while (currentBytes > maxBytes && attempts < 3) {
|
|
111
|
+
const scale = Math.max(0.2, Math.sqrt(maxBytes / currentBytes) * 0.95);
|
|
112
|
+
if (scale >= 0.99) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
const resized = await resizeDataUrl(currentUrl, format, quality, scale);
|
|
116
|
+
if (!resized) {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
currentUrl = resized.dataUrl;
|
|
120
|
+
currentBytes = resized.bytes;
|
|
121
|
+
scaled = true;
|
|
122
|
+
attempts += 1;
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
dataUrl: currentUrl,
|
|
126
|
+
bytes: currentBytes,
|
|
127
|
+
scaled,
|
|
128
|
+
oversized: currentBytes > maxBytes,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async function constrainDataUrl(dataUrl, format, quality, maxDim, maxBytes) {
|
|
132
|
+
let currentUrl = dataUrl;
|
|
133
|
+
let currentBytes = estimateDataUrlBytes(currentUrl);
|
|
134
|
+
let scaled = false;
|
|
135
|
+
if (Number.isFinite(maxDim) && maxDim > 0) {
|
|
136
|
+
const resized = await resizeDataUrlToMaxDim(currentUrl, format, quality, maxDim);
|
|
137
|
+
if (resized) {
|
|
138
|
+
currentUrl = resized.dataUrl;
|
|
139
|
+
currentBytes = resized.bytes;
|
|
140
|
+
scaled = true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (currentBytes > maxBytes) {
|
|
144
|
+
const resized = await ensureMaxBytes(currentUrl, format, quality, maxBytes);
|
|
145
|
+
return {
|
|
146
|
+
dataUrl: resized.dataUrl,
|
|
147
|
+
bytes: resized.bytes,
|
|
148
|
+
scaled: scaled || resized.scaled,
|
|
149
|
+
oversized: resized.oversized,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return { dataUrl: currentUrl, bytes: currentBytes, scaled, oversized: false };
|
|
153
|
+
}
|
|
154
|
+
async function captureVisible(windowId, format, quality) {
|
|
155
|
+
const options = { format };
|
|
156
|
+
if (format === "jpeg") {
|
|
157
|
+
options.quality = quality;
|
|
158
|
+
}
|
|
159
|
+
return chrome.tabs.captureVisibleTab(windowId, options);
|
|
160
|
+
}
|
|
161
|
+
async function getPageMetrics(tabId, timeoutMs, deps) {
|
|
162
|
+
const result = await deps.executeWithTimeout(tabId, timeoutMs, () => {
|
|
163
|
+
const doc = document.documentElement;
|
|
164
|
+
const body = document.body;
|
|
165
|
+
const pageWidth = Math.max(doc.scrollWidth, doc.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);
|
|
166
|
+
const pageHeight = Math.max(doc.scrollHeight, doc.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);
|
|
167
|
+
return {
|
|
168
|
+
pageWidth,
|
|
169
|
+
pageHeight,
|
|
170
|
+
viewportWidth: window.innerWidth,
|
|
171
|
+
viewportHeight: window.innerHeight,
|
|
172
|
+
devicePixelRatio: window.devicePixelRatio || 1,
|
|
173
|
+
scrollX: window.scrollX || window.pageXOffset || 0,
|
|
174
|
+
scrollY: window.scrollY || window.pageYOffset || 0,
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
if (!result || typeof result !== "object") {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
async function scrollToPosition(tabId, timeoutMs, x, y, deps) {
|
|
183
|
+
const result = await deps.executeWithTimeout(tabId, timeoutMs, (scrollX, scrollY) => {
|
|
184
|
+
window.scrollTo(scrollX, scrollY);
|
|
185
|
+
return {
|
|
186
|
+
scrollX: window.scrollX || window.pageXOffset || 0,
|
|
187
|
+
scrollY: window.scrollY || window.pageYOffset || 0,
|
|
188
|
+
};
|
|
189
|
+
}, [x, y]);
|
|
190
|
+
if (!result || typeof result !== "object") {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
async function captureTabTiles(tab, options, deps) {
|
|
196
|
+
const tabId = tab.tabId;
|
|
197
|
+
const windowId = tab.windowId;
|
|
198
|
+
if (!Number.isFinite(tabId) || !Number.isFinite(windowId)) {
|
|
199
|
+
throw new Error("Missing tab/window id");
|
|
200
|
+
}
|
|
201
|
+
const metrics = await getPageMetrics(tabId, exports.SCREENSHOT_PROCESS_TIMEOUT_MS, deps);
|
|
202
|
+
if (!metrics) {
|
|
203
|
+
throw new Error("Failed to read page metrics");
|
|
204
|
+
}
|
|
205
|
+
const pageWidth = Number(metrics.pageWidth);
|
|
206
|
+
const pageHeight = Number(metrics.pageHeight);
|
|
207
|
+
const viewportWidth = Number(metrics.viewportWidth);
|
|
208
|
+
const viewportHeight = Number(metrics.viewportHeight);
|
|
209
|
+
const devicePixelRatio = Number(metrics.devicePixelRatio) || 1;
|
|
210
|
+
const startScrollX = Number(metrics.scrollX) || 0;
|
|
211
|
+
const startScrollY = Number(metrics.scrollY) || 0;
|
|
212
|
+
if (!Number.isFinite(viewportWidth) || !Number.isFinite(viewportHeight)) {
|
|
213
|
+
throw new Error("Viewport size unavailable");
|
|
214
|
+
}
|
|
215
|
+
const tiles = [];
|
|
216
|
+
let tileIndex = 0;
|
|
217
|
+
const captureTile = async (x, y, width, height, total) => {
|
|
218
|
+
if (tileIndex > 0) {
|
|
219
|
+
await deps.delay(exports.SCREENSHOT_CAPTURE_DELAY_MS);
|
|
220
|
+
}
|
|
221
|
+
const rawDataUrl = await captureVisible(windowId, options.format, options.quality);
|
|
222
|
+
if (!rawDataUrl) {
|
|
223
|
+
throw new Error("Capture failed");
|
|
224
|
+
}
|
|
225
|
+
const croppedUrl = await cropDataUrl(rawDataUrl, options.format, options.quality, width, height, devicePixelRatio);
|
|
226
|
+
const sizeResult = await constrainDataUrl(croppedUrl, options.format, options.quality, options.tileMaxDim, options.maxBytes);
|
|
227
|
+
tiles.push({
|
|
228
|
+
index: tileIndex,
|
|
229
|
+
total,
|
|
230
|
+
x,
|
|
231
|
+
y,
|
|
232
|
+
width,
|
|
233
|
+
height,
|
|
234
|
+
scale: devicePixelRatio,
|
|
235
|
+
format: options.format,
|
|
236
|
+
bytes: sizeResult.bytes,
|
|
237
|
+
scaled: sizeResult.scaled,
|
|
238
|
+
oversized: sizeResult.oversized,
|
|
239
|
+
dataUrl: sizeResult.dataUrl,
|
|
240
|
+
});
|
|
241
|
+
tileIndex += 1;
|
|
242
|
+
};
|
|
243
|
+
if (options.mode === "viewport") {
|
|
244
|
+
await captureTile(startScrollX, startScrollY, viewportWidth, viewportHeight, 1);
|
|
245
|
+
return tiles;
|
|
246
|
+
}
|
|
247
|
+
const stepX = viewportWidth;
|
|
248
|
+
const stepY = Math.min(viewportHeight, options.tileMaxDim);
|
|
249
|
+
const maxX = viewportWidth;
|
|
250
|
+
const maxY = Math.max(viewportHeight, pageHeight);
|
|
251
|
+
const tileCount = Math.ceil(maxX / stepX) * Math.ceil(maxY / stepY);
|
|
252
|
+
for (let y = 0; y < maxY; y += stepY) {
|
|
253
|
+
for (let x = 0; x < maxX; x += stepX) {
|
|
254
|
+
await scrollToPosition(tabId, exports.SCREENSHOT_PROCESS_TIMEOUT_MS, x, y, deps);
|
|
255
|
+
await deps.delay(exports.SCREENSHOT_SCROLL_DELAY_MS);
|
|
256
|
+
const width = Math.min(stepX, maxX - x);
|
|
257
|
+
const height = Math.min(stepY, maxY - y);
|
|
258
|
+
try {
|
|
259
|
+
await captureTile(x, y, width, height, tileCount);
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
const message = err instanceof Error ? err.message : "capture_failed";
|
|
263
|
+
if (message.includes("MAX_CAPTURE_VISIBLE_TAB_CALLS_PER_SECOND")) {
|
|
264
|
+
await deps.delay(1000);
|
|
265
|
+
await captureTile(x, y, width, height, tileCount);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
throw err;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
await scrollToPosition(tabId, exports.SCREENSHOT_PROCESS_TIMEOUT_MS, startScrollX, startScrollY, deps);
|
|
274
|
+
return tiles;
|
|
275
|
+
}
|
|
276
|
+
async function screenshotTabs(params, requestId, deps) {
|
|
277
|
+
const snapshot = await deps.getTabSnapshot();
|
|
278
|
+
const selection = deps.selectTabsByScope(snapshot, params);
|
|
279
|
+
if (selection.error) {
|
|
280
|
+
throw selection.error;
|
|
281
|
+
}
|
|
282
|
+
const mode = params.mode === "full" ? "full" : "viewport";
|
|
283
|
+
const format = params.format === "jpeg" ? "jpeg" : "png";
|
|
284
|
+
const qualityRaw = Number(params.quality);
|
|
285
|
+
const quality = Number.isFinite(qualityRaw) ? Math.min(100, Math.max(0, Math.floor(qualityRaw))) : exports.SCREENSHOT_QUALITY;
|
|
286
|
+
const tileMaxDimRaw = Number(params.tileMaxDim);
|
|
287
|
+
const tileMaxDim = Number.isFinite(tileMaxDimRaw) && tileMaxDimRaw > 0 ? Math.floor(tileMaxDimRaw) : exports.SCREENSHOT_TILE_MAX_DIM;
|
|
288
|
+
const adjustedTileMaxDim = tileMaxDim < 50 ? 50 : tileMaxDim;
|
|
289
|
+
const maxBytesRaw = Number(params.maxBytes);
|
|
290
|
+
const maxBytes = Number.isFinite(maxBytesRaw) && maxBytesRaw > 0 ? Math.floor(maxBytesRaw) : exports.SCREENSHOT_MAX_BYTES;
|
|
291
|
+
const adjustedMaxBytes = maxBytes < 50000 ? 50000 : maxBytes;
|
|
292
|
+
const progressEnabled = params.progress === true;
|
|
293
|
+
const tabs = selection.tabs;
|
|
294
|
+
const entries = [];
|
|
295
|
+
let totalTiles = 0;
|
|
296
|
+
const startedAt = Date.now();
|
|
297
|
+
for (let index = 0; index < tabs.length; index += 1) {
|
|
298
|
+
const tab = tabs[index];
|
|
299
|
+
const tabId = tab.tabId;
|
|
300
|
+
const url = tab.url;
|
|
301
|
+
if (!deps.isScriptableUrl(url)) {
|
|
302
|
+
entries.push({
|
|
303
|
+
tabId,
|
|
304
|
+
windowId: tab.windowId,
|
|
305
|
+
groupId: tab.groupId,
|
|
306
|
+
url: tab.url,
|
|
307
|
+
title: tab.title,
|
|
308
|
+
error: { message: "unsupported_url" },
|
|
309
|
+
tiles: [],
|
|
310
|
+
});
|
|
311
|
+
if (progressEnabled) {
|
|
312
|
+
deps.sendProgress(requestId, { phase: "screenshot", processed: index + 1, total: tabs.length, tabId });
|
|
313
|
+
}
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
let tiles = [];
|
|
317
|
+
let error = null;
|
|
318
|
+
try {
|
|
319
|
+
const windowId = tab.windowId;
|
|
320
|
+
const activeTabs = await chrome.tabs.query({ windowId, active: true });
|
|
321
|
+
const activeTabId = activeTabs[0]?.id ?? null;
|
|
322
|
+
if (activeTabId && activeTabId !== tabId) {
|
|
323
|
+
await chrome.tabs.update(tabId, { active: true });
|
|
324
|
+
await deps.delay(exports.SCREENSHOT_SCROLL_DELAY_MS);
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
await deps.waitForTabReady(tabId, params, exports.SCREENSHOT_PROCESS_TIMEOUT_MS);
|
|
328
|
+
tiles = await captureTabTiles(tab, { mode, format, quality, tileMaxDim: adjustedTileMaxDim, maxBytes: adjustedMaxBytes }, deps);
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
if (activeTabId && activeTabId !== tabId) {
|
|
332
|
+
await chrome.tabs.update(activeTabId, { active: true });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
const message = err instanceof Error ? err.message : "capture_failed";
|
|
338
|
+
error = { message };
|
|
339
|
+
}
|
|
340
|
+
totalTiles += tiles.length;
|
|
341
|
+
entries.push({
|
|
342
|
+
tabId: tab.tabId,
|
|
343
|
+
windowId: tab.windowId,
|
|
344
|
+
groupId: tab.groupId,
|
|
345
|
+
url: tab.url,
|
|
346
|
+
title: tab.title,
|
|
347
|
+
tiles,
|
|
348
|
+
...(error ? { error } : {}),
|
|
349
|
+
});
|
|
350
|
+
if (progressEnabled) {
|
|
351
|
+
deps.sendProgress(requestId, { phase: "screenshot", processed: index + 1, total: tabs.length, tabId });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
generatedAt: Date.now(),
|
|
356
|
+
totals: { tabs: tabs.length, tiles: totalTiles },
|
|
357
|
+
meta: {
|
|
358
|
+
durationMs: Date.now() - startedAt,
|
|
359
|
+
mode,
|
|
360
|
+
format,
|
|
361
|
+
quality: format === "jpeg" ? quality : null,
|
|
362
|
+
tileMaxDim: adjustedTileMaxDim,
|
|
363
|
+
maxBytes: adjustedMaxBytes,
|
|
364
|
+
},
|
|
365
|
+
entries,
|
|
366
|
+
};
|
|
367
|
+
}
|