@skrillex1224/playwright-toolkit 3.0.19 → 3.0.22
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/dist/browser.js +0 -232
- package/dist/browser.js.map +3 -3
- package/dist/index.cjs +625 -21
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +624 -20
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -495,6 +495,7 @@ function createInternalLogger(moduleName, explicitLogger) {
|
|
|
495
495
|
|
|
496
496
|
// src/internals/screenshot.js
|
|
497
497
|
var import_delay = __toESM(require("delay"), 1);
|
|
498
|
+
var import_jimp = require("jimp");
|
|
498
499
|
|
|
499
500
|
// src/internals/constants.js
|
|
500
501
|
var PageRuntimeStateKey = "__playwright_toolkit_runtime_state__";
|
|
@@ -533,9 +534,19 @@ var FORCED_FULLPAGE_TYPE = "jpeg";
|
|
|
533
534
|
var FORCED_FULLPAGE_QUALITY = 50;
|
|
534
535
|
var SUPPORTED_TYPES = /* @__PURE__ */ new Set(["png", "jpeg", "webp"]);
|
|
535
536
|
var EXPANDED_SCROLLABLE_CLASS = "__pk_expanded__";
|
|
537
|
+
var STITCH_SCROLL_TARGET_ATTR = "data-pk-stitch-scroll-target";
|
|
536
538
|
var DEFAULT_MAX_HEIGHT = 8e3;
|
|
537
539
|
var DEFAULT_SETTLE_MS = 1e3;
|
|
538
540
|
var DEFAULT_MOBILE_SETTLE_MS = 50;
|
|
541
|
+
var DEFAULT_STITCH_SETTLE_MS = 120;
|
|
542
|
+
var DEFAULT_STITCH_OVERLAP_PX = 24;
|
|
543
|
+
var MIN_VIRTUALIZED_SCROLL_RATIO = 2.2;
|
|
544
|
+
var MIN_SPARSE_SCROLL_RATIO = 4;
|
|
545
|
+
var MOBILE_VIEWPORT_WIDTH_THRESHOLD = 520;
|
|
546
|
+
var DEFAULT_QUALITY_RETRY_ATTEMPTS = 2;
|
|
547
|
+
var DEFAULT_QUALITY_RETRY_DELAY_MS = 2500;
|
|
548
|
+
var MIN_TRAILING_BLANK_GAP_PX = 320;
|
|
549
|
+
var MIN_TRAILING_BLANK_GAP_RATIO = 0.12;
|
|
539
550
|
var toPositiveNumber = (value, fallback = 0) => {
|
|
540
551
|
const n = Number(value);
|
|
541
552
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
@@ -558,7 +569,22 @@ var normalizeQuality = (value, type) => {
|
|
|
558
569
|
if (rounded < 0 || rounded > 100) return void 0;
|
|
559
570
|
return rounded;
|
|
560
571
|
};
|
|
561
|
-
var resolvePageDevice = (page) =>
|
|
572
|
+
var resolvePageDevice = async (page) => {
|
|
573
|
+
const declared = normalizeDevice(page?.[PageRuntimeStateKey]?.device);
|
|
574
|
+
if (declared === Device.Mobile) return Device.Mobile;
|
|
575
|
+
const viewport = await resolveCurrentViewportSize(page).catch(() => null);
|
|
576
|
+
const viewportWidth = Math.round(Number(viewport?.width) || 0);
|
|
577
|
+
if (viewportWidth > 0 && viewportWidth <= MOBILE_VIEWPORT_WIDTH_THRESHOLD) {
|
|
578
|
+
return Device.Mobile;
|
|
579
|
+
}
|
|
580
|
+
return declared;
|
|
581
|
+
};
|
|
582
|
+
var resolveStitchMode = (options = {}) => {
|
|
583
|
+
const raw = options.stitch ?? options.stitching ?? options.strategy;
|
|
584
|
+
if (raw === false || raw === "off" || raw === "none" || raw === "single") return "off";
|
|
585
|
+
if (raw === true || raw === "force" || raw === "stitched") return "force";
|
|
586
|
+
return "auto";
|
|
587
|
+
};
|
|
562
588
|
var buildFullPageClip = (metrics, viewport, maxClipHeight) => {
|
|
563
589
|
const contentSize = metrics && typeof metrics === "object" ? metrics.contentSize || null : null;
|
|
564
590
|
const width = Math.max(1, Math.ceil(viewport.width || contentSize?.width || 1));
|
|
@@ -905,10 +931,156 @@ var restoreAffixedElementsForExpandedScreenshot = async (page) => {
|
|
|
905
931
|
});
|
|
906
932
|
}, EXPANDED_SCROLLABLE_CLASS);
|
|
907
933
|
};
|
|
934
|
+
var measureMeaningfulContentBounds = async (page) => {
|
|
935
|
+
return await page.evaluate(() => {
|
|
936
|
+
const body = document.body;
|
|
937
|
+
if (!body) return null;
|
|
938
|
+
const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || body.clientWidth || 0;
|
|
939
|
+
const viewportHeight = window.innerHeight || document.documentElement?.clientHeight || body.clientHeight || 0;
|
|
940
|
+
const scrollX = window.scrollX || window.pageXOffset || 0;
|
|
941
|
+
const scrollY = window.scrollY || window.pageYOffset || 0;
|
|
942
|
+
const bounds = {
|
|
943
|
+
left: Number.POSITIVE_INFINITY,
|
|
944
|
+
top: Number.POSITIVE_INFINITY,
|
|
945
|
+
right: 0,
|
|
946
|
+
bottom: 0,
|
|
947
|
+
nodes: 0
|
|
948
|
+
};
|
|
949
|
+
const isVisible = (el, style, rect) => {
|
|
950
|
+
if (!el || !style || !rect) return false;
|
|
951
|
+
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") return false;
|
|
952
|
+
if (Number(style.opacity) === 0) return false;
|
|
953
|
+
return rect.width > 1 && rect.height > 1;
|
|
954
|
+
};
|
|
955
|
+
const hasFixedAncestor = (el) => {
|
|
956
|
+
for (let node = el; node && node.nodeType === 1; node = node.parentElement) {
|
|
957
|
+
const position = String(window.getComputedStyle(node).position || "").toLowerCase();
|
|
958
|
+
if (position === "fixed") {
|
|
959
|
+
return true;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return false;
|
|
963
|
+
};
|
|
964
|
+
const parseRgbColor = (value) => {
|
|
965
|
+
const raw = String(value || "").trim();
|
|
966
|
+
const match = raw.match(/rgba?\(([^)]+)\)/i);
|
|
967
|
+
if (!match) return null;
|
|
968
|
+
const parts = match[1].replace(/\//g, " ").split(/[,\s]+/).map((part) => part.trim()).filter(Boolean);
|
|
969
|
+
if (parts.length < 3) return null;
|
|
970
|
+
const normalizeChannel = (part) => {
|
|
971
|
+
if (part.endsWith("%")) {
|
|
972
|
+
return Math.max(0, Math.min(255, Number.parseFloat(part) * 2.55));
|
|
973
|
+
}
|
|
974
|
+
return Math.max(0, Math.min(255, Number.parseFloat(part)));
|
|
975
|
+
};
|
|
976
|
+
const alpha = parts.length >= 4 ? parts[3].endsWith("%") ? Number.parseFloat(parts[3]) / 100 : Number.parseFloat(parts[3]) : 1;
|
|
977
|
+
return {
|
|
978
|
+
r: normalizeChannel(parts[0]),
|
|
979
|
+
g: normalizeChannel(parts[1]),
|
|
980
|
+
b: normalizeChannel(parts[2]),
|
|
981
|
+
a: Number.isFinite(alpha) ? Math.max(0, Math.min(1, alpha)) : 1
|
|
982
|
+
};
|
|
983
|
+
};
|
|
984
|
+
const colorLuminance = (color) => {
|
|
985
|
+
const channel = (value) => {
|
|
986
|
+
const ratio = Math.max(0, Math.min(255, value)) / 255;
|
|
987
|
+
return ratio <= 0.03928 ? ratio / 12.92 : ((ratio + 0.055) / 1.055) ** 2.4;
|
|
988
|
+
};
|
|
989
|
+
return 0.2126 * channel(color.r) + 0.7152 * channel(color.g) + 0.0722 * channel(color.b);
|
|
990
|
+
};
|
|
991
|
+
const contrastRatio = (a, b) => {
|
|
992
|
+
const l1 = colorLuminance(a);
|
|
993
|
+
const l2 = colorLuminance(b);
|
|
994
|
+
const lighter = Math.max(l1, l2);
|
|
995
|
+
const darker = Math.min(l1, l2);
|
|
996
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
997
|
+
};
|
|
998
|
+
const resolveBackgroundColor = (el) => {
|
|
999
|
+
for (let node = el; node && node.nodeType === 1; node = node.parentElement) {
|
|
1000
|
+
const background = parseRgbColor(window.getComputedStyle(node).backgroundColor);
|
|
1001
|
+
if (background && background.a > 0.2) {
|
|
1002
|
+
return background;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return { r: 255, g: 255, b: 255, a: 1 };
|
|
1006
|
+
};
|
|
1007
|
+
const isLowInformationTextPaint = (el, style) => {
|
|
1008
|
+
const color = parseRgbColor(style?.color);
|
|
1009
|
+
if (!color) return false;
|
|
1010
|
+
const opacity = Number(style.opacity);
|
|
1011
|
+
if (color.a <= 0.08 || Number.isFinite(opacity) && opacity <= 0.18) {
|
|
1012
|
+
return true;
|
|
1013
|
+
}
|
|
1014
|
+
const max = Math.max(color.r, color.g, color.b);
|
|
1015
|
+
const min = Math.min(color.r, color.g, color.b);
|
|
1016
|
+
if (!(min >= 224 && max - min <= 24 && color.a >= 0.85)) {
|
|
1017
|
+
return false;
|
|
1018
|
+
}
|
|
1019
|
+
const background = resolveBackgroundColor(el);
|
|
1020
|
+
return colorLuminance(background) > 0.76 && contrastRatio(color, background) < 1.35;
|
|
1021
|
+
};
|
|
1022
|
+
const addRect = (rect) => {
|
|
1023
|
+
if (!rect || rect.width <= 1 || rect.height <= 1) return;
|
|
1024
|
+
const left = Math.max(0, Math.floor(rect.left + scrollX));
|
|
1025
|
+
const top = Math.max(0, Math.floor(rect.top + scrollY));
|
|
1026
|
+
const right = Math.ceil(rect.right + scrollX);
|
|
1027
|
+
const bottom = Math.ceil(rect.bottom + scrollY);
|
|
1028
|
+
if (right <= left || bottom <= top) return;
|
|
1029
|
+
bounds.left = Math.min(bounds.left, left);
|
|
1030
|
+
bounds.top = Math.min(bounds.top, top);
|
|
1031
|
+
bounds.right = Math.max(bounds.right, right);
|
|
1032
|
+
bounds.bottom = Math.max(bounds.bottom, bottom);
|
|
1033
|
+
bounds.nodes += 1;
|
|
1034
|
+
};
|
|
1035
|
+
const addElement = (el, { minArea = 12, text = false } = {}) => {
|
|
1036
|
+
if (!el || el.nodeType !== 1 || hasFixedAncestor(el)) return;
|
|
1037
|
+
const style = window.getComputedStyle(el);
|
|
1038
|
+
if (text && isLowInformationTextPaint(el, style)) return;
|
|
1039
|
+
const rects = Array.from(el.getClientRects ? el.getClientRects() : []);
|
|
1040
|
+
rects.forEach((rect) => {
|
|
1041
|
+
if (!isVisible(el, style, rect)) return;
|
|
1042
|
+
if (rect.width * rect.height < minArea) return;
|
|
1043
|
+
addRect(rect);
|
|
1044
|
+
});
|
|
1045
|
+
};
|
|
1046
|
+
const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT);
|
|
1047
|
+
let visitedTextNodes = 0;
|
|
1048
|
+
while (walker.nextNode() && visitedTextNodes < 6e3) {
|
|
1049
|
+
const node = walker.currentNode;
|
|
1050
|
+
visitedTextNodes += 1;
|
|
1051
|
+
const text = String(node.nodeValue || "").replace(/\s+/g, " ").trim();
|
|
1052
|
+
if (text.length < 2) continue;
|
|
1053
|
+
addElement(node.parentElement, { minArea: 8, text: true });
|
|
1054
|
+
}
|
|
1055
|
+
document.querySelectorAll("img,video,canvas,svg,picture").forEach((el) => {
|
|
1056
|
+
addElement(el, { minArea: 900 });
|
|
1057
|
+
});
|
|
1058
|
+
if (!bounds.nodes || !Number.isFinite(bounds.left)) return null;
|
|
1059
|
+
const paddingX = Math.max(16, Math.min(96, viewportWidth * 0.04));
|
|
1060
|
+
const paddingY = Math.max(24, Math.min(160, viewportHeight * 0.18));
|
|
1061
|
+
return {
|
|
1062
|
+
left: Math.max(0, Math.floor(bounds.left - paddingX)),
|
|
1063
|
+
top: Math.max(0, Math.floor(bounds.top - paddingY)),
|
|
1064
|
+
right: Math.ceil(bounds.right + paddingX),
|
|
1065
|
+
bottom: Math.ceil(bounds.bottom + paddingY),
|
|
1066
|
+
width: Math.max(1, Math.ceil(bounds.right - bounds.left)),
|
|
1067
|
+
height: Math.max(1, Math.ceil(bounds.bottom - bounds.top)),
|
|
1068
|
+
nodes: bounds.nodes,
|
|
1069
|
+
viewport: {
|
|
1070
|
+
width: Math.max(1, Math.ceil(viewportWidth || 1)),
|
|
1071
|
+
height: Math.max(1, Math.ceil(viewportHeight || 1))
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
}).catch((error) => {
|
|
1075
|
+
logger.warning(`\u622A\u56FE\u5185\u5BB9\u8FB9\u754C\u6D4B\u91CF\u5931\u8D25: ${error?.message || error}`);
|
|
1076
|
+
return null;
|
|
1077
|
+
});
|
|
1078
|
+
};
|
|
908
1079
|
var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
909
1080
|
const originalViewport = await resolveCurrentViewportSize(page);
|
|
910
1081
|
const maxHeight = toPositiveInteger(options.maxHeight, DEFAULT_MAX_HEIGHT);
|
|
911
|
-
const
|
|
1082
|
+
const device = await resolvePageDevice(page);
|
|
1083
|
+
const preserveViewport = options.preserveViewport ?? device === Device.Mobile;
|
|
912
1084
|
const defaultSettleMs = preserveViewport ? DEFAULT_MOBILE_SETTLE_MS : DEFAULT_SETTLE_MS;
|
|
913
1085
|
const requestedSettleMs = Math.max(0, Number(options.settleMs ?? defaultSettleMs) || 0);
|
|
914
1086
|
const settleMs = preserveViewport ? Math.min(requestedSettleMs, DEFAULT_MOBILE_SETTLE_MS) : requestedSettleMs;
|
|
@@ -917,7 +1089,21 @@ var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
917
1089
|
visibleOnly: options.visibleOnly !== false,
|
|
918
1090
|
expandDocumentElements: preserveViewport
|
|
919
1091
|
});
|
|
920
|
-
const
|
|
1092
|
+
const contentBounds = await measureMeaningfulContentBounds(page);
|
|
1093
|
+
let targetHeight = Math.min(maxScrollHeight, maxHeight);
|
|
1094
|
+
if (contentBounds?.bottom > 0) {
|
|
1095
|
+
const contentBottom = Math.min(Math.ceil(contentBounds.bottom), maxHeight);
|
|
1096
|
+
const trailingGap = targetHeight - contentBottom;
|
|
1097
|
+
const minTrailingGap = Math.max(
|
|
1098
|
+
MIN_TRAILING_BLANK_GAP_PX,
|
|
1099
|
+
Math.floor(targetHeight * MIN_TRAILING_BLANK_GAP_RATIO),
|
|
1100
|
+
Math.floor(originalViewport.height * 0.45)
|
|
1101
|
+
);
|
|
1102
|
+
if (trailingGap >= minTrailingGap && contentBottom >= originalViewport.height * 0.65) {
|
|
1103
|
+
targetHeight = Math.max(1, contentBottom);
|
|
1104
|
+
logger.info(`\u957F\u622A\u56FE\u88C1\u6389\u4F4E\u4FE1\u606F\u5C3E\u90E8: contentBottom=${contentBottom}, originalHeight=${Math.min(maxScrollHeight, maxHeight)}`);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
921
1107
|
let viewportResized = false;
|
|
922
1108
|
if (!preserveViewport) {
|
|
923
1109
|
await page.setViewportSize({
|
|
@@ -937,6 +1123,7 @@ var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
937
1123
|
originalViewport,
|
|
938
1124
|
maxScrollHeight,
|
|
939
1125
|
targetHeight,
|
|
1126
|
+
contentBounds,
|
|
940
1127
|
preserveViewport,
|
|
941
1128
|
viewportResized
|
|
942
1129
|
};
|
|
@@ -969,6 +1156,411 @@ var restoreExpandedFullPageScreenshot = async (page, state2 = {}) => {
|
|
|
969
1156
|
await page.setViewportSize(state2.originalViewport);
|
|
970
1157
|
}
|
|
971
1158
|
};
|
|
1159
|
+
var resolveVirtualizedScrollTarget = async (page, options = {}) => {
|
|
1160
|
+
const stitchMode = resolveStitchMode(options);
|
|
1161
|
+
if (stitchMode === "off") return null;
|
|
1162
|
+
const device = await resolvePageDevice(page);
|
|
1163
|
+
if (stitchMode !== "force" && device !== Device.Mobile) {
|
|
1164
|
+
return null;
|
|
1165
|
+
}
|
|
1166
|
+
return await page.evaluate((config) => {
|
|
1167
|
+
const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || document.body?.clientWidth || 0;
|
|
1168
|
+
const viewportHeight = window.innerHeight || document.documentElement?.clientHeight || document.body?.clientHeight || 0;
|
|
1169
|
+
const root = document.documentElement;
|
|
1170
|
+
const body = document.body;
|
|
1171
|
+
const scrollingElement = document.scrollingElement;
|
|
1172
|
+
const candidates = [];
|
|
1173
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1174
|
+
const scrollableOverflow = /* @__PURE__ */ new Set(["auto", "scroll", "overlay"]);
|
|
1175
|
+
const clippingOverflow = /* @__PURE__ */ new Set(["hidden", "clip"]);
|
|
1176
|
+
const pushCandidate = (el2) => {
|
|
1177
|
+
if (!el2 || seen.has(el2)) return;
|
|
1178
|
+
seen.add(el2);
|
|
1179
|
+
candidates.push(el2);
|
|
1180
|
+
};
|
|
1181
|
+
pushCandidate(scrollingElement);
|
|
1182
|
+
pushCandidate(root);
|
|
1183
|
+
pushCandidate(body);
|
|
1184
|
+
document.querySelectorAll("*").forEach(pushCandidate);
|
|
1185
|
+
const isDocumentElement = (el2) => el2 === root || el2 === body || el2 === scrollingElement;
|
|
1186
|
+
const isVisible = (el2, style, rect) => {
|
|
1187
|
+
if (!el2 || !style || !rect) return false;
|
|
1188
|
+
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") return false;
|
|
1189
|
+
if (Number(style.opacity) === 0) return false;
|
|
1190
|
+
return rect.width > 0 && rect.height > 0;
|
|
1191
|
+
};
|
|
1192
|
+
const hasOverflowValue = (style, values) => {
|
|
1193
|
+
const overflowY = String(style?.overflowY || "").toLowerCase();
|
|
1194
|
+
const overflow = String(style?.overflow || "").toLowerCase();
|
|
1195
|
+
return values.has(overflowY) || values.has(overflow);
|
|
1196
|
+
};
|
|
1197
|
+
const looksScrollable = (el2, style) => {
|
|
1198
|
+
if (!el2 || el2.scrollHeight <= el2.clientHeight + 1) return false;
|
|
1199
|
+
if (isDocumentElement(el2)) return true;
|
|
1200
|
+
return hasOverflowValue(style, scrollableOverflow) || hasOverflowValue(style, clippingOverflow);
|
|
1201
|
+
};
|
|
1202
|
+
const textHeightFor = (el2) => {
|
|
1203
|
+
const nodes = isDocumentElement(el2) ? document.querySelectorAll("body *") : el2.querySelectorAll("*");
|
|
1204
|
+
let textHeight = 0;
|
|
1205
|
+
let textNodes = 0;
|
|
1206
|
+
const maxNodes = 3500;
|
|
1207
|
+
for (const node of nodes) {
|
|
1208
|
+
if (textNodes >= maxNodes) break;
|
|
1209
|
+
const text = String(node.textContent || "").replace(/\s+/g, " ").trim();
|
|
1210
|
+
if (text.length < 2) continue;
|
|
1211
|
+
let childHasText = false;
|
|
1212
|
+
for (const child of node.children || []) {
|
|
1213
|
+
if (String(child.textContent || "").replace(/\s+/g, " ").trim().length >= 2) {
|
|
1214
|
+
childHasText = true;
|
|
1215
|
+
break;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
if (childHasText) continue;
|
|
1219
|
+
const style = window.getComputedStyle(node);
|
|
1220
|
+
const rect = node.getBoundingClientRect();
|
|
1221
|
+
if (!isVisible(node, style, rect)) continue;
|
|
1222
|
+
textNodes += 1;
|
|
1223
|
+
textHeight += Math.min(Math.ceil(rect.height || 0), 900);
|
|
1224
|
+
}
|
|
1225
|
+
return { textHeight, textNodes };
|
|
1226
|
+
};
|
|
1227
|
+
const resolveEdgeOcclusion = () => {
|
|
1228
|
+
let top = 0;
|
|
1229
|
+
let bottom = 0;
|
|
1230
|
+
const maxHeight = Math.max(48, viewportHeight * 0.45);
|
|
1231
|
+
document.querySelectorAll("*").forEach((el2) => {
|
|
1232
|
+
const style = window.getComputedStyle(el2);
|
|
1233
|
+
const position = String(style.position || "").toLowerCase();
|
|
1234
|
+
if (position !== "fixed" && position !== "sticky") return;
|
|
1235
|
+
const rect = el2.getBoundingClientRect();
|
|
1236
|
+
if (!isVisible(el2, style, rect)) return;
|
|
1237
|
+
if (rect.height <= 0 || rect.height > maxHeight) return;
|
|
1238
|
+
if (rect.width < viewportWidth * 0.35) return;
|
|
1239
|
+
if (rect.top <= Math.max(16, viewportHeight * 0.08)) {
|
|
1240
|
+
top = Math.max(top, Math.ceil(rect.bottom));
|
|
1241
|
+
}
|
|
1242
|
+
if (rect.bottom >= viewportHeight - Math.max(16, viewportHeight * 0.08)) {
|
|
1243
|
+
bottom = Math.max(bottom, Math.ceil(viewportHeight - rect.top));
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
1246
|
+
return {
|
|
1247
|
+
top: Math.max(0, Math.min(Math.ceil(top), Math.floor(viewportHeight * 0.45))),
|
|
1248
|
+
bottom: Math.max(0, Math.min(Math.ceil(bottom), Math.floor(viewportHeight * 0.45)))
|
|
1249
|
+
};
|
|
1250
|
+
};
|
|
1251
|
+
let best = null;
|
|
1252
|
+
candidates.forEach((el2) => {
|
|
1253
|
+
const style = window.getComputedStyle(el2);
|
|
1254
|
+
const rect = isDocumentElement(el2) ? {
|
|
1255
|
+
top: 0,
|
|
1256
|
+
bottom: viewportHeight,
|
|
1257
|
+
left: 0,
|
|
1258
|
+
right: viewportWidth,
|
|
1259
|
+
width: viewportWidth,
|
|
1260
|
+
height: viewportHeight
|
|
1261
|
+
} : el2.getBoundingClientRect();
|
|
1262
|
+
if (!isDocumentElement(el2) && !isVisible(el2, style, rect)) return;
|
|
1263
|
+
if (!looksScrollable(el2, style)) return;
|
|
1264
|
+
const scrollHeight = Math.ceil(el2.scrollHeight || 0);
|
|
1265
|
+
const clientHeight = Math.ceil(el2.clientHeight || viewportHeight || 0);
|
|
1266
|
+
if (scrollHeight < Math.max(900, clientHeight * config.minScrollRatio)) return;
|
|
1267
|
+
const { textHeight, textNodes } = textHeightFor(el2);
|
|
1268
|
+
const sparseRatio = scrollHeight / Math.max(1, textHeight);
|
|
1269
|
+
const sparse = config.force || textNodes > 0 && sparseRatio >= config.minSparseRatio && scrollHeight - textHeight > clientHeight;
|
|
1270
|
+
if (!sparse) return;
|
|
1271
|
+
const visibleWidth = Math.max(1, Math.ceil(rect.width || viewportWidth || 1));
|
|
1272
|
+
const score2 = scrollHeight * Math.min(visibleWidth, viewportWidth || visibleWidth);
|
|
1273
|
+
if (!best || score2 > best.score) {
|
|
1274
|
+
best = {
|
|
1275
|
+
el: el2,
|
|
1276
|
+
score: score2,
|
|
1277
|
+
kind: isDocumentElement(el2) ? "document" : "element",
|
|
1278
|
+
scrollHeight,
|
|
1279
|
+
clientHeight,
|
|
1280
|
+
scrollTop: Math.max(0, Math.round(el2.scrollTop || 0)),
|
|
1281
|
+
rect: {
|
|
1282
|
+
top: Math.max(0, Math.round(rect.top || 0)),
|
|
1283
|
+
bottom: Math.min(viewportHeight, Math.round(rect.bottom || viewportHeight)),
|
|
1284
|
+
left: Math.max(0, Math.round(rect.left || 0)),
|
|
1285
|
+
width: Math.max(1, Math.round(rect.width || viewportWidth || 1)),
|
|
1286
|
+
height: Math.max(1, Math.round(rect.height || viewportHeight || 1))
|
|
1287
|
+
},
|
|
1288
|
+
textHeight,
|
|
1289
|
+
textNodes,
|
|
1290
|
+
sparseRatio
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
});
|
|
1294
|
+
if (!best) return null;
|
|
1295
|
+
document.querySelectorAll(`[${config.attrName}="1"]`).forEach((node) => {
|
|
1296
|
+
node.removeAttribute(config.attrName);
|
|
1297
|
+
});
|
|
1298
|
+
if (best.kind === "element") {
|
|
1299
|
+
best.el.setAttribute(config.attrName, "1");
|
|
1300
|
+
}
|
|
1301
|
+
const { el, score, ...target } = best;
|
|
1302
|
+
return {
|
|
1303
|
+
...target,
|
|
1304
|
+
viewport: {
|
|
1305
|
+
width: Math.max(1, Math.ceil(viewportWidth || best.rect.width || 1)),
|
|
1306
|
+
height: Math.max(1, Math.ceil(viewportHeight || best.rect.height || 1))
|
|
1307
|
+
},
|
|
1308
|
+
edgeOcclusion: resolveEdgeOcclusion()
|
|
1309
|
+
};
|
|
1310
|
+
}, {
|
|
1311
|
+
attrName: STITCH_SCROLL_TARGET_ATTR,
|
|
1312
|
+
force: stitchMode === "force",
|
|
1313
|
+
minScrollRatio: MIN_VIRTUALIZED_SCROLL_RATIO,
|
|
1314
|
+
minSparseRatio: MIN_SPARSE_SCROLL_RATIO
|
|
1315
|
+
}).catch((error) => {
|
|
1316
|
+
logger.warning(`\u865A\u62DF\u6EDA\u52A8\u622A\u56FE\u63A2\u6D4B\u5931\u8D25: ${error?.message || error}`);
|
|
1317
|
+
return null;
|
|
1318
|
+
});
|
|
1319
|
+
};
|
|
1320
|
+
var setStitchScrollTop = async (page, target, scrollTop) => {
|
|
1321
|
+
await page.evaluate(({ attrName, kind, top }) => {
|
|
1322
|
+
const el = kind === "document" ? document.scrollingElement || document.documentElement || document.body : document.querySelector(`[${attrName}="1"]`);
|
|
1323
|
+
if (!el) return;
|
|
1324
|
+
el.scrollTop = Math.max(0, Math.round(Number(top) || 0));
|
|
1325
|
+
el.dispatchEvent(new Event("scroll", { bubbles: true }));
|
|
1326
|
+
window.dispatchEvent(new Event("scroll"));
|
|
1327
|
+
}, {
|
|
1328
|
+
attrName: STITCH_SCROLL_TARGET_ATTR,
|
|
1329
|
+
kind: target.kind,
|
|
1330
|
+
top: scrollTop
|
|
1331
|
+
});
|
|
1332
|
+
};
|
|
1333
|
+
var cleanupStitchTarget = async (page) => {
|
|
1334
|
+
await page.evaluate((attrName) => {
|
|
1335
|
+
document.querySelectorAll(`[${attrName}="1"]`).forEach((node) => {
|
|
1336
|
+
node.removeAttribute(attrName);
|
|
1337
|
+
});
|
|
1338
|
+
}, STITCH_SCROLL_TARGET_ATTR).catch(() => {
|
|
1339
|
+
});
|
|
1340
|
+
};
|
|
1341
|
+
var cropImage = (image, crop) => image.clone().crop({
|
|
1342
|
+
x: Math.max(0, Math.round(crop.x || 0)),
|
|
1343
|
+
y: Math.max(0, Math.round(crop.y || 0)),
|
|
1344
|
+
w: Math.max(1, Math.round(crop.w || 1)),
|
|
1345
|
+
h: Math.max(1, Math.round(crop.h || 1))
|
|
1346
|
+
});
|
|
1347
|
+
var captureStitchedScrollableScreenshot = async (page, target, options = {}) => {
|
|
1348
|
+
const maxHeight = toPositiveInteger(options.maxHeight, DEFAULT_MAX_HEIGHT);
|
|
1349
|
+
const settleMs = Math.max(0, Number(options.stitchSettleMs ?? DEFAULT_STITCH_SETTLE_MS) || 0);
|
|
1350
|
+
const overlapPx = Math.max(0, Math.round(Number(options.stitchOverlapPx ?? DEFAULT_STITCH_OVERLAP_PX) || 0));
|
|
1351
|
+
const viewport = target.viewport || await resolveCurrentViewportSize(page);
|
|
1352
|
+
const rect = target.rect || {
|
|
1353
|
+
top: 0,
|
|
1354
|
+
bottom: viewport.height,
|
|
1355
|
+
left: 0,
|
|
1356
|
+
width: viewport.width,
|
|
1357
|
+
height: viewport.height
|
|
1358
|
+
};
|
|
1359
|
+
const edge = target.edgeOcclusion || { top: 0, bottom: 0 };
|
|
1360
|
+
const topCrop = Math.max(0, Math.min(rect.top, viewport.height - 1));
|
|
1361
|
+
const topOverlay = Math.max(topCrop, Math.min(edge.top || 0, viewport.height - 1));
|
|
1362
|
+
const bottomOverlay = Math.max(0, Math.min(edge.bottom || 0, viewport.height - topOverlay - 1));
|
|
1363
|
+
const middleCropY = Math.max(topCrop, topOverlay);
|
|
1364
|
+
const middleCropBottom = Math.max(
|
|
1365
|
+
middleCropY + 1,
|
|
1366
|
+
Math.min(rect.bottom || viewport.height, viewport.height - bottomOverlay)
|
|
1367
|
+
);
|
|
1368
|
+
const middleCropHeight = Math.max(1, middleCropBottom - middleCropY);
|
|
1369
|
+
const scrollStep = Math.max(120, middleCropHeight - overlapPx);
|
|
1370
|
+
const maxScrollTop = Math.max(0, Math.ceil(target.scrollHeight - target.clientHeight));
|
|
1371
|
+
const positions = [];
|
|
1372
|
+
for (let top = 0; top < maxScrollTop; top += scrollStep) {
|
|
1373
|
+
positions.push(Math.round(top));
|
|
1374
|
+
}
|
|
1375
|
+
if (!positions.includes(maxScrollTop)) {
|
|
1376
|
+
positions.push(maxScrollTop);
|
|
1377
|
+
}
|
|
1378
|
+
if (positions.length === 0) {
|
|
1379
|
+
positions.push(0);
|
|
1380
|
+
}
|
|
1381
|
+
const segments = [];
|
|
1382
|
+
let totalHeight = 0;
|
|
1383
|
+
let outputWidth = Math.max(1, Math.round(viewport.width || rect.width || 1));
|
|
1384
|
+
try {
|
|
1385
|
+
for (let index = 0; index < positions.length && totalHeight < maxHeight; index += 1) {
|
|
1386
|
+
const isFirst = index === 0;
|
|
1387
|
+
const isLast = index === positions.length - 1;
|
|
1388
|
+
await setStitchScrollTop(page, target, positions[index]);
|
|
1389
|
+
if (settleMs > 0) {
|
|
1390
|
+
await (0, import_delay.default)(settleMs);
|
|
1391
|
+
}
|
|
1392
|
+
const buffer = await capturePageScreenshot(page, {
|
|
1393
|
+
type: options.type || "png",
|
|
1394
|
+
quality: options.quality,
|
|
1395
|
+
timeout: options.timeout
|
|
1396
|
+
});
|
|
1397
|
+
const image = await import_jimp.Jimp.read(buffer);
|
|
1398
|
+
outputWidth = Math.min(outputWidth, image.bitmap.width);
|
|
1399
|
+
const cropY = isFirst ? 0 : middleCropY;
|
|
1400
|
+
const cropBottom = isLast ? image.bitmap.height : middleCropBottom;
|
|
1401
|
+
let cropHeight = Math.max(1, Math.min(image.bitmap.height, cropBottom) - cropY);
|
|
1402
|
+
const remaining = maxHeight - totalHeight;
|
|
1403
|
+
if (cropHeight > remaining) {
|
|
1404
|
+
cropHeight = remaining;
|
|
1405
|
+
}
|
|
1406
|
+
segments.push(cropImage(image, {
|
|
1407
|
+
x: 0,
|
|
1408
|
+
y: cropY,
|
|
1409
|
+
w: outputWidth,
|
|
1410
|
+
h: cropHeight
|
|
1411
|
+
}));
|
|
1412
|
+
totalHeight += cropHeight;
|
|
1413
|
+
}
|
|
1414
|
+
const canvas = new import_jimp.Jimp({
|
|
1415
|
+
width: outputWidth,
|
|
1416
|
+
height: Math.max(1, totalHeight),
|
|
1417
|
+
color: 4294967295
|
|
1418
|
+
});
|
|
1419
|
+
let y = 0;
|
|
1420
|
+
for (const segment of segments) {
|
|
1421
|
+
canvas.composite(segment, 0, y);
|
|
1422
|
+
y += segment.bitmap.height;
|
|
1423
|
+
}
|
|
1424
|
+
logger.info(
|
|
1425
|
+
`\u865A\u62DF\u6EDA\u52A8\u5206\u6BB5\u622A\u56FE: segments=${segments.length}, height=${totalHeight}, scrollHeight=${target.scrollHeight}, textNodes=${target.textNodes}, sparseRatio=${Number(target.sparseRatio || 0).toFixed(2)}`
|
|
1426
|
+
);
|
|
1427
|
+
return await canvas.getBuffer(import_jimp.JimpMime.png);
|
|
1428
|
+
} finally {
|
|
1429
|
+
await setStitchScrollTop(page, target, target.scrollTop || 0).catch(() => {
|
|
1430
|
+
});
|
|
1431
|
+
await cleanupStitchTarget(page);
|
|
1432
|
+
}
|
|
1433
|
+
};
|
|
1434
|
+
var isLightBlankPixel = ({ r, g, b }) => {
|
|
1435
|
+
const max = Math.max(r, g, b);
|
|
1436
|
+
const min = Math.min(r, g, b);
|
|
1437
|
+
return max >= 238 && max - min <= 18;
|
|
1438
|
+
};
|
|
1439
|
+
var isDarkBlankPixel = ({ r, g, b }) => {
|
|
1440
|
+
const max = Math.max(r, g, b);
|
|
1441
|
+
const min = Math.min(r, g, b);
|
|
1442
|
+
return max <= 34 && max - min <= 10;
|
|
1443
|
+
};
|
|
1444
|
+
var isLowInfoPixel = ({ r, g, b }) => {
|
|
1445
|
+
const max = Math.max(r, g, b);
|
|
1446
|
+
const min = Math.min(r, g, b);
|
|
1447
|
+
return max - min <= 12;
|
|
1448
|
+
};
|
|
1449
|
+
var analyzeScreenshotBuffer = async (buffer) => {
|
|
1450
|
+
const image = await import_jimp.Jimp.read(buffer);
|
|
1451
|
+
const width = image.bitmap.width;
|
|
1452
|
+
const height = image.bitmap.height;
|
|
1453
|
+
const stepY = Math.max(1, Math.floor(height / 900));
|
|
1454
|
+
const stepX = Math.max(1, Math.floor(width / 420));
|
|
1455
|
+
const rows = [];
|
|
1456
|
+
let lightRows = 0;
|
|
1457
|
+
let darkRows = 0;
|
|
1458
|
+
let lowInfoRows = 0;
|
|
1459
|
+
let loadingLikeRows = 0;
|
|
1460
|
+
const rowFlags = [];
|
|
1461
|
+
for (let y = 0; y < height; y += stepY) {
|
|
1462
|
+
let light = 0;
|
|
1463
|
+
let dark = 0;
|
|
1464
|
+
let lowInfo = 0;
|
|
1465
|
+
let edge = 0;
|
|
1466
|
+
let count = 0;
|
|
1467
|
+
let prev = null;
|
|
1468
|
+
for (let x = 0; x < width; x += stepX) {
|
|
1469
|
+
const rgba = (0, import_jimp.intToRGBA)(image.getPixelColor(x, y));
|
|
1470
|
+
count += 1;
|
|
1471
|
+
if (isLightBlankPixel(rgba)) light += 1;
|
|
1472
|
+
if (isDarkBlankPixel(rgba)) dark += 1;
|
|
1473
|
+
if (isLowInfoPixel(rgba)) lowInfo += 1;
|
|
1474
|
+
if (prev) {
|
|
1475
|
+
const diff = Math.abs(rgba.r - prev.r) + Math.abs(rgba.g - prev.g) + Math.abs(rgba.b - prev.b);
|
|
1476
|
+
if (diff > 45) edge += 1;
|
|
1477
|
+
}
|
|
1478
|
+
prev = rgba;
|
|
1479
|
+
}
|
|
1480
|
+
const stat = {
|
|
1481
|
+
y,
|
|
1482
|
+
lightRatio: light / Math.max(1, count),
|
|
1483
|
+
darkRatio: dark / Math.max(1, count),
|
|
1484
|
+
lowInfoRatio: lowInfo / Math.max(1, count),
|
|
1485
|
+
edgeRatio: edge / Math.max(1, count - 1)
|
|
1486
|
+
};
|
|
1487
|
+
rows.push(stat);
|
|
1488
|
+
const lightBlank = stat.lightRatio >= 0.965 && stat.edgeRatio <= 0.015;
|
|
1489
|
+
const darkBlank = stat.darkRatio >= 0.965 && stat.edgeRatio <= 0.012;
|
|
1490
|
+
const lowInfoBlank = stat.lowInfoRatio >= 0.965 && stat.edgeRatio <= 0.012;
|
|
1491
|
+
const loadingLike = stat.darkRatio >= 0.88 && stat.edgeRatio <= 0.025;
|
|
1492
|
+
if (lightBlank) lightRows += 1;
|
|
1493
|
+
if (darkBlank) darkRows += 1;
|
|
1494
|
+
if (lowInfoBlank) lowInfoRows += 1;
|
|
1495
|
+
if (loadingLike) loadingLikeRows += 1;
|
|
1496
|
+
rowFlags.push({ lightBlank, darkBlank, lowInfoBlank, loadingLike });
|
|
1497
|
+
}
|
|
1498
|
+
const sampledRows = Math.max(1, rows.length);
|
|
1499
|
+
return {
|
|
1500
|
+
image,
|
|
1501
|
+
width,
|
|
1502
|
+
height,
|
|
1503
|
+
lightBlankRowsRatio: lightRows / sampledRows,
|
|
1504
|
+
darkBlankRowsRatio: darkRows / sampledRows,
|
|
1505
|
+
lowInfoRowsRatio: lowInfoRows / sampledRows,
|
|
1506
|
+
loadingLikeRowsRatio: loadingLikeRows / sampledRows,
|
|
1507
|
+
rowFlags
|
|
1508
|
+
};
|
|
1509
|
+
};
|
|
1510
|
+
var resolveScreenshotQualityIssue = (analysis) => {
|
|
1511
|
+
if (!analysis) return null;
|
|
1512
|
+
if (analysis.loadingLikeRowsRatio >= 0.92 || analysis.darkBlankRowsRatio >= 0.72) {
|
|
1513
|
+
return "loading-like-dark-screenshot";
|
|
1514
|
+
}
|
|
1515
|
+
return null;
|
|
1516
|
+
};
|
|
1517
|
+
var cropBufferToContentBounds = async (buffer, bounds, options = {}) => {
|
|
1518
|
+
if (!bounds || bounds.nodes <= 0) return buffer;
|
|
1519
|
+
const image = options.image || await import_jimp.Jimp.read(buffer);
|
|
1520
|
+
const width = image.bitmap.width;
|
|
1521
|
+
const height = image.bitmap.height;
|
|
1522
|
+
const safeLeft = Math.max(0, Math.min(width - 1, Math.floor(bounds.left || 0)));
|
|
1523
|
+
const safeRight = Math.max(safeLeft + 1, Math.min(width, Math.ceil(bounds.right || width)));
|
|
1524
|
+
const safeTop = Math.max(0, Math.min(height - 1, Math.floor(bounds.top || 0)));
|
|
1525
|
+
const safeBottom = Math.max(safeTop + 1, Math.min(height, Math.ceil(bounds.bottom || height)));
|
|
1526
|
+
const cropW = safeRight - safeLeft;
|
|
1527
|
+
const cropH = safeBottom - safeTop;
|
|
1528
|
+
const shouldCropX = cropW > 80 && cropW <= width * 0.82 && (safeLeft > width * 0.04 || width - safeRight > width * 0.04);
|
|
1529
|
+
const shouldCropY = cropH > 160 && cropH <= height * 0.88 && height - safeBottom > Math.max(320, height * 0.1);
|
|
1530
|
+
if (!shouldCropX && !shouldCropY) {
|
|
1531
|
+
return buffer;
|
|
1532
|
+
}
|
|
1533
|
+
const x = shouldCropX ? safeLeft : 0;
|
|
1534
|
+
const y = shouldCropY ? safeTop : 0;
|
|
1535
|
+
const w = shouldCropX ? cropW : width;
|
|
1536
|
+
const h = shouldCropY ? cropH : height;
|
|
1537
|
+
logger.info(`\u5185\u5BB9\u611F\u77E5\u88C1\u526A\u622A\u56FE: x=${x}, y=${y}, w=${w}, h=${h}, original=${width}x${height}`);
|
|
1538
|
+
return await cropImage(image, { x, y, w, h }).getBuffer(import_jimp.JimpMime.png);
|
|
1539
|
+
};
|
|
1540
|
+
var captureExpandedFullPageScreenshotOnce = async (page, options = {}) => {
|
|
1541
|
+
const stitchedTarget = await resolveVirtualizedScrollTarget(page, options);
|
|
1542
|
+
if (stitchedTarget) {
|
|
1543
|
+
return await captureStitchedScrollableScreenshot(page, stitchedTarget, options);
|
|
1544
|
+
}
|
|
1545
|
+
const state2 = await prepareExpandedFullPageScreenshot(page, options);
|
|
1546
|
+
try {
|
|
1547
|
+
const buffer = await capturePageScreenshot(page, {
|
|
1548
|
+
fullPage: true,
|
|
1549
|
+
type: options.type || "png",
|
|
1550
|
+
quality: options.quality,
|
|
1551
|
+
timeout: options.timeout,
|
|
1552
|
+
maxClipHeight: state2.targetHeight
|
|
1553
|
+
});
|
|
1554
|
+
return await cropBufferToContentBounds(buffer, state2.contentBounds);
|
|
1555
|
+
} finally {
|
|
1556
|
+
await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
|
|
1557
|
+
logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
|
|
1558
|
+
});
|
|
1559
|
+
if (options.restore) {
|
|
1560
|
+
await restoreExpandedFullPageScreenshot(page, state2);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
972
1564
|
var capturePageScreenshot = async (page, options = {}) => {
|
|
973
1565
|
const fullPage = Boolean(options.fullPage);
|
|
974
1566
|
const type = fullPage ? FORCED_FULLPAGE_TYPE : normalizeType(options.type);
|
|
@@ -1023,23 +1615,35 @@ var capturePageScreenshot = async (page, options = {}) => {
|
|
|
1023
1615
|
}
|
|
1024
1616
|
};
|
|
1025
1617
|
var captureExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
|
|
1037
|
-
logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
|
|
1618
|
+
const attempts = Math.max(1, toPositiveInteger(options.qualityRetryAttempts, DEFAULT_QUALITY_RETRY_ATTEMPTS));
|
|
1619
|
+
const retryDelayMs = Math.max(0, Number(options.qualityRetryDelayMs ?? DEFAULT_QUALITY_RETRY_DELAY_MS) || 0);
|
|
1620
|
+
let lastBuffer = null;
|
|
1621
|
+
let lastAnalysis = null;
|
|
1622
|
+
let lastIssue = null;
|
|
1623
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
1624
|
+
const buffer = await captureExpandedFullPageScreenshotOnce(page, options);
|
|
1625
|
+
const analysis = await analyzeScreenshotBuffer(buffer).catch((error) => {
|
|
1626
|
+
logger.warning(`\u622A\u56FE\u8D28\u91CF\u5206\u6790\u5931\u8D25: ${error?.message || error}`);
|
|
1627
|
+
return null;
|
|
1038
1628
|
});
|
|
1039
|
-
|
|
1040
|
-
|
|
1629
|
+
const issue = resolveScreenshotQualityIssue(analysis);
|
|
1630
|
+
lastBuffer = buffer;
|
|
1631
|
+
lastAnalysis = analysis;
|
|
1632
|
+
lastIssue = issue;
|
|
1633
|
+
if (!issue) {
|
|
1634
|
+
return buffer;
|
|
1635
|
+
}
|
|
1636
|
+
if (attempt < attempts && retryDelayMs > 0) {
|
|
1637
|
+
logger.warning(`\u622A\u56FE\u7591\u4F3C\u5F02\u5E38(${issue})\uFF0C\u7B49\u5F85\u540E\u91CD\u8BD5: attempt=${attempt}/${attempts}`);
|
|
1638
|
+
await (0, import_delay.default)(retryDelayMs);
|
|
1041
1639
|
}
|
|
1042
1640
|
}
|
|
1641
|
+
if (lastIssue && lastAnalysis) {
|
|
1642
|
+
logger.warning(
|
|
1643
|
+
`\u622A\u56FE\u8D28\u91CF\u4ECD\u5F02\u5E38(${lastIssue})\uFF0C\u8FD4\u56DE\u6700\u4F73\u53EF\u5F97\u7ED3\u679C: light=${lastAnalysis.lightBlankRowsRatio.toFixed(2)}, dark=${lastAnalysis.darkBlankRowsRatio.toFixed(2)}, loading=${lastAnalysis.loadingLikeRowsRatio.toFixed(2)}`
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
return lastBuffer;
|
|
1043
1647
|
};
|
|
1044
1648
|
|
|
1045
1649
|
// src/errors.js
|
|
@@ -9716,7 +10320,7 @@ var watermarkifyScreenshotBuffer = async (buffer, meta, page = null, options = {
|
|
|
9716
10320
|
};
|
|
9717
10321
|
|
|
9718
10322
|
// src/internals/compression.js
|
|
9719
|
-
var
|
|
10323
|
+
var import_jimp2 = require("jimp");
|
|
9720
10324
|
var logger15 = createInternalLogger("Compression");
|
|
9721
10325
|
var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
|
|
9722
10326
|
var DEFAULT_SCREENSHOT_OUTPUT_TYPE = "jpeg";
|
|
@@ -9783,10 +10387,10 @@ var encodeJpeg = async (sourceImage, compression, scale, quality) => {
|
|
|
9783
10387
|
image.resize({
|
|
9784
10388
|
w: width,
|
|
9785
10389
|
h: height,
|
|
9786
|
-
mode:
|
|
10390
|
+
mode: import_jimp2.ResizeStrategy.BILINEAR
|
|
9787
10391
|
});
|
|
9788
10392
|
}
|
|
9789
|
-
const buffer = await image.getBuffer(
|
|
10393
|
+
const buffer = await image.getBuffer(import_jimp2.JimpMime.jpeg, { quality });
|
|
9790
10394
|
return {
|
|
9791
10395
|
buffer,
|
|
9792
10396
|
bytes: getBase64BytesFromBuffer(buffer),
|
|
@@ -9798,7 +10402,7 @@ var encodeJpeg = async (sourceImage, compression, scale, quality) => {
|
|
|
9798
10402
|
};
|
|
9799
10403
|
};
|
|
9800
10404
|
var compressImageBuffer = async (buffer, compression) => {
|
|
9801
|
-
const sourceImage = await
|
|
10405
|
+
const sourceImage = await import_jimp2.Jimp.read(buffer);
|
|
9802
10406
|
const maxQuality = toJpegQuality(compression.quality);
|
|
9803
10407
|
const minQuality = Math.min(maxQuality, toJpegQuality(compression.minQuality));
|
|
9804
10408
|
let quality = maxQuality;
|