@reshotdev/screenshot 0.0.1-beta.12 → 0.0.1-beta.13
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/README.md +67 -22
- package/package.json +18 -14
- package/src/commands/auth.js +37 -7
- package/src/commands/capture-dom.js +50 -0
- package/src/commands/compose.js +220 -0
- package/src/commands/doctor-target.js +36 -4
- package/src/commands/drifts.js +13 -1
- package/src/commands/publish.js +137 -12
- package/src/commands/pull.js +9 -4
- package/src/commands/refresh.js +166 -0
- package/src/commands/setup-wizard.js +35 -2
- package/src/commands/status.js +22 -2
- package/src/commands/variation.js +194 -0
- package/src/index.js +187 -9
- package/src/lib/api-client.js +61 -35
- package/src/lib/auto-update/refresh.js +598 -0
- package/src/lib/auto-update/scene-runtime.compose.tsx +73 -0
- package/src/lib/auto-update/spec.js +89 -0
- package/src/lib/capture-engine.js +73 -0
- package/src/lib/capture-script-runner.js +280 -134
- package/src/lib/certification.js +23 -1
- package/src/lib/compose-context.js +156 -0
- package/src/lib/compose-pack.js +42 -0
- package/src/lib/compose-runtime.js +34 -0
- package/src/lib/compose-upload.js +142 -0
- package/src/lib/config.js +2 -2
- package/src/lib/dom-capture.js +64 -0
- package/src/lib/record-clip.js +83 -3
- package/src/lib/record-config.js +0 -4
- package/src/lib/resolve-targets.js +60 -0
- package/src/lib/run-manifest.js +45 -0
- package/src/lib/ui-api-helpers.js +118 -0
- package/src/lib/ui-api.js +28 -820
- package/src/lib/ui-asset-cleanup.js +62 -0
- package/src/lib/ui-output-versions.js +165 -0
- package/src/lib/ui-recorder-routes.js +341 -0
- package/src/lib/ui-scenario-metadata.js +161 -0
- package/vendor/compose/dist/auto-update.cjs +5544 -0
- package/vendor/compose/dist/auto-update.mjs +5518 -0
- package/vendor/compose/dist/capture.cjs +1450 -0
- package/vendor/compose/dist/capture.mjs +1416 -0
- package/vendor/compose/dist/eligibility.cjs +5331 -0
- package/vendor/compose/dist/eligibility.mjs +5313 -0
- package/vendor/compose/dist/index.cjs +2046 -0
- package/vendor/compose/dist/index.mjs +1997 -0
- package/vendor/compose/dist/jsx-dev-runtime.cjs +55 -0
- package/vendor/compose/dist/jsx-dev-runtime.mjs +27 -0
- package/vendor/compose/dist/jsx-runtime.cjs +58 -0
- package/vendor/compose/dist/jsx-runtime.mjs +31 -0
- package/vendor/compose/dist/render.cjs +558 -0
- package/vendor/compose/dist/render.mjs +515 -0
- package/vendor/compose/dist/verify-cli.cjs +3806 -0
- package/vendor/compose/dist/verify-cli.mjs +3812 -0
- package/vendor/compose/dist/verify.cjs +3880 -0
- package/vendor/compose/dist/verify.mjs +3858 -0
- package/web/manager/dist/assets/{index-CvleJUur.js → index-D0S2otug.js} +56 -56
- package/web/manager/dist/index.html +1 -1
- package/src/commands/ingest.js +0 -458
- package/src/commands/setup.js +0 -165
- package/src/lib/playwright-runner.js +0 -252
|
@@ -0,0 +1,1450 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/capture/index.ts
|
|
21
|
+
var capture_exports = {};
|
|
22
|
+
__export(capture_exports, {
|
|
23
|
+
DEFAULT_CAPTURE_SETTINGS: () => DEFAULT_CAPTURE_SETTINGS,
|
|
24
|
+
captureDom: () => captureDom,
|
|
25
|
+
captureUrl: () => captureUrl,
|
|
26
|
+
launchBrowser: () => launchBrowser,
|
|
27
|
+
liveScreenshot: () => liveScreenshot,
|
|
28
|
+
navigate: () => navigate,
|
|
29
|
+
openPage: () => openPage,
|
|
30
|
+
remountScreenshot: () => remountScreenshot,
|
|
31
|
+
renderArtifactOffline: () => renderArtifactOffline,
|
|
32
|
+
settle: () => settle,
|
|
33
|
+
shouldFallbackToM4: () => shouldFallbackToM4,
|
|
34
|
+
writeArtifact: () => writeArtifact
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(capture_exports);
|
|
37
|
+
var import_promises2 = require("fs/promises");
|
|
38
|
+
var import_node_path2 = require("path");
|
|
39
|
+
|
|
40
|
+
// src/capture/resources.ts
|
|
41
|
+
var MAX_RESOURCE_BYTES = 4e6;
|
|
42
|
+
async function resolveResourceUris(page, urls) {
|
|
43
|
+
const unique = [.../* @__PURE__ */ new Set([...urls])].filter((u) => u && !u.startsWith("data:"));
|
|
44
|
+
const out = {};
|
|
45
|
+
await Promise.all(
|
|
46
|
+
unique.map(async (u) => {
|
|
47
|
+
try {
|
|
48
|
+
const resp = await page.request.get(u, { timeout: 8e3 });
|
|
49
|
+
if (!resp.ok()) return;
|
|
50
|
+
const buf = await resp.body();
|
|
51
|
+
if (buf.length > MAX_RESOURCE_BYTES) return;
|
|
52
|
+
const ct = resp.headers()["content-type"] || "application/octet-stream";
|
|
53
|
+
out[u] = `data:${ct.split(";")[0]};base64,${buf.toString("base64")}`;
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
function inlineResolvedUris(html, dataUris) {
|
|
61
|
+
let out = html;
|
|
62
|
+
for (const [u, d] of Object.entries(dataUris)) {
|
|
63
|
+
out = out.split(u).join(d);
|
|
64
|
+
const escaped = u.replace(/&/g, "&");
|
|
65
|
+
if (escaped !== u) out = out.split(escaped).join(d);
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/capture/m4-domsnapshot.ts
|
|
71
|
+
var PROPS = [
|
|
72
|
+
"display",
|
|
73
|
+
"position",
|
|
74
|
+
"top",
|
|
75
|
+
"right",
|
|
76
|
+
"bottom",
|
|
77
|
+
"left",
|
|
78
|
+
"float",
|
|
79
|
+
"clear",
|
|
80
|
+
"z-index",
|
|
81
|
+
"box-sizing",
|
|
82
|
+
"width",
|
|
83
|
+
"height",
|
|
84
|
+
"min-width",
|
|
85
|
+
"min-height",
|
|
86
|
+
"max-width",
|
|
87
|
+
"max-height",
|
|
88
|
+
"margin-top",
|
|
89
|
+
"margin-right",
|
|
90
|
+
"margin-bottom",
|
|
91
|
+
"margin-left",
|
|
92
|
+
"padding-top",
|
|
93
|
+
"padding-right",
|
|
94
|
+
"padding-bottom",
|
|
95
|
+
"padding-left",
|
|
96
|
+
"border-top-width",
|
|
97
|
+
"border-right-width",
|
|
98
|
+
"border-bottom-width",
|
|
99
|
+
"border-left-width",
|
|
100
|
+
"border-top-style",
|
|
101
|
+
"border-right-style",
|
|
102
|
+
"border-bottom-style",
|
|
103
|
+
"border-left-style",
|
|
104
|
+
"border-top-color",
|
|
105
|
+
"border-right-color",
|
|
106
|
+
"border-bottom-color",
|
|
107
|
+
"border-left-color",
|
|
108
|
+
"border-top-left-radius",
|
|
109
|
+
"border-top-right-radius",
|
|
110
|
+
"border-bottom-left-radius",
|
|
111
|
+
"border-bottom-right-radius",
|
|
112
|
+
"outline-width",
|
|
113
|
+
"outline-style",
|
|
114
|
+
"outline-color",
|
|
115
|
+
"outline-offset",
|
|
116
|
+
"color",
|
|
117
|
+
"background-color",
|
|
118
|
+
"background-image",
|
|
119
|
+
"background-position",
|
|
120
|
+
"background-size",
|
|
121
|
+
"background-repeat",
|
|
122
|
+
"background-origin",
|
|
123
|
+
"background-clip",
|
|
124
|
+
"background-attachment",
|
|
125
|
+
"-webkit-background-clip",
|
|
126
|
+
"-webkit-text-fill-color",
|
|
127
|
+
"opacity",
|
|
128
|
+
"visibility",
|
|
129
|
+
"overflow-x",
|
|
130
|
+
"overflow-y",
|
|
131
|
+
"font-family",
|
|
132
|
+
"font-size",
|
|
133
|
+
"font-weight",
|
|
134
|
+
"font-style",
|
|
135
|
+
"font-stretch",
|
|
136
|
+
"font-variant",
|
|
137
|
+
"line-height",
|
|
138
|
+
"letter-spacing",
|
|
139
|
+
"word-spacing",
|
|
140
|
+
"text-align",
|
|
141
|
+
"text-transform",
|
|
142
|
+
"text-decoration",
|
|
143
|
+
"text-decoration-color",
|
|
144
|
+
"text-decoration-line",
|
|
145
|
+
"text-decoration-style",
|
|
146
|
+
"text-shadow",
|
|
147
|
+
"text-indent",
|
|
148
|
+
"white-space",
|
|
149
|
+
"word-break",
|
|
150
|
+
"overflow-wrap",
|
|
151
|
+
"vertical-align",
|
|
152
|
+
"list-style",
|
|
153
|
+
"direction",
|
|
154
|
+
"writing-mode",
|
|
155
|
+
"unicode-bidi",
|
|
156
|
+
"box-shadow",
|
|
157
|
+
"filter",
|
|
158
|
+
"backdrop-filter",
|
|
159
|
+
"mix-blend-mode",
|
|
160
|
+
"isolation",
|
|
161
|
+
"transform",
|
|
162
|
+
"transform-origin",
|
|
163
|
+
"perspective",
|
|
164
|
+
"transform-style",
|
|
165
|
+
"clip-path",
|
|
166
|
+
"-webkit-clip-path",
|
|
167
|
+
"mask",
|
|
168
|
+
"-webkit-mask",
|
|
169
|
+
"-webkit-mask-image",
|
|
170
|
+
"flex-direction",
|
|
171
|
+
"flex-wrap",
|
|
172
|
+
"flex-grow",
|
|
173
|
+
"flex-shrink",
|
|
174
|
+
"flex-basis",
|
|
175
|
+
"order",
|
|
176
|
+
"justify-content",
|
|
177
|
+
"align-items",
|
|
178
|
+
"align-content",
|
|
179
|
+
"align-self",
|
|
180
|
+
"justify-self",
|
|
181
|
+
"gap",
|
|
182
|
+
"row-gap",
|
|
183
|
+
"column-gap",
|
|
184
|
+
"grid-template-columns",
|
|
185
|
+
"grid-template-rows",
|
|
186
|
+
"grid-template-areas",
|
|
187
|
+
"grid-column",
|
|
188
|
+
"grid-row",
|
|
189
|
+
"grid-auto-flow",
|
|
190
|
+
"grid-auto-columns",
|
|
191
|
+
"grid-auto-rows",
|
|
192
|
+
"aspect-ratio",
|
|
193
|
+
"object-fit",
|
|
194
|
+
"object-position",
|
|
195
|
+
"content",
|
|
196
|
+
"border-collapse",
|
|
197
|
+
"border-spacing",
|
|
198
|
+
"table-layout"
|
|
199
|
+
];
|
|
200
|
+
var NOISE = /* @__PURE__ */ new Set(["auto", "normal", "none", ""]);
|
|
201
|
+
var SVG_TAGS = /* @__PURE__ */ new Set([
|
|
202
|
+
"svg",
|
|
203
|
+
"g",
|
|
204
|
+
"path",
|
|
205
|
+
"rect",
|
|
206
|
+
"circle",
|
|
207
|
+
"ellipse",
|
|
208
|
+
"line",
|
|
209
|
+
"polyline",
|
|
210
|
+
"polygon",
|
|
211
|
+
"text",
|
|
212
|
+
"tspan",
|
|
213
|
+
"defs",
|
|
214
|
+
"use",
|
|
215
|
+
"symbol",
|
|
216
|
+
"clippath",
|
|
217
|
+
"lineargradient",
|
|
218
|
+
"radialgradient",
|
|
219
|
+
"stop",
|
|
220
|
+
"mask",
|
|
221
|
+
"pattern",
|
|
222
|
+
"image",
|
|
223
|
+
"marker",
|
|
224
|
+
"filter",
|
|
225
|
+
"fegaussianblur",
|
|
226
|
+
"feoffset",
|
|
227
|
+
"feblend",
|
|
228
|
+
"femerge",
|
|
229
|
+
"femergenode",
|
|
230
|
+
"fecolormatrix",
|
|
231
|
+
"fecomposite",
|
|
232
|
+
"fedropshadow",
|
|
233
|
+
"foreignobject",
|
|
234
|
+
"title",
|
|
235
|
+
"desc",
|
|
236
|
+
"textpath"
|
|
237
|
+
]);
|
|
238
|
+
var VOID = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]);
|
|
239
|
+
var escAttr = (s) => String(s).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
240
|
+
var escText = (s) => String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
241
|
+
function harvestFontFaceInPage() {
|
|
242
|
+
const sameOrigin = (u) => {
|
|
243
|
+
try {
|
|
244
|
+
return new URL(u, location.href).origin === location.origin;
|
|
245
|
+
} catch {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
const blocks = [];
|
|
250
|
+
const tasks = [];
|
|
251
|
+
const mime = (u) => /\.woff2/i.test(u) ? "woff2" : /\.woff/i.test(u) ? "woff" : /\.(ttf|truetype)/i.test(u) ? "truetype" : /\.(otf|opentype)/i.test(u) ? "opentype" : "woff2";
|
|
252
|
+
async function toDataUri(abs) {
|
|
253
|
+
try {
|
|
254
|
+
const r = await fetch(abs, { mode: sameOrigin(abs) ? "same-origin" : "cors" });
|
|
255
|
+
if (!r.ok) return null;
|
|
256
|
+
const b = await r.blob();
|
|
257
|
+
return await new Promise((res) => {
|
|
258
|
+
const fr = new FileReader();
|
|
259
|
+
fr.onload = () => res(fr.result);
|
|
260
|
+
fr.onerror = () => res(null);
|
|
261
|
+
fr.readAsDataURL(b);
|
|
262
|
+
});
|
|
263
|
+
} catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function fromText(cssText, base) {
|
|
268
|
+
const re = /@font-face\s*\{([^}]*)\}/gi;
|
|
269
|
+
let m;
|
|
270
|
+
while (m = re.exec(cssText)) {
|
|
271
|
+
const body = m[1] ?? "";
|
|
272
|
+
const g = (p) => {
|
|
273
|
+
const r = new RegExp(p + "\\s*:\\s*([^;]+)", "i").exec(body);
|
|
274
|
+
return r ? (r[1] ?? "").trim() : "";
|
|
275
|
+
};
|
|
276
|
+
const um = g("src").match(/url\((['"]?)([^'")]+)\1\)/);
|
|
277
|
+
if (!um) continue;
|
|
278
|
+
let abs;
|
|
279
|
+
try {
|
|
280
|
+
abs = new URL(um[2], base).href;
|
|
281
|
+
} catch {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const idx = blocks.length;
|
|
285
|
+
blocks.push(null);
|
|
286
|
+
tasks.push(toDataUri(abs).then((d) => {
|
|
287
|
+
if (d) blocks[idx] = `@font-face{font-family:${g("font-family")};font-style:${g("font-style") || "normal"};font-weight:${g("font-weight") || "normal"};font-display:block;src:url(${d}) format('${mime(abs)}');}`;
|
|
288
|
+
}));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function walk(sheet) {
|
|
292
|
+
let rules;
|
|
293
|
+
try {
|
|
294
|
+
rules = sheet.cssRules;
|
|
295
|
+
} catch {
|
|
296
|
+
if (sheet.href) tasks.push(fetch(sheet.href, { mode: "cors" }).then((r) => r.ok ? r.text() : "").then((t) => t && fromText(t, sheet.href)).catch(() => {
|
|
297
|
+
}));
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (!rules) return;
|
|
301
|
+
for (const rule of Array.from(rules)) {
|
|
302
|
+
if (rule.type === CSSRule.IMPORT_RULE) {
|
|
303
|
+
const ir = rule;
|
|
304
|
+
let ok = false;
|
|
305
|
+
try {
|
|
306
|
+
ok = !!(ir.styleSheet && ir.styleSheet.cssRules);
|
|
307
|
+
} catch {
|
|
308
|
+
}
|
|
309
|
+
if (ok && ir.styleSheet) walk(ir.styleSheet);
|
|
310
|
+
else {
|
|
311
|
+
let h = ir.href;
|
|
312
|
+
try {
|
|
313
|
+
h = new URL(ir.href, sheet.href || location.href).href;
|
|
314
|
+
} catch {
|
|
315
|
+
}
|
|
316
|
+
if (h) tasks.push(fetch(h, { mode: "cors" }).then((r) => r.ok ? r.text() : "").then((t) => t && fromText(t, h)).catch(() => {
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
} else if (rule.type === CSSRule.FONT_FACE_RULE) {
|
|
320
|
+
const fr = rule;
|
|
321
|
+
const src = fr.style.getPropertyValue("src");
|
|
322
|
+
if (!src) continue;
|
|
323
|
+
const um = src.match(/url\((['"]?)([^'")]+)\1\)/);
|
|
324
|
+
if (!um) continue;
|
|
325
|
+
if (um[2].startsWith("data:")) {
|
|
326
|
+
blocks.push(`@font-face{${fr.style.cssText}}`);
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
let abs;
|
|
330
|
+
try {
|
|
331
|
+
abs = new URL(um[2], sheet.href || location.href).href;
|
|
332
|
+
} catch {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
const fam = fr.style.getPropertyValue("font-family");
|
|
336
|
+
const wt = fr.style.getPropertyValue("font-weight") || "normal";
|
|
337
|
+
const st = fr.style.getPropertyValue("font-style") || "normal";
|
|
338
|
+
const idx = blocks.length;
|
|
339
|
+
blocks.push(null);
|
|
340
|
+
tasks.push(toDataUri(abs).then((d) => {
|
|
341
|
+
if (d) blocks[idx] = `@font-face{font-family:${fam};font-style:${st};font-weight:${wt};font-display:block;src:url(${d}) format('${mime(abs)}');}`;
|
|
342
|
+
}));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
for (const s of Array.from(document.styleSheets)) walk(s);
|
|
347
|
+
return Promise.all(tasks).then(() => blocks.filter(Boolean).join("\n"));
|
|
348
|
+
}
|
|
349
|
+
async function snapshotM4(page) {
|
|
350
|
+
const client = await page.context().newCDPSession(page);
|
|
351
|
+
let snap;
|
|
352
|
+
try {
|
|
353
|
+
await client.send("DOMSnapshot.enable").catch(() => {
|
|
354
|
+
});
|
|
355
|
+
snap = await client.send("DOMSnapshot.captureSnapshot", {
|
|
356
|
+
computedStyles: PROPS,
|
|
357
|
+
includePaintOrder: true,
|
|
358
|
+
includeDOMRects: true
|
|
359
|
+
});
|
|
360
|
+
} finally {
|
|
361
|
+
await client.detach().catch(() => {
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
const strings = snap.strings;
|
|
365
|
+
const doc = snap.documents[0];
|
|
366
|
+
const nodes = doc.nodes;
|
|
367
|
+
const nodeType = nodes["nodeType"];
|
|
368
|
+
const nodeName = nodes["nodeName"];
|
|
369
|
+
const nodeValue = nodes["nodeValue"];
|
|
370
|
+
const parentIndex = nodes["parentIndex"];
|
|
371
|
+
const attributesArr = nodes["attributes"];
|
|
372
|
+
const pseudoType = nodes["pseudoType"];
|
|
373
|
+
const currentSourceURL = nodes["currentSourceURL"];
|
|
374
|
+
const inputValue = nodes["inputValue"];
|
|
375
|
+
const inputCheckedIndex = nodes.inputChecked?.index;
|
|
376
|
+
const layout = doc.layout;
|
|
377
|
+
const S = (i) => i != null && i >= 0 ? strings[i] : null;
|
|
378
|
+
const styleByNode = /* @__PURE__ */ new Map();
|
|
379
|
+
for (let j = 0; j < layout.nodeIndex.length; j++) {
|
|
380
|
+
const nodeIdx = layout.nodeIndex[j];
|
|
381
|
+
const styleArr = layout.styles[j];
|
|
382
|
+
if (!styleArr) continue;
|
|
383
|
+
let css = "";
|
|
384
|
+
const bag = {};
|
|
385
|
+
for (let p = 0; p < PROPS.length; p++) {
|
|
386
|
+
const v = S(styleArr[p]);
|
|
387
|
+
if (v == null) continue;
|
|
388
|
+
bag[PROPS[p]] = v;
|
|
389
|
+
if (NOISE.has(v)) continue;
|
|
390
|
+
css += `${PROPS[p]}:${v};`;
|
|
391
|
+
}
|
|
392
|
+
styleByNode.set(nodeIdx, { css, bag });
|
|
393
|
+
}
|
|
394
|
+
const children = /* @__PURE__ */ new Map();
|
|
395
|
+
for (let i = 0; i < parentIndex.length; i++) {
|
|
396
|
+
const p = parentIndex[i];
|
|
397
|
+
if (p < 0) continue;
|
|
398
|
+
if (!children.has(p)) children.set(p, []);
|
|
399
|
+
children.get(p).push(i);
|
|
400
|
+
}
|
|
401
|
+
function attrsOf(i) {
|
|
402
|
+
const flat = attributesArr[i] || [];
|
|
403
|
+
const m = /* @__PURE__ */ new Map();
|
|
404
|
+
for (let k = 0; k + 1 < flat.length; k += 2) {
|
|
405
|
+
const name = S(flat[k]);
|
|
406
|
+
const val = S(flat[k + 1]);
|
|
407
|
+
if (name != null) m.set(name, val ?? "");
|
|
408
|
+
}
|
|
409
|
+
return m;
|
|
410
|
+
}
|
|
411
|
+
const resourceUrls = /* @__PURE__ */ new Set();
|
|
412
|
+
const baseURL = doc.baseURL != null ? S(doc.baseURL) : doc.documentURL != null ? S(doc.documentURL) : null;
|
|
413
|
+
const absUrl = (u) => {
|
|
414
|
+
if (!u || u.startsWith("data:")) return null;
|
|
415
|
+
try {
|
|
416
|
+
return new URL(u, baseURL || void 0).href;
|
|
417
|
+
} catch {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
function emit(i, inSvg) {
|
|
422
|
+
const type = nodeType[i];
|
|
423
|
+
if (type === 3) {
|
|
424
|
+
const v = S(nodeValue[i]);
|
|
425
|
+
return v != null ? escText(v) : "";
|
|
426
|
+
}
|
|
427
|
+
if (type === 8) return "";
|
|
428
|
+
if (type !== 1) return (children.get(i) || []).map((c) => emit(c, inSvg)).join("");
|
|
429
|
+
const rawName = S(nodeName[i]) || "div";
|
|
430
|
+
const tag = rawName.toLowerCase();
|
|
431
|
+
const pseudo = pseudoType && pseudoType[i] != null ? S(pseudoType[i]) : null;
|
|
432
|
+
if (tag === "script" || tag === "noscript" || tag === "link" || tag === "meta" || tag === "base" || tag === "title") {
|
|
433
|
+
return "";
|
|
434
|
+
}
|
|
435
|
+
const styleEntry = styleByNode.get(i);
|
|
436
|
+
const bag = styleEntry ? styleEntry.bag : {};
|
|
437
|
+
if (pseudo === "before" || pseudo === "after") {
|
|
438
|
+
const content = bag["content"];
|
|
439
|
+
if (!content || content === "none" || content === "normal") return "";
|
|
440
|
+
const css2 = styleEntry ? styleEntry.css : "";
|
|
441
|
+
const txt = String(content).replace(/^["']|["']$/g, "");
|
|
442
|
+
const safeTxt = txt && txt !== "counter" && !txt.startsWith("url(") ? escText(txt) : "";
|
|
443
|
+
return `<span data-pseudo="::${pseudo}" style="${escAttr(css2)}">${safeTxt}</span>`;
|
|
444
|
+
}
|
|
445
|
+
if (pseudo) return "";
|
|
446
|
+
const nowSvg = inSvg || tag === "svg" || SVG_TAGS.has(tag);
|
|
447
|
+
if (tag === "style") {
|
|
448
|
+
const inner = (children.get(i) || []).map((c) => emit(c, false)).join("");
|
|
449
|
+
return `<style>${inner}</style>`;
|
|
450
|
+
}
|
|
451
|
+
const attrs = attrsOf(i);
|
|
452
|
+
let attrStr = "";
|
|
453
|
+
for (const a of [
|
|
454
|
+
"class",
|
|
455
|
+
"id",
|
|
456
|
+
"dir",
|
|
457
|
+
"lang",
|
|
458
|
+
"role",
|
|
459
|
+
"viewBox",
|
|
460
|
+
"width",
|
|
461
|
+
"height",
|
|
462
|
+
"d",
|
|
463
|
+
"points",
|
|
464
|
+
"x",
|
|
465
|
+
"y",
|
|
466
|
+
"x1",
|
|
467
|
+
"y1",
|
|
468
|
+
"x2",
|
|
469
|
+
"y2",
|
|
470
|
+
"cx",
|
|
471
|
+
"cy",
|
|
472
|
+
"r",
|
|
473
|
+
"rx",
|
|
474
|
+
"ry",
|
|
475
|
+
"fill",
|
|
476
|
+
"stroke",
|
|
477
|
+
"stroke-width",
|
|
478
|
+
"transform",
|
|
479
|
+
"offset",
|
|
480
|
+
"stop-color",
|
|
481
|
+
"gradientUnits",
|
|
482
|
+
"href",
|
|
483
|
+
"xlink:href",
|
|
484
|
+
"preserveAspectRatio",
|
|
485
|
+
"clip-path",
|
|
486
|
+
"aria-hidden"
|
|
487
|
+
]) {
|
|
488
|
+
if (attrs.has(a)) attrStr += ` ${a}="${escAttr(attrs.get(a))}"`;
|
|
489
|
+
}
|
|
490
|
+
const css = styleEntry ? styleEntry.css : "";
|
|
491
|
+
const bg = bag["background-image"];
|
|
492
|
+
if (bg && bg.includes("url(")) {
|
|
493
|
+
const m = bg.match(/url\((['"]?)(.*?)\1\)/);
|
|
494
|
+
const abs = m && absUrl(m[2] ?? null);
|
|
495
|
+
if (abs) resourceUrls.add(abs);
|
|
496
|
+
}
|
|
497
|
+
if (tag === "img") {
|
|
498
|
+
const src = currentSourceURL && S(currentSourceURL[i]) || attrs.get("src") || "";
|
|
499
|
+
const abs = absUrl(src);
|
|
500
|
+
if (abs) {
|
|
501
|
+
resourceUrls.add(abs);
|
|
502
|
+
attrStr += ` src="${escAttr(abs)}"`;
|
|
503
|
+
} else if (src) attrStr += ` src="${escAttr(src)}"`;
|
|
504
|
+
if (attrs.has("alt")) attrStr += ` alt="${escAttr(attrs.get("alt"))}"`;
|
|
505
|
+
return `<img${attrStr} style="${escAttr(css)}">`;
|
|
506
|
+
}
|
|
507
|
+
if (tag === "input" || tag === "textarea" || tag === "select") {
|
|
508
|
+
const iv = inputValue && S(inputValue[i]);
|
|
509
|
+
if (iv != null) attrStr += ` value="${escAttr(iv)}"`;
|
|
510
|
+
if (inputCheckedIndex && inputCheckedIndex.includes(i)) attrStr += " checked";
|
|
511
|
+
if (attrs.has("type")) attrStr += ` type="${escAttr(attrs.get("type"))}"`;
|
|
512
|
+
if (attrs.has("placeholder")) attrStr += ` placeholder="${escAttr(attrs.get("placeholder"))}"`;
|
|
513
|
+
}
|
|
514
|
+
const styleAttr = css ? ` style="${escAttr(css)}"` : "";
|
|
515
|
+
const open = `<${tag}${attrStr}${styleAttr}>`;
|
|
516
|
+
if (VOID.has(tag)) return open;
|
|
517
|
+
const kids = (children.get(i) || []).map((c) => emit(c, nowSvg)).join("");
|
|
518
|
+
return `${open}${kids}</${tag}>`;
|
|
519
|
+
}
|
|
520
|
+
let htmlIdx = -1, bodyIdx = -1, headIdx = -1;
|
|
521
|
+
for (let i = 0; i < nodeName.length; i++) {
|
|
522
|
+
const nm = (S(nodeName[i]) || "").toLowerCase();
|
|
523
|
+
if (nm === "html" && htmlIdx < 0) htmlIdx = i;
|
|
524
|
+
if (nm === "body" && bodyIdx < 0) bodyIdx = i;
|
|
525
|
+
if (nm === "head" && headIdx < 0) headIdx = i;
|
|
526
|
+
}
|
|
527
|
+
const htmlStyle = htmlIdx >= 0 && styleByNode.get(htmlIdx) ? styleByNode.get(htmlIdx).css : "";
|
|
528
|
+
const bgColor = (htmlIdx >= 0 ? styleByNode.get(htmlIdx)?.bag["background-color"] : void 0) || (bodyIdx >= 0 ? styleByNode.get(bodyIdx)?.bag["background-color"] : void 0) || "#ffffff";
|
|
529
|
+
let headStyles = "";
|
|
530
|
+
if (headIdx >= 0) {
|
|
531
|
+
for (const c of children.get(headIdx) || []) {
|
|
532
|
+
if ((S(nodeName[c]) || "").toLowerCase() === "style") headStyles += emit(c, false);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
const bodyHtml = bodyIdx >= 0 ? emit(bodyIdx, false) : "";
|
|
536
|
+
const fontFaceCss = await page.evaluate(harvestFontFaceInPage).catch(() => "");
|
|
537
|
+
const dataUris = await resolveResourceUris(page, resourceUrls);
|
|
538
|
+
let html = bodyHtml;
|
|
539
|
+
for (const [u, d] of Object.entries(dataUris)) {
|
|
540
|
+
html = html.split(`src="${escAttr(u)}"`).join(`src="${d}"`);
|
|
541
|
+
html = html.split(u).join(d);
|
|
542
|
+
}
|
|
543
|
+
const out = `<!doctype html><html style="${escAttr(htmlStyle)}"><head><meta charset="utf-8"><meta name="viewport" content="width=${doc.contentWidth || ""}"><style>html,body{margin:0;background:${bgColor};}*{box-sizing:border-box;}[data-pseudo]{display:inline-block;}</style>` + (headStyles ? `<style>${headStyles}</style>` : "") + (fontFaceCss ? `<style>${fontFaceCss}</style>` : "") + `</head>${html}</html>`;
|
|
544
|
+
return {
|
|
545
|
+
method: "m4",
|
|
546
|
+
html: out,
|
|
547
|
+
scrolls: [{ sel: ":root", x: doc.scrollOffsetX || 0, y: doc.scrollOffsetY || 0 }],
|
|
548
|
+
surfaces: [],
|
|
549
|
+
notes: `CDP DOMSnapshot: ${layout.nodeIndex.length} laid-out nodes, ${Object.keys(dataUris).length} resources inlined; canvas/webgl/video are blind (structural method)`
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// src/capture/serialize.ts
|
|
554
|
+
function serializeHybridInPage() {
|
|
555
|
+
const PROPS2 = [
|
|
556
|
+
"display",
|
|
557
|
+
"position",
|
|
558
|
+
"top",
|
|
559
|
+
"right",
|
|
560
|
+
"bottom",
|
|
561
|
+
"left",
|
|
562
|
+
"float",
|
|
563
|
+
"clear",
|
|
564
|
+
"z-index",
|
|
565
|
+
"box-sizing",
|
|
566
|
+
"width",
|
|
567
|
+
"height",
|
|
568
|
+
"min-width",
|
|
569
|
+
"min-height",
|
|
570
|
+
"max-width",
|
|
571
|
+
"max-height",
|
|
572
|
+
"margin-top",
|
|
573
|
+
"margin-right",
|
|
574
|
+
"margin-bottom",
|
|
575
|
+
"margin-left",
|
|
576
|
+
"padding-top",
|
|
577
|
+
"padding-right",
|
|
578
|
+
"padding-bottom",
|
|
579
|
+
"padding-left",
|
|
580
|
+
"border-top-width",
|
|
581
|
+
"border-right-width",
|
|
582
|
+
"border-bottom-width",
|
|
583
|
+
"border-left-width",
|
|
584
|
+
"border-top-style",
|
|
585
|
+
"border-right-style",
|
|
586
|
+
"border-bottom-style",
|
|
587
|
+
"border-left-style",
|
|
588
|
+
"border-top-color",
|
|
589
|
+
"border-right-color",
|
|
590
|
+
"border-bottom-color",
|
|
591
|
+
"border-left-color",
|
|
592
|
+
"border-top-left-radius",
|
|
593
|
+
"border-top-right-radius",
|
|
594
|
+
"border-bottom-left-radius",
|
|
595
|
+
"border-bottom-right-radius",
|
|
596
|
+
"outline-width",
|
|
597
|
+
"outline-style",
|
|
598
|
+
"outline-color",
|
|
599
|
+
"outline-offset",
|
|
600
|
+
"color",
|
|
601
|
+
"background-color",
|
|
602
|
+
"background-image",
|
|
603
|
+
"background-position",
|
|
604
|
+
"background-size",
|
|
605
|
+
"background-repeat",
|
|
606
|
+
"background-origin",
|
|
607
|
+
"background-clip",
|
|
608
|
+
"background-attachment",
|
|
609
|
+
"-webkit-background-clip",
|
|
610
|
+
"-webkit-text-fill-color",
|
|
611
|
+
"opacity",
|
|
612
|
+
"visibility",
|
|
613
|
+
"overflow",
|
|
614
|
+
"overflow-x",
|
|
615
|
+
"overflow-y",
|
|
616
|
+
"font-family",
|
|
617
|
+
"font-size",
|
|
618
|
+
"font-weight",
|
|
619
|
+
"font-style",
|
|
620
|
+
"font-stretch",
|
|
621
|
+
"font-variant",
|
|
622
|
+
"line-height",
|
|
623
|
+
"letter-spacing",
|
|
624
|
+
"word-spacing",
|
|
625
|
+
"text-align",
|
|
626
|
+
"text-transform",
|
|
627
|
+
"text-decoration",
|
|
628
|
+
"text-decoration-color",
|
|
629
|
+
"text-decoration-line",
|
|
630
|
+
"text-decoration-style",
|
|
631
|
+
"text-shadow",
|
|
632
|
+
"text-indent",
|
|
633
|
+
"white-space",
|
|
634
|
+
"word-break",
|
|
635
|
+
"overflow-wrap",
|
|
636
|
+
"vertical-align",
|
|
637
|
+
"list-style",
|
|
638
|
+
"direction",
|
|
639
|
+
"writing-mode",
|
|
640
|
+
"unicode-bidi",
|
|
641
|
+
"box-shadow",
|
|
642
|
+
"filter",
|
|
643
|
+
"backdrop-filter",
|
|
644
|
+
"mix-blend-mode",
|
|
645
|
+
"isolation",
|
|
646
|
+
"transform",
|
|
647
|
+
"transform-origin",
|
|
648
|
+
"perspective",
|
|
649
|
+
"transform-style",
|
|
650
|
+
"clip-path",
|
|
651
|
+
"-webkit-clip-path",
|
|
652
|
+
"mask",
|
|
653
|
+
"-webkit-mask",
|
|
654
|
+
"-webkit-mask-image",
|
|
655
|
+
"mask-image",
|
|
656
|
+
"mask-position",
|
|
657
|
+
"mask-size",
|
|
658
|
+
"mask-repeat",
|
|
659
|
+
"mask-mode",
|
|
660
|
+
"mask-composite",
|
|
661
|
+
"mask-clip",
|
|
662
|
+
"mask-origin",
|
|
663
|
+
"flex-direction",
|
|
664
|
+
"flex-wrap",
|
|
665
|
+
"flex-grow",
|
|
666
|
+
"flex-shrink",
|
|
667
|
+
"flex-basis",
|
|
668
|
+
"order",
|
|
669
|
+
"justify-content",
|
|
670
|
+
"align-items",
|
|
671
|
+
"align-content",
|
|
672
|
+
"align-self",
|
|
673
|
+
"gap",
|
|
674
|
+
"row-gap",
|
|
675
|
+
"column-gap",
|
|
676
|
+
"grid-template-columns",
|
|
677
|
+
"grid-template-rows",
|
|
678
|
+
"grid-template-areas",
|
|
679
|
+
"grid-column",
|
|
680
|
+
"grid-row",
|
|
681
|
+
"grid-auto-flow",
|
|
682
|
+
"grid-auto-columns",
|
|
683
|
+
"grid-auto-rows",
|
|
684
|
+
"aspect-ratio",
|
|
685
|
+
"object-fit",
|
|
686
|
+
"object-position",
|
|
687
|
+
"content",
|
|
688
|
+
"border-collapse",
|
|
689
|
+
"border-spacing",
|
|
690
|
+
"table-layout"
|
|
691
|
+
];
|
|
692
|
+
const SVGNS = "http://www.w3.org/2000/svg";
|
|
693
|
+
const XLINKNS = "http://www.w3.org/1999/xlink";
|
|
694
|
+
const sameOrigin = (url) => {
|
|
695
|
+
try {
|
|
696
|
+
return new URL(url, location.href).origin === location.origin;
|
|
697
|
+
} catch {
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
const fetchTasks = [];
|
|
702
|
+
const imgRefs = [];
|
|
703
|
+
const bgUrls = [];
|
|
704
|
+
let imgSeq = 0;
|
|
705
|
+
const toAbs = (url) => {
|
|
706
|
+
try {
|
|
707
|
+
return new URL(url, location.href).href;
|
|
708
|
+
} catch {
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
const styleCss = (el, pseudo) => {
|
|
713
|
+
const cs = getComputedStyle(el, pseudo ?? null);
|
|
714
|
+
let out = "";
|
|
715
|
+
for (const p of PROPS2) {
|
|
716
|
+
const v = cs.getPropertyValue(p);
|
|
717
|
+
if (v && !(v === "none" && p !== "content") && v !== "auto" && v !== "normal") out += `${p}:${v};`;
|
|
718
|
+
}
|
|
719
|
+
return { out, cs };
|
|
720
|
+
};
|
|
721
|
+
const externalSymbols = /* @__PURE__ */ new Map();
|
|
722
|
+
function inlineExternalUse(useEl) {
|
|
723
|
+
const href = useEl.getAttribute("href") || useEl.getAttributeNS(XLINKNS, "href") || useEl.getAttribute("xlink:href") || "";
|
|
724
|
+
if (!href || href.startsWith("#") || href.startsWith("data:")) return;
|
|
725
|
+
const hashIdx = href.indexOf("#");
|
|
726
|
+
if (hashIdx < 0) return;
|
|
727
|
+
const fileUrl = href.slice(0, hashIdx);
|
|
728
|
+
const symId = href.slice(hashIdx + 1);
|
|
729
|
+
fetchTasks.push(
|
|
730
|
+
(async () => {
|
|
731
|
+
try {
|
|
732
|
+
let abs;
|
|
733
|
+
try {
|
|
734
|
+
abs = new URL(fileUrl, location.href).href;
|
|
735
|
+
} catch {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const resp = await fetch(abs, { mode: sameOrigin(abs) ? "same-origin" : "cors" });
|
|
739
|
+
if (!resp.ok) return;
|
|
740
|
+
const text = await resp.text();
|
|
741
|
+
const doc = new DOMParser().parseFromString(text, "image/svg+xml");
|
|
742
|
+
const sym = doc.getElementById(symId);
|
|
743
|
+
if (sym) {
|
|
744
|
+
externalSymbols.set(symId, sym.outerHTML);
|
|
745
|
+
useEl.setAttribute("href", `#${symId}`);
|
|
746
|
+
}
|
|
747
|
+
} catch {
|
|
748
|
+
}
|
|
749
|
+
})()
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
const fontFaceTasks = [];
|
|
753
|
+
const fontFaceBlocks = [];
|
|
754
|
+
const mimeForFont = (url) => {
|
|
755
|
+
if (/\.woff2(\?|$)/i.test(url)) return "woff2";
|
|
756
|
+
if (/\.woff(\?|$)/i.test(url)) return "woff";
|
|
757
|
+
if (/\.(ttf|truetype)(\?|$)/i.test(url)) return "truetype";
|
|
758
|
+
if (/\.(otf|opentype)(\?|$)/i.test(url)) return "opentype";
|
|
759
|
+
return "woff2";
|
|
760
|
+
};
|
|
761
|
+
function emitFontFace(block) {
|
|
762
|
+
return `@font-face{font-family:${block.family};font-style:${block.style || "normal"};font-weight:${block.weight || "normal"};${block.stretch ? `font-stretch:${block.stretch};` : ""}${block.unicodeRange ? `unicode-range:${block.unicodeRange};` : ""}font-display:${block.display || "block"};src:url(${block.dataUri}) format('${block.fmt}');}`;
|
|
763
|
+
}
|
|
764
|
+
function collectFontFaceFromCssText(cssText, baseHref) {
|
|
765
|
+
const ffRe = /@font-face\s*\{([^}]*)\}/gi;
|
|
766
|
+
let m;
|
|
767
|
+
while (m = ffRe.exec(cssText)) {
|
|
768
|
+
const body2 = m[1] ?? "";
|
|
769
|
+
const get = (prop) => {
|
|
770
|
+
const r = new RegExp(prop + "\\s*:\\s*([^;]+)", "i").exec(body2);
|
|
771
|
+
return r ? (r[1] ?? "").trim() : "";
|
|
772
|
+
};
|
|
773
|
+
const src = get("src");
|
|
774
|
+
const urlMatch = src.match(/url\((['"]?)([^'")]+)\1\)/);
|
|
775
|
+
if (!urlMatch) continue;
|
|
776
|
+
const url = urlMatch[2];
|
|
777
|
+
const family = get("font-family");
|
|
778
|
+
const idx = fontFaceBlocks.length;
|
|
779
|
+
fontFaceBlocks.push(null);
|
|
780
|
+
fontFaceTasks.push(
|
|
781
|
+
(async () => {
|
|
782
|
+
let abs;
|
|
783
|
+
try {
|
|
784
|
+
abs = new URL(url, baseHref || location.href).href;
|
|
785
|
+
} catch {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
try {
|
|
789
|
+
const resp = await fetch(abs, { mode: sameOrigin(abs) ? "same-origin" : "cors" });
|
|
790
|
+
if (!resp.ok) return;
|
|
791
|
+
const blob = await resp.blob();
|
|
792
|
+
const dataUri = await new Promise((res) => {
|
|
793
|
+
const fr = new FileReader();
|
|
794
|
+
fr.onload = () => res(fr.result);
|
|
795
|
+
fr.onerror = () => res(null);
|
|
796
|
+
fr.readAsDataURL(blob);
|
|
797
|
+
});
|
|
798
|
+
if (!dataUri) return;
|
|
799
|
+
fontFaceBlocks[idx] = emitFontFace({
|
|
800
|
+
family,
|
|
801
|
+
style: get("font-style"),
|
|
802
|
+
weight: get("font-weight"),
|
|
803
|
+
stretch: get("font-stretch"),
|
|
804
|
+
unicodeRange: get("unicode-range"),
|
|
805
|
+
display: get("font-display"),
|
|
806
|
+
dataUri,
|
|
807
|
+
fmt: mimeForFont(abs)
|
|
808
|
+
});
|
|
809
|
+
} catch {
|
|
810
|
+
}
|
|
811
|
+
})()
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
function collectFontFaceRules(sheet) {
|
|
816
|
+
let rules;
|
|
817
|
+
try {
|
|
818
|
+
rules = sheet.cssRules;
|
|
819
|
+
} catch {
|
|
820
|
+
if (sheet.href) {
|
|
821
|
+
fontFaceTasks.push(
|
|
822
|
+
fetch(sheet.href, { mode: "cors" }).then((r) => r.ok ? r.text() : "").then((t) => {
|
|
823
|
+
if (t) collectFontFaceFromCssText(t, sheet.href);
|
|
824
|
+
}).catch(() => {
|
|
825
|
+
})
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
if (!rules) return;
|
|
831
|
+
for (const rule of Array.from(rules)) {
|
|
832
|
+
if (rule.type === CSSRule.IMPORT_RULE) {
|
|
833
|
+
const importRule = rule;
|
|
834
|
+
let importedReadable = false;
|
|
835
|
+
try {
|
|
836
|
+
importedReadable = !!(importRule.styleSheet && importRule.styleSheet.cssRules);
|
|
837
|
+
} catch {
|
|
838
|
+
importedReadable = false;
|
|
839
|
+
}
|
|
840
|
+
if (importedReadable && importRule.styleSheet) {
|
|
841
|
+
collectFontFaceRules(importRule.styleSheet);
|
|
842
|
+
} else {
|
|
843
|
+
let importHref = importRule.href;
|
|
844
|
+
try {
|
|
845
|
+
importHref = new URL(importRule.href, sheet.href || location.href).href;
|
|
846
|
+
} catch {
|
|
847
|
+
}
|
|
848
|
+
if (importHref) {
|
|
849
|
+
fontFaceTasks.push(
|
|
850
|
+
fetch(importHref, { mode: "cors" }).then((r) => r.ok ? r.text() : "").then((t) => {
|
|
851
|
+
if (t) collectFontFaceFromCssText(t, importHref);
|
|
852
|
+
}).catch(() => {
|
|
853
|
+
})
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
} else if (rule.type === CSSRule.FONT_FACE_RULE) {
|
|
858
|
+
const ffRule = rule;
|
|
859
|
+
const src = ffRule.style.getPropertyValue("src");
|
|
860
|
+
if (!src) continue;
|
|
861
|
+
const urlMatch = src.match(/url\((['"]?)([^'")]+)\1\)/);
|
|
862
|
+
if (!urlMatch) continue;
|
|
863
|
+
const url = urlMatch[2];
|
|
864
|
+
const family = ffRule.style.getPropertyValue("font-family");
|
|
865
|
+
const weight = ffRule.style.getPropertyValue("font-weight") || "normal";
|
|
866
|
+
const style = ffRule.style.getPropertyValue("font-style") || "normal";
|
|
867
|
+
const stretch = ffRule.style.getPropertyValue("font-stretch") || "";
|
|
868
|
+
const unicodeRange = ffRule.style.getPropertyValue("unicode-range") || "";
|
|
869
|
+
const display = ffRule.style.getPropertyValue("font-display") || "block";
|
|
870
|
+
if (url.startsWith("data:")) {
|
|
871
|
+
fontFaceBlocks.push(`@font-face{${ffRule.style.cssText}}`);
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
const idx = fontFaceBlocks.length;
|
|
875
|
+
fontFaceBlocks.push(null);
|
|
876
|
+
fontFaceTasks.push(
|
|
877
|
+
(async () => {
|
|
878
|
+
let abs;
|
|
879
|
+
try {
|
|
880
|
+
abs = new URL(url, sheet.href || location.href).href;
|
|
881
|
+
} catch {
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
try {
|
|
885
|
+
const resp = await fetch(abs, { mode: sameOrigin(abs) ? "same-origin" : "cors" });
|
|
886
|
+
if (!resp.ok) return;
|
|
887
|
+
const blob = await resp.blob();
|
|
888
|
+
const dataUri = await new Promise((res) => {
|
|
889
|
+
const fr = new FileReader();
|
|
890
|
+
fr.onload = () => res(fr.result);
|
|
891
|
+
fr.onerror = () => res(null);
|
|
892
|
+
fr.readAsDataURL(blob);
|
|
893
|
+
});
|
|
894
|
+
if (!dataUri) return;
|
|
895
|
+
fontFaceBlocks[idx] = emitFontFace({
|
|
896
|
+
family,
|
|
897
|
+
style,
|
|
898
|
+
weight,
|
|
899
|
+
stretch,
|
|
900
|
+
unicodeRange,
|
|
901
|
+
display,
|
|
902
|
+
dataUri,
|
|
903
|
+
fmt: mimeForFont(abs)
|
|
904
|
+
});
|
|
905
|
+
} catch {
|
|
906
|
+
}
|
|
907
|
+
})()
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
for (const sheet of Array.from(document.styleSheets)) collectFontFaceRules(sheet);
|
|
913
|
+
const surfaces = [];
|
|
914
|
+
let surfaceSeq = 0;
|
|
915
|
+
function rectOf(el) {
|
|
916
|
+
const r = el.getBoundingClientRect();
|
|
917
|
+
return { x: r.left + window.scrollX, y: r.top + window.scrollY, vx: r.left, vy: r.top, w: r.width, h: r.height };
|
|
918
|
+
}
|
|
919
|
+
function withContainingBlock(style, pos) {
|
|
920
|
+
const p = (pos || "").trim();
|
|
921
|
+
if (p && p !== "static") return style;
|
|
922
|
+
return `${style};position:relative`;
|
|
923
|
+
}
|
|
924
|
+
function tryCanvasDataUri(canvas) {
|
|
925
|
+
try {
|
|
926
|
+
return canvas.toDataURL("image/png");
|
|
927
|
+
} catch {
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
function tryVideoDataUri(video) {
|
|
932
|
+
try {
|
|
933
|
+
const c = document.createElement("canvas");
|
|
934
|
+
c.width = video.videoWidth || video.clientWidth;
|
|
935
|
+
c.height = video.videoHeight || video.clientHeight;
|
|
936
|
+
if (!c.width || !c.height) return null;
|
|
937
|
+
c.getContext("2d").drawImage(video, 0, 0, c.width, c.height);
|
|
938
|
+
return c.toDataURL("image/png");
|
|
939
|
+
} catch {
|
|
940
|
+
return null;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function isUnserializable(el) {
|
|
944
|
+
const tag = el.tagName.toLowerCase();
|
|
945
|
+
if (tag === "canvas") return "canvas";
|
|
946
|
+
if (tag === "video") return "video";
|
|
947
|
+
if (tag === "iframe") {
|
|
948
|
+
const src = el.getAttribute("src") || "";
|
|
949
|
+
if (src && !sameOrigin(src)) return "iframe";
|
|
950
|
+
}
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
function clone(el) {
|
|
954
|
+
const kind = isUnserializable(el);
|
|
955
|
+
if (kind) {
|
|
956
|
+
const id = `hyb${surfaceSeq++}`;
|
|
957
|
+
const rect = rectOf(el);
|
|
958
|
+
let dataUri = null;
|
|
959
|
+
if (kind === "canvas") dataUri = tryCanvasDataUri(el);
|
|
960
|
+
else if (kind === "video") dataUri = tryVideoDataUri(el);
|
|
961
|
+
const { out: out2, cs: pcs } = styleCss(el);
|
|
962
|
+
const surface = {
|
|
963
|
+
id,
|
|
964
|
+
kind,
|
|
965
|
+
rect,
|
|
966
|
+
dataUri,
|
|
967
|
+
needPlaywrightShot: !dataUri,
|
|
968
|
+
clip: {
|
|
969
|
+
borderRadius: pcs.getPropertyValue("border-top-left-radius") + " " + pcs.getPropertyValue("border-top-right-radius") + " " + pcs.getPropertyValue("border-bottom-right-radius") + " " + pcs.getPropertyValue("border-bottom-left-radius"),
|
|
970
|
+
clipPath: pcs.getPropertyValue("clip-path")
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
surfaces.push(surface);
|
|
974
|
+
const ph = document.createElement("div");
|
|
975
|
+
ph.setAttribute("data-hybrid-id", id);
|
|
976
|
+
ph.setAttribute("style", withContainingBlock(out2, pcs.getPropertyValue("position")));
|
|
977
|
+
return ph;
|
|
978
|
+
}
|
|
979
|
+
const tag = el.tagName.toLowerCase();
|
|
980
|
+
if (el.namespaceURI === SVGNS || tag === "svg") {
|
|
981
|
+
el.querySelectorAll("use").forEach((u) => inlineExternalUse(u));
|
|
982
|
+
const frag = new DOMParser().parseFromString(
|
|
983
|
+
`<svg xmlns="${SVGNS}" xmlns:xlink="${XLINKNS}">${el.outerHTML}</svg>`,
|
|
984
|
+
"image/svg+xml"
|
|
985
|
+
);
|
|
986
|
+
const parsed = frag.documentElement.firstElementChild;
|
|
987
|
+
const { out: cssText } = styleCss(el);
|
|
988
|
+
if (parsed && cssText) parsed.setAttribute("style", (parsed.getAttribute("style") || "") + cssText);
|
|
989
|
+
return parsed || document.createElementNS(SVGNS, "g");
|
|
990
|
+
}
|
|
991
|
+
const out = document.createElement(tag === "html" ? "div" : tag);
|
|
992
|
+
const { out: css, cs } = styleCss(el);
|
|
993
|
+
if (css) out.setAttribute("style", css);
|
|
994
|
+
const bg = cs.getPropertyValue("background-image");
|
|
995
|
+
if (bg && bg.includes("url(")) {
|
|
996
|
+
const urlRe = /url\((['"]?)([^'")]+)\1\)/g;
|
|
997
|
+
let bm;
|
|
998
|
+
while (bm = urlRe.exec(bg)) {
|
|
999
|
+
const u = bm[2];
|
|
1000
|
+
if (u && !u.startsWith("data:")) {
|
|
1001
|
+
const abs = toAbs(u);
|
|
1002
|
+
if (abs) bgUrls.push(abs);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
for (const a of ["class", "id", "width", "height", "viewBox", "role", "dir", "lang"]) {
|
|
1007
|
+
if (el.hasAttribute(a)) out.setAttribute(a, el.getAttribute(a));
|
|
1008
|
+
}
|
|
1009
|
+
if (tag === "img") {
|
|
1010
|
+
const img = el;
|
|
1011
|
+
const src = img.currentSrc || img.src;
|
|
1012
|
+
if (img.alt) out.setAttribute("alt", img.alt);
|
|
1013
|
+
if (src) {
|
|
1014
|
+
if (src.startsWith("data:")) {
|
|
1015
|
+
out.setAttribute("src", src);
|
|
1016
|
+
} else {
|
|
1017
|
+
const abs = toAbs(src) || src;
|
|
1018
|
+
out.setAttribute("src", abs);
|
|
1019
|
+
const id = `img${imgSeq++}`;
|
|
1020
|
+
out.setAttribute("data-hybrid-img", id);
|
|
1021
|
+
imgRefs.push({ id, url: abs, rect: rectOf(el) });
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
return out;
|
|
1025
|
+
}
|
|
1026
|
+
if (tag === "input" || tag === "textarea" || tag === "select") {
|
|
1027
|
+
const field = el;
|
|
1028
|
+
if (field.type) out.setAttribute("type", field.type);
|
|
1029
|
+
if (field.value != null) out.setAttribute("value", field.value);
|
|
1030
|
+
if (field.checked) out.setAttribute("checked", "");
|
|
1031
|
+
if (field.placeholder) out.setAttribute("placeholder", field.placeholder);
|
|
1032
|
+
}
|
|
1033
|
+
for (const pseudo of ["::before", "::after"]) {
|
|
1034
|
+
const ps = getComputedStyle(el, pseudo);
|
|
1035
|
+
const content = ps.getPropertyValue("content");
|
|
1036
|
+
if (content && content !== "none" && content !== "normal") {
|
|
1037
|
+
const { out: pcss } = styleCss(el, pseudo);
|
|
1038
|
+
const span = document.createElement("span");
|
|
1039
|
+
span.setAttribute("data-pseudo", pseudo);
|
|
1040
|
+
span.setAttribute("style", pcss);
|
|
1041
|
+
const txt = content.replace(/^["']|["']$/g, "");
|
|
1042
|
+
if (txt && txt !== "counter") span.textContent = txt;
|
|
1043
|
+
if (pseudo === "::before") out.insertBefore(span, out.firstChild);
|
|
1044
|
+
else out.appendChild(span);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
for (const node of Array.from(el.childNodes)) {
|
|
1048
|
+
if (node.nodeType === Node.TEXT_NODE) out.appendChild(document.createTextNode(node.nodeValue ?? ""));
|
|
1049
|
+
else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
1050
|
+
const child = node;
|
|
1051
|
+
const tn = child.tagName.toLowerCase();
|
|
1052
|
+
if (tn === "script" || tn === "noscript" || tn === "style" || tn === "link") continue;
|
|
1053
|
+
out.appendChild(clone(child));
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
return out;
|
|
1057
|
+
}
|
|
1058
|
+
const scrolls = [
|
|
1059
|
+
{ sel: ":root", x: window.scrollX, y: window.scrollY }
|
|
1060
|
+
];
|
|
1061
|
+
document.querySelectorAll("*").forEach((el, i) => {
|
|
1062
|
+
if (el.scrollTop || el.scrollLeft) {
|
|
1063
|
+
el.setAttribute("data-m5-scroll", `${i}`);
|
|
1064
|
+
scrolls.push({ sel: `[data-m5-scroll="${i}"]`, x: el.scrollLeft, y: el.scrollTop });
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
const body = clone(document.body);
|
|
1068
|
+
async function drainTasks() {
|
|
1069
|
+
let n = -1;
|
|
1070
|
+
while (fetchTasks.length + fontFaceTasks.length !== n) {
|
|
1071
|
+
n = fetchTasks.length + fontFaceTasks.length;
|
|
1072
|
+
await Promise.all([...fetchTasks, ...fontFaceTasks]);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
return drainTasks().then(() => {
|
|
1076
|
+
const rootStyle = styleCss(document.documentElement).out;
|
|
1077
|
+
const bgColor = getComputedStyle(document.documentElement).backgroundColor;
|
|
1078
|
+
const fontFaceCss = fontFaceBlocks.filter(Boolean).join("\n");
|
|
1079
|
+
let extDefs = "";
|
|
1080
|
+
if (externalSymbols.size) {
|
|
1081
|
+
extDefs = `<svg xmlns="${SVGNS}" width="0" height="0" style="position:absolute" aria-hidden="true">` + [...externalSymbols.values()].join("") + `</svg>`;
|
|
1082
|
+
}
|
|
1083
|
+
const payload = {
|
|
1084
|
+
rootStyle,
|
|
1085
|
+
bodyHtml: body.outerHTML,
|
|
1086
|
+
scrolls,
|
|
1087
|
+
surfaces,
|
|
1088
|
+
imgRefs,
|
|
1089
|
+
bgUrls,
|
|
1090
|
+
dpr: window.devicePixelRatio,
|
|
1091
|
+
vw: window.innerWidth,
|
|
1092
|
+
vh: window.innerHeight,
|
|
1093
|
+
lang: document.documentElement.lang || "",
|
|
1094
|
+
dir: document.documentElement.dir || "",
|
|
1095
|
+
bgColor,
|
|
1096
|
+
fontFaceCss,
|
|
1097
|
+
extDefs
|
|
1098
|
+
};
|
|
1099
|
+
return JSON.stringify(payload);
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
function detectClosedShadowInPage() {
|
|
1103
|
+
const all = document.querySelectorAll("*");
|
|
1104
|
+
for (const el of Array.from(all)) {
|
|
1105
|
+
const tag = el.tagName.toLowerCase();
|
|
1106
|
+
if (!tag.includes("-")) continue;
|
|
1107
|
+
if (!customElements.get(tag)) continue;
|
|
1108
|
+
if (el.shadowRoot === null) {
|
|
1109
|
+
const r = el.getBoundingClientRect();
|
|
1110
|
+
if (r.width > 0 && r.height > 0) return true;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// src/capture/m7-hybrid.ts
|
|
1117
|
+
function injectIntoBody(bodyHtml, injected) {
|
|
1118
|
+
if (!injected) return bodyHtml;
|
|
1119
|
+
const m = bodyHtml.match(/^(\s*<body[^>]*>)/i);
|
|
1120
|
+
if (m) return bodyHtml.slice(0, m[0].length) + injected + bodyHtml.slice(m[0].length);
|
|
1121
|
+
return injected + bodyHtml;
|
|
1122
|
+
}
|
|
1123
|
+
function sidecarImg(s) {
|
|
1124
|
+
if (!s.dataUri) return "";
|
|
1125
|
+
const radius = s.clip?.borderRadius?.trim() ? `border-radius:${s.clip.borderRadius};` : "";
|
|
1126
|
+
const clipPath = s.clip?.clipPath && s.clip.clipPath !== "none" ? `clip-path:${s.clip.clipPath};` : "";
|
|
1127
|
+
const style = `position:absolute;inset:0;width:100%;height:100%;object-fit:fill;pointer-events:none;${radius}${clipPath}`;
|
|
1128
|
+
return `<img data-hybrid-sidecar="${s.id}" src="${s.dataUri}" style="${style}">`;
|
|
1129
|
+
}
|
|
1130
|
+
function injectSidecars(bodyHtml, surfaces) {
|
|
1131
|
+
let html = bodyHtml;
|
|
1132
|
+
for (const s of surfaces) {
|
|
1133
|
+
const img = sidecarImg(s);
|
|
1134
|
+
if (!img) continue;
|
|
1135
|
+
const re = new RegExp(`(<[a-zA-Z][^>]*\\bdata-hybrid-id="${s.id}"[^>]*>)`);
|
|
1136
|
+
if (re.test(html)) html = html.replace(re, `$1${img}`);
|
|
1137
|
+
}
|
|
1138
|
+
return html;
|
|
1139
|
+
}
|
|
1140
|
+
async function shotDataUri(page, rect) {
|
|
1141
|
+
try {
|
|
1142
|
+
const buf = await page.screenshot({
|
|
1143
|
+
clip: {
|
|
1144
|
+
x: Math.max(0, rect.vx),
|
|
1145
|
+
y: Math.max(0, rect.vy),
|
|
1146
|
+
width: Math.max(1, rect.w),
|
|
1147
|
+
height: Math.max(1, rect.h)
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
return `data:image/png;base64,${buf.toString("base64")}`;
|
|
1151
|
+
} catch {
|
|
1152
|
+
return null;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
async function snapshotM7(page) {
|
|
1156
|
+
const payload = JSON.parse(await page.evaluate(serializeHybridInPage));
|
|
1157
|
+
const dataUris = await resolveResourceUris(page, [
|
|
1158
|
+
...payload.imgRefs.map((r) => r.url),
|
|
1159
|
+
...payload.bgUrls
|
|
1160
|
+
]);
|
|
1161
|
+
for (const ref of payload.imgRefs) {
|
|
1162
|
+
if (dataUris[ref.url]) continue;
|
|
1163
|
+
if (ref.rect.w < 1 || ref.rect.h < 1) continue;
|
|
1164
|
+
const uri = await shotDataUri(page, ref.rect);
|
|
1165
|
+
if (uri) dataUris[ref.url] = uri;
|
|
1166
|
+
}
|
|
1167
|
+
for (const s of payload.surfaces) {
|
|
1168
|
+
if (s.dataUri) continue;
|
|
1169
|
+
s.dataUri = await shotDataUri(page, s.rect);
|
|
1170
|
+
}
|
|
1171
|
+
const bodyInlined = inlineResolvedUris(payload.bodyHtml, dataUris);
|
|
1172
|
+
const bodyWithDefs = injectIntoBody(bodyInlined, payload.extDefs || "");
|
|
1173
|
+
const bodyWithInjections = injectSidecars(bodyWithDefs, payload.surfaces);
|
|
1174
|
+
const html = `<!doctype html><html lang="${payload.lang}" dir="${payload.dir}" style="${payload.rootStyle}"><head><meta charset="utf-8"><meta name="viewport" content="width=${payload.vw}"><style>html,body{margin:0;background:${payload.bgColor || "#fff"};}*{box-sizing:border-box;}[data-hybrid-id]{position:relative;}</style>` + (payload.fontFaceCss ? `<style>${payload.fontFaceCss}</style>` : "") + `</head>${bodyWithInjections}</html>`;
|
|
1175
|
+
const surfaceMeta = payload.surfaces.map((s) => ({
|
|
1176
|
+
id: s.id,
|
|
1177
|
+
kind: s.kind,
|
|
1178
|
+
rect: s.rect,
|
|
1179
|
+
rasterized: Boolean(s.dataUri),
|
|
1180
|
+
via: s.needPlaywrightShot ? "playwright" : "inline"
|
|
1181
|
+
}));
|
|
1182
|
+
return {
|
|
1183
|
+
method: "m7",
|
|
1184
|
+
html,
|
|
1185
|
+
scrolls: payload.scrolls,
|
|
1186
|
+
surfaces: surfaceMeta,
|
|
1187
|
+
notes: `m5 DOM + ${Object.keys(dataUris).length} resources inlined (node-side) + ${surfaceMeta.filter((s) => s.rasterized).length}/${surfaceMeta.length} surfaces rasterized`
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// src/capture/remount.ts
|
|
1192
|
+
var import_playwright_core2 = require("playwright-core");
|
|
1193
|
+
|
|
1194
|
+
// src/render/playwright-driver.ts
|
|
1195
|
+
var import_node_fs = require("fs");
|
|
1196
|
+
var import_promises = require("fs/promises");
|
|
1197
|
+
var import_node_child_process2 = require("child_process");
|
|
1198
|
+
var import_node_os = require("os");
|
|
1199
|
+
var import_node_path = require("path");
|
|
1200
|
+
var import_node_url = require("url");
|
|
1201
|
+
var import_playwright_core = require("playwright-core");
|
|
1202
|
+
|
|
1203
|
+
// src/render/ffmpeg-transcode.ts
|
|
1204
|
+
var import_node_child_process = require("child_process");
|
|
1205
|
+
var import_node_util = require("util");
|
|
1206
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
1207
|
+
|
|
1208
|
+
// src/render/playwright-driver.ts
|
|
1209
|
+
var CHROMIUM_NOT_FOUND_MESSAGE = `\u2717 Chromium browser not found. Required for rendering compositions.
|
|
1210
|
+
|
|
1211
|
+
Option 1 (recommended): npx playwright install chromium
|
|
1212
|
+
Option 2: set CHROME_PATH to your system Chrome (~200 MB savings)
|
|
1213
|
+
|
|
1214
|
+
Run one of the above and try again.`;
|
|
1215
|
+
var ChromiumNotFoundError = class extends Error {
|
|
1216
|
+
constructor() {
|
|
1217
|
+
super(CHROMIUM_NOT_FOUND_MESSAGE);
|
|
1218
|
+
this.name = "ChromiumNotFoundError";
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
function resolveChromiumExecutable() {
|
|
1222
|
+
const candidates = [
|
|
1223
|
+
process.env.CHROME_PATH,
|
|
1224
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
1225
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
1226
|
+
safePlaywrightExecutablePath()
|
|
1227
|
+
].filter(Boolean);
|
|
1228
|
+
for (const candidate of candidates) {
|
|
1229
|
+
if (candidate && (0, import_node_fs.existsSync)(candidate)) return candidate;
|
|
1230
|
+
}
|
|
1231
|
+
return void 0;
|
|
1232
|
+
}
|
|
1233
|
+
function safePlaywrightExecutablePath() {
|
|
1234
|
+
try {
|
|
1235
|
+
return import_playwright_core.chromium.executablePath();
|
|
1236
|
+
} catch {
|
|
1237
|
+
return void 0;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// src/capture/remount.ts
|
|
1242
|
+
var DEFAULT_CAPTURE_SETTINGS = {
|
|
1243
|
+
width: 1e3,
|
|
1244
|
+
height: 800,
|
|
1245
|
+
deviceScaleFactor: 2
|
|
1246
|
+
};
|
|
1247
|
+
var LAUNCH_ARGS = [
|
|
1248
|
+
"--force-color-profile=srgb",
|
|
1249
|
+
"--disable-lcd-text",
|
|
1250
|
+
"--font-render-hinting=none",
|
|
1251
|
+
"--disable-skia-runtime-opts",
|
|
1252
|
+
"--hide-scrollbars"
|
|
1253
|
+
];
|
|
1254
|
+
async function launchBrowser() {
|
|
1255
|
+
const executablePath = resolveChromiumExecutable();
|
|
1256
|
+
if (!executablePath) throw new ChromiumNotFoundError();
|
|
1257
|
+
return import_playwright_core2.chromium.launch({ headless: true, executablePath, args: LAUNCH_ARGS });
|
|
1258
|
+
}
|
|
1259
|
+
var KILL_ANIM_CSS = "*,*::before,*::after{animation:none!important;transition:none!important;caret-color:transparent!important;animation-play-state:paused!important;scroll-behavior:auto!important}";
|
|
1260
|
+
async function settle(page) {
|
|
1261
|
+
await page.addStyleTag({ content: KILL_ANIM_CSS }).catch(() => {
|
|
1262
|
+
});
|
|
1263
|
+
await page.evaluate(async () => {
|
|
1264
|
+
try {
|
|
1265
|
+
if (document.fonts?.ready) await document.fonts.ready;
|
|
1266
|
+
} catch {
|
|
1267
|
+
}
|
|
1268
|
+
try {
|
|
1269
|
+
document.querySelectorAll("video,audio").forEach((m) => {
|
|
1270
|
+
const el = m;
|
|
1271
|
+
el.pause();
|
|
1272
|
+
if (Number.isFinite(el.duration)) el.currentTime = 0;
|
|
1273
|
+
});
|
|
1274
|
+
} catch {
|
|
1275
|
+
}
|
|
1276
|
+
try {
|
|
1277
|
+
for (const a of document.getAnimations()) {
|
|
1278
|
+
a.currentTime = 0;
|
|
1279
|
+
a.pause();
|
|
1280
|
+
}
|
|
1281
|
+
} catch {
|
|
1282
|
+
}
|
|
1283
|
+
}).catch(() => {
|
|
1284
|
+
});
|
|
1285
|
+
await page.evaluate(() => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r())))).catch(() => {
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
async function viewportShot(page) {
|
|
1289
|
+
return page.screenshot({ type: "png" });
|
|
1290
|
+
}
|
|
1291
|
+
async function navigate(page, url, idleMs = 6e3) {
|
|
1292
|
+
const resp = await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
1293
|
+
await page.waitForLoadState("networkidle", { timeout: idleMs }).catch(() => {
|
|
1294
|
+
});
|
|
1295
|
+
await settleDynamicContent(page);
|
|
1296
|
+
await page.waitForTimeout(400);
|
|
1297
|
+
return resp;
|
|
1298
|
+
}
|
|
1299
|
+
async function settleDynamicContent(page, maxIters = 8, stepMs = 600) {
|
|
1300
|
+
let prev = -1;
|
|
1301
|
+
for (let i = 0; i < maxIters; i += 1) {
|
|
1302
|
+
const len = await page.evaluate(() => (document.body?.innerText || "").length).catch(() => prev);
|
|
1303
|
+
if (len === prev) return;
|
|
1304
|
+
prev = len;
|
|
1305
|
+
await page.waitForTimeout(stepMs);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
async function openPage(browser, settings, storageState) {
|
|
1309
|
+
const context = await browser.newContext({
|
|
1310
|
+
viewport: { width: settings.width, height: settings.height },
|
|
1311
|
+
deviceScaleFactor: settings.deviceScaleFactor,
|
|
1312
|
+
reducedMotion: "reduce",
|
|
1313
|
+
colorScheme: "light",
|
|
1314
|
+
locale: "en-US",
|
|
1315
|
+
timezoneId: "UTC",
|
|
1316
|
+
...storageState ? { storageState } : {}
|
|
1317
|
+
});
|
|
1318
|
+
return context.newPage();
|
|
1319
|
+
}
|
|
1320
|
+
async function remountScreenshot(html, scrolls, settings = DEFAULT_CAPTURE_SETTINGS) {
|
|
1321
|
+
const browser = await launchBrowser();
|
|
1322
|
+
try {
|
|
1323
|
+
const page = await openPage(browser, settings);
|
|
1324
|
+
await page.setContent(html, { waitUntil: "load" });
|
|
1325
|
+
await page.evaluate(() => document.fonts?.ready).catch(() => {
|
|
1326
|
+
});
|
|
1327
|
+
await restoreScroll(page, scrolls);
|
|
1328
|
+
await settle(page);
|
|
1329
|
+
return await viewportShot(page);
|
|
1330
|
+
} finally {
|
|
1331
|
+
await browser.close();
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
async function restoreScroll(page, scrolls) {
|
|
1335
|
+
await page.evaluate((list) => {
|
|
1336
|
+
for (const s of list) {
|
|
1337
|
+
if (s.sel === ":root") {
|
|
1338
|
+
window.scrollTo(s.x, s.y);
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
const el = document.querySelector(s.sel);
|
|
1342
|
+
if (el) {
|
|
1343
|
+
el.scrollLeft = s.x;
|
|
1344
|
+
el.scrollTop = s.y;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}, scrolls).catch(() => {
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
async function renderArtifactOffline(html, settings = DEFAULT_CAPTURE_SETTINGS) {
|
|
1351
|
+
const browser = await launchBrowser();
|
|
1352
|
+
try {
|
|
1353
|
+
const page = await openPage(browser, settings);
|
|
1354
|
+
const networkRequests = [];
|
|
1355
|
+
page.on("request", (req) => {
|
|
1356
|
+
const url = req.url();
|
|
1357
|
+
if (url.startsWith("http://") || url.startsWith("https://")) networkRequests.push(url);
|
|
1358
|
+
});
|
|
1359
|
+
await page.setContent(html, { waitUntil: "load" });
|
|
1360
|
+
await page.evaluate(() => document.fonts?.ready).catch(() => {
|
|
1361
|
+
});
|
|
1362
|
+
await settle(page);
|
|
1363
|
+
const png = await viewportShot(page);
|
|
1364
|
+
return { networkRequests, png };
|
|
1365
|
+
} finally {
|
|
1366
|
+
await browser.close();
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
async function liveScreenshot(url, settings = DEFAULT_CAPTURE_SETTINGS) {
|
|
1370
|
+
const browser = await launchBrowser();
|
|
1371
|
+
try {
|
|
1372
|
+
const page = await openPage(browser, settings);
|
|
1373
|
+
await navigate(page, url);
|
|
1374
|
+
await settle(page);
|
|
1375
|
+
return await viewportShot(page);
|
|
1376
|
+
} finally {
|
|
1377
|
+
await browser.close();
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
// src/capture/index.ts
|
|
1382
|
+
function shouldFallbackToM4(signals) {
|
|
1383
|
+
if (signals.closedShadow) return true;
|
|
1384
|
+
const csp = (signals.csp ?? "").toLowerCase();
|
|
1385
|
+
if (!csp) return false;
|
|
1386
|
+
if (/(?:default-src|connect-src)\s+'none'/.test(csp)) return true;
|
|
1387
|
+
if (/connect-src\s+'self'(?:[^;]*)?(?:;|$)/.test(csp) && !/connect-src[^;]*https?:/.test(csp)) return true;
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
async function captureDom(page, opts = {}) {
|
|
1391
|
+
let method;
|
|
1392
|
+
if (opts.forceMethod) {
|
|
1393
|
+
method = opts.forceMethod;
|
|
1394
|
+
} else {
|
|
1395
|
+
const closedShadow = await page.evaluate(detectClosedShadowInPage).catch(() => false);
|
|
1396
|
+
method = shouldFallbackToM4({ closedShadow, csp: opts.csp }) ? "m4" : "m7";
|
|
1397
|
+
}
|
|
1398
|
+
return method === "m4" ? snapshotM4(page) : snapshotM7(page);
|
|
1399
|
+
}
|
|
1400
|
+
async function writeArtifact(snapshot, basePath) {
|
|
1401
|
+
await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(basePath), { recursive: true });
|
|
1402
|
+
const html = `${basePath}.dom.html`;
|
|
1403
|
+
const meta = `${basePath}.dom.meta.json`;
|
|
1404
|
+
await (0, import_promises2.writeFile)(html, snapshot.html, "utf8");
|
|
1405
|
+
await (0, import_promises2.writeFile)(
|
|
1406
|
+
meta,
|
|
1407
|
+
JSON.stringify(
|
|
1408
|
+
{ method: snapshot.method, scrolls: snapshot.scrolls, surfaces: snapshot.surfaces, notes: snapshot.notes },
|
|
1409
|
+
null,
|
|
1410
|
+
2
|
|
1411
|
+
),
|
|
1412
|
+
"utf8"
|
|
1413
|
+
);
|
|
1414
|
+
return { html, meta };
|
|
1415
|
+
}
|
|
1416
|
+
async function captureUrl(url, opts = {}) {
|
|
1417
|
+
const settings = opts.settings ?? DEFAULT_CAPTURE_SETTINGS;
|
|
1418
|
+
const browser = await launchBrowser();
|
|
1419
|
+
let snapshot;
|
|
1420
|
+
let method;
|
|
1421
|
+
let livePng;
|
|
1422
|
+
try {
|
|
1423
|
+
const page = await openPage(browser, settings, opts.storageState);
|
|
1424
|
+
const resp = await navigate(page, url);
|
|
1425
|
+
await settle(page);
|
|
1426
|
+
livePng = await page.screenshot({ type: "png" });
|
|
1427
|
+
const csp = opts.csp ?? resp?.headers()["content-security-policy"] ?? null;
|
|
1428
|
+
snapshot = await captureDom(page, { forceMethod: opts.forceMethod, csp });
|
|
1429
|
+
method = snapshot.method;
|
|
1430
|
+
} finally {
|
|
1431
|
+
await browser.close();
|
|
1432
|
+
}
|
|
1433
|
+
const remountPng = await remountScreenshot(snapshot.html, snapshot.scrolls, settings);
|
|
1434
|
+
return { snapshot, method, livePng, remountPng };
|
|
1435
|
+
}
|
|
1436
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1437
|
+
0 && (module.exports = {
|
|
1438
|
+
DEFAULT_CAPTURE_SETTINGS,
|
|
1439
|
+
captureDom,
|
|
1440
|
+
captureUrl,
|
|
1441
|
+
launchBrowser,
|
|
1442
|
+
liveScreenshot,
|
|
1443
|
+
navigate,
|
|
1444
|
+
openPage,
|
|
1445
|
+
remountScreenshot,
|
|
1446
|
+
renderArtifactOffline,
|
|
1447
|
+
settle,
|
|
1448
|
+
shouldFallbackToM4,
|
|
1449
|
+
writeArtifact
|
|
1450
|
+
});
|