@skrillex1224/playwright-toolkit 3.0.21 → 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/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 +9 -1
- package/scripts/postinstall.js +203 -0
package/dist/index.js
CHANGED
|
@@ -468,6 +468,7 @@ function createInternalLogger(moduleName, explicitLogger) {
|
|
|
468
468
|
|
|
469
469
|
// src/internals/screenshot.js
|
|
470
470
|
import delay from "delay";
|
|
471
|
+
import { Jimp, JimpMime, intToRGBA } from "jimp";
|
|
471
472
|
|
|
472
473
|
// src/internals/constants.js
|
|
473
474
|
var PageRuntimeStateKey = "__playwright_toolkit_runtime_state__";
|
|
@@ -506,9 +507,19 @@ var FORCED_FULLPAGE_TYPE = "jpeg";
|
|
|
506
507
|
var FORCED_FULLPAGE_QUALITY = 50;
|
|
507
508
|
var SUPPORTED_TYPES = /* @__PURE__ */ new Set(["png", "jpeg", "webp"]);
|
|
508
509
|
var EXPANDED_SCROLLABLE_CLASS = "__pk_expanded__";
|
|
510
|
+
var STITCH_SCROLL_TARGET_ATTR = "data-pk-stitch-scroll-target";
|
|
509
511
|
var DEFAULT_MAX_HEIGHT = 8e3;
|
|
510
512
|
var DEFAULT_SETTLE_MS = 1e3;
|
|
511
513
|
var DEFAULT_MOBILE_SETTLE_MS = 50;
|
|
514
|
+
var DEFAULT_STITCH_SETTLE_MS = 120;
|
|
515
|
+
var DEFAULT_STITCH_OVERLAP_PX = 24;
|
|
516
|
+
var MIN_VIRTUALIZED_SCROLL_RATIO = 2.2;
|
|
517
|
+
var MIN_SPARSE_SCROLL_RATIO = 4;
|
|
518
|
+
var MOBILE_VIEWPORT_WIDTH_THRESHOLD = 520;
|
|
519
|
+
var DEFAULT_QUALITY_RETRY_ATTEMPTS = 2;
|
|
520
|
+
var DEFAULT_QUALITY_RETRY_DELAY_MS = 2500;
|
|
521
|
+
var MIN_TRAILING_BLANK_GAP_PX = 320;
|
|
522
|
+
var MIN_TRAILING_BLANK_GAP_RATIO = 0.12;
|
|
512
523
|
var toPositiveNumber = (value, fallback = 0) => {
|
|
513
524
|
const n = Number(value);
|
|
514
525
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
@@ -531,7 +542,22 @@ var normalizeQuality = (value, type) => {
|
|
|
531
542
|
if (rounded < 0 || rounded > 100) return void 0;
|
|
532
543
|
return rounded;
|
|
533
544
|
};
|
|
534
|
-
var resolvePageDevice = (page) =>
|
|
545
|
+
var resolvePageDevice = async (page) => {
|
|
546
|
+
const declared = normalizeDevice(page?.[PageRuntimeStateKey]?.device);
|
|
547
|
+
if (declared === Device.Mobile) return Device.Mobile;
|
|
548
|
+
const viewport = await resolveCurrentViewportSize(page).catch(() => null);
|
|
549
|
+
const viewportWidth = Math.round(Number(viewport?.width) || 0);
|
|
550
|
+
if (viewportWidth > 0 && viewportWidth <= MOBILE_VIEWPORT_WIDTH_THRESHOLD) {
|
|
551
|
+
return Device.Mobile;
|
|
552
|
+
}
|
|
553
|
+
return declared;
|
|
554
|
+
};
|
|
555
|
+
var resolveStitchMode = (options = {}) => {
|
|
556
|
+
const raw = options.stitch ?? options.stitching ?? options.strategy;
|
|
557
|
+
if (raw === false || raw === "off" || raw === "none" || raw === "single") return "off";
|
|
558
|
+
if (raw === true || raw === "force" || raw === "stitched") return "force";
|
|
559
|
+
return "auto";
|
|
560
|
+
};
|
|
535
561
|
var buildFullPageClip = (metrics, viewport, maxClipHeight) => {
|
|
536
562
|
const contentSize = metrics && typeof metrics === "object" ? metrics.contentSize || null : null;
|
|
537
563
|
const width = Math.max(1, Math.ceil(viewport.width || contentSize?.width || 1));
|
|
@@ -878,10 +904,156 @@ var restoreAffixedElementsForExpandedScreenshot = async (page) => {
|
|
|
878
904
|
});
|
|
879
905
|
}, EXPANDED_SCROLLABLE_CLASS);
|
|
880
906
|
};
|
|
907
|
+
var measureMeaningfulContentBounds = async (page) => {
|
|
908
|
+
return await page.evaluate(() => {
|
|
909
|
+
const body = document.body;
|
|
910
|
+
if (!body) return null;
|
|
911
|
+
const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || body.clientWidth || 0;
|
|
912
|
+
const viewportHeight = window.innerHeight || document.documentElement?.clientHeight || body.clientHeight || 0;
|
|
913
|
+
const scrollX = window.scrollX || window.pageXOffset || 0;
|
|
914
|
+
const scrollY = window.scrollY || window.pageYOffset || 0;
|
|
915
|
+
const bounds = {
|
|
916
|
+
left: Number.POSITIVE_INFINITY,
|
|
917
|
+
top: Number.POSITIVE_INFINITY,
|
|
918
|
+
right: 0,
|
|
919
|
+
bottom: 0,
|
|
920
|
+
nodes: 0
|
|
921
|
+
};
|
|
922
|
+
const isVisible = (el, style, rect) => {
|
|
923
|
+
if (!el || !style || !rect) return false;
|
|
924
|
+
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") return false;
|
|
925
|
+
if (Number(style.opacity) === 0) return false;
|
|
926
|
+
return rect.width > 1 && rect.height > 1;
|
|
927
|
+
};
|
|
928
|
+
const hasFixedAncestor = (el) => {
|
|
929
|
+
for (let node = el; node && node.nodeType === 1; node = node.parentElement) {
|
|
930
|
+
const position = String(window.getComputedStyle(node).position || "").toLowerCase();
|
|
931
|
+
if (position === "fixed") {
|
|
932
|
+
return true;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return false;
|
|
936
|
+
};
|
|
937
|
+
const parseRgbColor = (value) => {
|
|
938
|
+
const raw = String(value || "").trim();
|
|
939
|
+
const match = raw.match(/rgba?\(([^)]+)\)/i);
|
|
940
|
+
if (!match) return null;
|
|
941
|
+
const parts = match[1].replace(/\//g, " ").split(/[,\s]+/).map((part) => part.trim()).filter(Boolean);
|
|
942
|
+
if (parts.length < 3) return null;
|
|
943
|
+
const normalizeChannel = (part) => {
|
|
944
|
+
if (part.endsWith("%")) {
|
|
945
|
+
return Math.max(0, Math.min(255, Number.parseFloat(part) * 2.55));
|
|
946
|
+
}
|
|
947
|
+
return Math.max(0, Math.min(255, Number.parseFloat(part)));
|
|
948
|
+
};
|
|
949
|
+
const alpha = parts.length >= 4 ? parts[3].endsWith("%") ? Number.parseFloat(parts[3]) / 100 : Number.parseFloat(parts[3]) : 1;
|
|
950
|
+
return {
|
|
951
|
+
r: normalizeChannel(parts[0]),
|
|
952
|
+
g: normalizeChannel(parts[1]),
|
|
953
|
+
b: normalizeChannel(parts[2]),
|
|
954
|
+
a: Number.isFinite(alpha) ? Math.max(0, Math.min(1, alpha)) : 1
|
|
955
|
+
};
|
|
956
|
+
};
|
|
957
|
+
const colorLuminance = (color) => {
|
|
958
|
+
const channel = (value) => {
|
|
959
|
+
const ratio = Math.max(0, Math.min(255, value)) / 255;
|
|
960
|
+
return ratio <= 0.03928 ? ratio / 12.92 : ((ratio + 0.055) / 1.055) ** 2.4;
|
|
961
|
+
};
|
|
962
|
+
return 0.2126 * channel(color.r) + 0.7152 * channel(color.g) + 0.0722 * channel(color.b);
|
|
963
|
+
};
|
|
964
|
+
const contrastRatio = (a, b) => {
|
|
965
|
+
const l1 = colorLuminance(a);
|
|
966
|
+
const l2 = colorLuminance(b);
|
|
967
|
+
const lighter = Math.max(l1, l2);
|
|
968
|
+
const darker = Math.min(l1, l2);
|
|
969
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
970
|
+
};
|
|
971
|
+
const resolveBackgroundColor = (el) => {
|
|
972
|
+
for (let node = el; node && node.nodeType === 1; node = node.parentElement) {
|
|
973
|
+
const background = parseRgbColor(window.getComputedStyle(node).backgroundColor);
|
|
974
|
+
if (background && background.a > 0.2) {
|
|
975
|
+
return background;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
return { r: 255, g: 255, b: 255, a: 1 };
|
|
979
|
+
};
|
|
980
|
+
const isLowInformationTextPaint = (el, style) => {
|
|
981
|
+
const color = parseRgbColor(style?.color);
|
|
982
|
+
if (!color) return false;
|
|
983
|
+
const opacity = Number(style.opacity);
|
|
984
|
+
if (color.a <= 0.08 || Number.isFinite(opacity) && opacity <= 0.18) {
|
|
985
|
+
return true;
|
|
986
|
+
}
|
|
987
|
+
const max = Math.max(color.r, color.g, color.b);
|
|
988
|
+
const min = Math.min(color.r, color.g, color.b);
|
|
989
|
+
if (!(min >= 224 && max - min <= 24 && color.a >= 0.85)) {
|
|
990
|
+
return false;
|
|
991
|
+
}
|
|
992
|
+
const background = resolveBackgroundColor(el);
|
|
993
|
+
return colorLuminance(background) > 0.76 && contrastRatio(color, background) < 1.35;
|
|
994
|
+
};
|
|
995
|
+
const addRect = (rect) => {
|
|
996
|
+
if (!rect || rect.width <= 1 || rect.height <= 1) return;
|
|
997
|
+
const left = Math.max(0, Math.floor(rect.left + scrollX));
|
|
998
|
+
const top = Math.max(0, Math.floor(rect.top + scrollY));
|
|
999
|
+
const right = Math.ceil(rect.right + scrollX);
|
|
1000
|
+
const bottom = Math.ceil(rect.bottom + scrollY);
|
|
1001
|
+
if (right <= left || bottom <= top) return;
|
|
1002
|
+
bounds.left = Math.min(bounds.left, left);
|
|
1003
|
+
bounds.top = Math.min(bounds.top, top);
|
|
1004
|
+
bounds.right = Math.max(bounds.right, right);
|
|
1005
|
+
bounds.bottom = Math.max(bounds.bottom, bottom);
|
|
1006
|
+
bounds.nodes += 1;
|
|
1007
|
+
};
|
|
1008
|
+
const addElement = (el, { minArea = 12, text = false } = {}) => {
|
|
1009
|
+
if (!el || el.nodeType !== 1 || hasFixedAncestor(el)) return;
|
|
1010
|
+
const style = window.getComputedStyle(el);
|
|
1011
|
+
if (text && isLowInformationTextPaint(el, style)) return;
|
|
1012
|
+
const rects = Array.from(el.getClientRects ? el.getClientRects() : []);
|
|
1013
|
+
rects.forEach((rect) => {
|
|
1014
|
+
if (!isVisible(el, style, rect)) return;
|
|
1015
|
+
if (rect.width * rect.height < minArea) return;
|
|
1016
|
+
addRect(rect);
|
|
1017
|
+
});
|
|
1018
|
+
};
|
|
1019
|
+
const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT);
|
|
1020
|
+
let visitedTextNodes = 0;
|
|
1021
|
+
while (walker.nextNode() && visitedTextNodes < 6e3) {
|
|
1022
|
+
const node = walker.currentNode;
|
|
1023
|
+
visitedTextNodes += 1;
|
|
1024
|
+
const text = String(node.nodeValue || "").replace(/\s+/g, " ").trim();
|
|
1025
|
+
if (text.length < 2) continue;
|
|
1026
|
+
addElement(node.parentElement, { minArea: 8, text: true });
|
|
1027
|
+
}
|
|
1028
|
+
document.querySelectorAll("img,video,canvas,svg,picture").forEach((el) => {
|
|
1029
|
+
addElement(el, { minArea: 900 });
|
|
1030
|
+
});
|
|
1031
|
+
if (!bounds.nodes || !Number.isFinite(bounds.left)) return null;
|
|
1032
|
+
const paddingX = Math.max(16, Math.min(96, viewportWidth * 0.04));
|
|
1033
|
+
const paddingY = Math.max(24, Math.min(160, viewportHeight * 0.18));
|
|
1034
|
+
return {
|
|
1035
|
+
left: Math.max(0, Math.floor(bounds.left - paddingX)),
|
|
1036
|
+
top: Math.max(0, Math.floor(bounds.top - paddingY)),
|
|
1037
|
+
right: Math.ceil(bounds.right + paddingX),
|
|
1038
|
+
bottom: Math.ceil(bounds.bottom + paddingY),
|
|
1039
|
+
width: Math.max(1, Math.ceil(bounds.right - bounds.left)),
|
|
1040
|
+
height: Math.max(1, Math.ceil(bounds.bottom - bounds.top)),
|
|
1041
|
+
nodes: bounds.nodes,
|
|
1042
|
+
viewport: {
|
|
1043
|
+
width: Math.max(1, Math.ceil(viewportWidth || 1)),
|
|
1044
|
+
height: Math.max(1, Math.ceil(viewportHeight || 1))
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
}).catch((error) => {
|
|
1048
|
+
logger.warning(`\u622A\u56FE\u5185\u5BB9\u8FB9\u754C\u6D4B\u91CF\u5931\u8D25: ${error?.message || error}`);
|
|
1049
|
+
return null;
|
|
1050
|
+
});
|
|
1051
|
+
};
|
|
881
1052
|
var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
882
1053
|
const originalViewport = await resolveCurrentViewportSize(page);
|
|
883
1054
|
const maxHeight = toPositiveInteger(options.maxHeight, DEFAULT_MAX_HEIGHT);
|
|
884
|
-
const
|
|
1055
|
+
const device = await resolvePageDevice(page);
|
|
1056
|
+
const preserveViewport = options.preserveViewport ?? device === Device.Mobile;
|
|
885
1057
|
const defaultSettleMs = preserveViewport ? DEFAULT_MOBILE_SETTLE_MS : DEFAULT_SETTLE_MS;
|
|
886
1058
|
const requestedSettleMs = Math.max(0, Number(options.settleMs ?? defaultSettleMs) || 0);
|
|
887
1059
|
const settleMs = preserveViewport ? Math.min(requestedSettleMs, DEFAULT_MOBILE_SETTLE_MS) : requestedSettleMs;
|
|
@@ -890,7 +1062,21 @@ var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
890
1062
|
visibleOnly: options.visibleOnly !== false,
|
|
891
1063
|
expandDocumentElements: preserveViewport
|
|
892
1064
|
});
|
|
893
|
-
const
|
|
1065
|
+
const contentBounds = await measureMeaningfulContentBounds(page);
|
|
1066
|
+
let targetHeight = Math.min(maxScrollHeight, maxHeight);
|
|
1067
|
+
if (contentBounds?.bottom > 0) {
|
|
1068
|
+
const contentBottom = Math.min(Math.ceil(contentBounds.bottom), maxHeight);
|
|
1069
|
+
const trailingGap = targetHeight - contentBottom;
|
|
1070
|
+
const minTrailingGap = Math.max(
|
|
1071
|
+
MIN_TRAILING_BLANK_GAP_PX,
|
|
1072
|
+
Math.floor(targetHeight * MIN_TRAILING_BLANK_GAP_RATIO),
|
|
1073
|
+
Math.floor(originalViewport.height * 0.45)
|
|
1074
|
+
);
|
|
1075
|
+
if (trailingGap >= minTrailingGap && contentBottom >= originalViewport.height * 0.65) {
|
|
1076
|
+
targetHeight = Math.max(1, contentBottom);
|
|
1077
|
+
logger.info(`\u957F\u622A\u56FE\u88C1\u6389\u4F4E\u4FE1\u606F\u5C3E\u90E8: contentBottom=${contentBottom}, originalHeight=${Math.min(maxScrollHeight, maxHeight)}`);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
894
1080
|
let viewportResized = false;
|
|
895
1081
|
if (!preserveViewport) {
|
|
896
1082
|
await page.setViewportSize({
|
|
@@ -910,6 +1096,7 @@ var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
910
1096
|
originalViewport,
|
|
911
1097
|
maxScrollHeight,
|
|
912
1098
|
targetHeight,
|
|
1099
|
+
contentBounds,
|
|
913
1100
|
preserveViewport,
|
|
914
1101
|
viewportResized
|
|
915
1102
|
};
|
|
@@ -942,6 +1129,411 @@ var restoreExpandedFullPageScreenshot = async (page, state2 = {}) => {
|
|
|
942
1129
|
await page.setViewportSize(state2.originalViewport);
|
|
943
1130
|
}
|
|
944
1131
|
};
|
|
1132
|
+
var resolveVirtualizedScrollTarget = async (page, options = {}) => {
|
|
1133
|
+
const stitchMode = resolveStitchMode(options);
|
|
1134
|
+
if (stitchMode === "off") return null;
|
|
1135
|
+
const device = await resolvePageDevice(page);
|
|
1136
|
+
if (stitchMode !== "force" && device !== Device.Mobile) {
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
return await page.evaluate((config) => {
|
|
1140
|
+
const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || document.body?.clientWidth || 0;
|
|
1141
|
+
const viewportHeight = window.innerHeight || document.documentElement?.clientHeight || document.body?.clientHeight || 0;
|
|
1142
|
+
const root = document.documentElement;
|
|
1143
|
+
const body = document.body;
|
|
1144
|
+
const scrollingElement = document.scrollingElement;
|
|
1145
|
+
const candidates = [];
|
|
1146
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1147
|
+
const scrollableOverflow = /* @__PURE__ */ new Set(["auto", "scroll", "overlay"]);
|
|
1148
|
+
const clippingOverflow = /* @__PURE__ */ new Set(["hidden", "clip"]);
|
|
1149
|
+
const pushCandidate = (el2) => {
|
|
1150
|
+
if (!el2 || seen.has(el2)) return;
|
|
1151
|
+
seen.add(el2);
|
|
1152
|
+
candidates.push(el2);
|
|
1153
|
+
};
|
|
1154
|
+
pushCandidate(scrollingElement);
|
|
1155
|
+
pushCandidate(root);
|
|
1156
|
+
pushCandidate(body);
|
|
1157
|
+
document.querySelectorAll("*").forEach(pushCandidate);
|
|
1158
|
+
const isDocumentElement = (el2) => el2 === root || el2 === body || el2 === scrollingElement;
|
|
1159
|
+
const isVisible = (el2, style, rect) => {
|
|
1160
|
+
if (!el2 || !style || !rect) return false;
|
|
1161
|
+
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") return false;
|
|
1162
|
+
if (Number(style.opacity) === 0) return false;
|
|
1163
|
+
return rect.width > 0 && rect.height > 0;
|
|
1164
|
+
};
|
|
1165
|
+
const hasOverflowValue = (style, values) => {
|
|
1166
|
+
const overflowY = String(style?.overflowY || "").toLowerCase();
|
|
1167
|
+
const overflow = String(style?.overflow || "").toLowerCase();
|
|
1168
|
+
return values.has(overflowY) || values.has(overflow);
|
|
1169
|
+
};
|
|
1170
|
+
const looksScrollable = (el2, style) => {
|
|
1171
|
+
if (!el2 || el2.scrollHeight <= el2.clientHeight + 1) return false;
|
|
1172
|
+
if (isDocumentElement(el2)) return true;
|
|
1173
|
+
return hasOverflowValue(style, scrollableOverflow) || hasOverflowValue(style, clippingOverflow);
|
|
1174
|
+
};
|
|
1175
|
+
const textHeightFor = (el2) => {
|
|
1176
|
+
const nodes = isDocumentElement(el2) ? document.querySelectorAll("body *") : el2.querySelectorAll("*");
|
|
1177
|
+
let textHeight = 0;
|
|
1178
|
+
let textNodes = 0;
|
|
1179
|
+
const maxNodes = 3500;
|
|
1180
|
+
for (const node of nodes) {
|
|
1181
|
+
if (textNodes >= maxNodes) break;
|
|
1182
|
+
const text = String(node.textContent || "").replace(/\s+/g, " ").trim();
|
|
1183
|
+
if (text.length < 2) continue;
|
|
1184
|
+
let childHasText = false;
|
|
1185
|
+
for (const child of node.children || []) {
|
|
1186
|
+
if (String(child.textContent || "").replace(/\s+/g, " ").trim().length >= 2) {
|
|
1187
|
+
childHasText = true;
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
if (childHasText) continue;
|
|
1192
|
+
const style = window.getComputedStyle(node);
|
|
1193
|
+
const rect = node.getBoundingClientRect();
|
|
1194
|
+
if (!isVisible(node, style, rect)) continue;
|
|
1195
|
+
textNodes += 1;
|
|
1196
|
+
textHeight += Math.min(Math.ceil(rect.height || 0), 900);
|
|
1197
|
+
}
|
|
1198
|
+
return { textHeight, textNodes };
|
|
1199
|
+
};
|
|
1200
|
+
const resolveEdgeOcclusion = () => {
|
|
1201
|
+
let top = 0;
|
|
1202
|
+
let bottom = 0;
|
|
1203
|
+
const maxHeight = Math.max(48, viewportHeight * 0.45);
|
|
1204
|
+
document.querySelectorAll("*").forEach((el2) => {
|
|
1205
|
+
const style = window.getComputedStyle(el2);
|
|
1206
|
+
const position = String(style.position || "").toLowerCase();
|
|
1207
|
+
if (position !== "fixed" && position !== "sticky") return;
|
|
1208
|
+
const rect = el2.getBoundingClientRect();
|
|
1209
|
+
if (!isVisible(el2, style, rect)) return;
|
|
1210
|
+
if (rect.height <= 0 || rect.height > maxHeight) return;
|
|
1211
|
+
if (rect.width < viewportWidth * 0.35) return;
|
|
1212
|
+
if (rect.top <= Math.max(16, viewportHeight * 0.08)) {
|
|
1213
|
+
top = Math.max(top, Math.ceil(rect.bottom));
|
|
1214
|
+
}
|
|
1215
|
+
if (rect.bottom >= viewportHeight - Math.max(16, viewportHeight * 0.08)) {
|
|
1216
|
+
bottom = Math.max(bottom, Math.ceil(viewportHeight - rect.top));
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
return {
|
|
1220
|
+
top: Math.max(0, Math.min(Math.ceil(top), Math.floor(viewportHeight * 0.45))),
|
|
1221
|
+
bottom: Math.max(0, Math.min(Math.ceil(bottom), Math.floor(viewportHeight * 0.45)))
|
|
1222
|
+
};
|
|
1223
|
+
};
|
|
1224
|
+
let best = null;
|
|
1225
|
+
candidates.forEach((el2) => {
|
|
1226
|
+
const style = window.getComputedStyle(el2);
|
|
1227
|
+
const rect = isDocumentElement(el2) ? {
|
|
1228
|
+
top: 0,
|
|
1229
|
+
bottom: viewportHeight,
|
|
1230
|
+
left: 0,
|
|
1231
|
+
right: viewportWidth,
|
|
1232
|
+
width: viewportWidth,
|
|
1233
|
+
height: viewportHeight
|
|
1234
|
+
} : el2.getBoundingClientRect();
|
|
1235
|
+
if (!isDocumentElement(el2) && !isVisible(el2, style, rect)) return;
|
|
1236
|
+
if (!looksScrollable(el2, style)) return;
|
|
1237
|
+
const scrollHeight = Math.ceil(el2.scrollHeight || 0);
|
|
1238
|
+
const clientHeight = Math.ceil(el2.clientHeight || viewportHeight || 0);
|
|
1239
|
+
if (scrollHeight < Math.max(900, clientHeight * config.minScrollRatio)) return;
|
|
1240
|
+
const { textHeight, textNodes } = textHeightFor(el2);
|
|
1241
|
+
const sparseRatio = scrollHeight / Math.max(1, textHeight);
|
|
1242
|
+
const sparse = config.force || textNodes > 0 && sparseRatio >= config.minSparseRatio && scrollHeight - textHeight > clientHeight;
|
|
1243
|
+
if (!sparse) return;
|
|
1244
|
+
const visibleWidth = Math.max(1, Math.ceil(rect.width || viewportWidth || 1));
|
|
1245
|
+
const score2 = scrollHeight * Math.min(visibleWidth, viewportWidth || visibleWidth);
|
|
1246
|
+
if (!best || score2 > best.score) {
|
|
1247
|
+
best = {
|
|
1248
|
+
el: el2,
|
|
1249
|
+
score: score2,
|
|
1250
|
+
kind: isDocumentElement(el2) ? "document" : "element",
|
|
1251
|
+
scrollHeight,
|
|
1252
|
+
clientHeight,
|
|
1253
|
+
scrollTop: Math.max(0, Math.round(el2.scrollTop || 0)),
|
|
1254
|
+
rect: {
|
|
1255
|
+
top: Math.max(0, Math.round(rect.top || 0)),
|
|
1256
|
+
bottom: Math.min(viewportHeight, Math.round(rect.bottom || viewportHeight)),
|
|
1257
|
+
left: Math.max(0, Math.round(rect.left || 0)),
|
|
1258
|
+
width: Math.max(1, Math.round(rect.width || viewportWidth || 1)),
|
|
1259
|
+
height: Math.max(1, Math.round(rect.height || viewportHeight || 1))
|
|
1260
|
+
},
|
|
1261
|
+
textHeight,
|
|
1262
|
+
textNodes,
|
|
1263
|
+
sparseRatio
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
if (!best) return null;
|
|
1268
|
+
document.querySelectorAll(`[${config.attrName}="1"]`).forEach((node) => {
|
|
1269
|
+
node.removeAttribute(config.attrName);
|
|
1270
|
+
});
|
|
1271
|
+
if (best.kind === "element") {
|
|
1272
|
+
best.el.setAttribute(config.attrName, "1");
|
|
1273
|
+
}
|
|
1274
|
+
const { el, score, ...target } = best;
|
|
1275
|
+
return {
|
|
1276
|
+
...target,
|
|
1277
|
+
viewport: {
|
|
1278
|
+
width: Math.max(1, Math.ceil(viewportWidth || best.rect.width || 1)),
|
|
1279
|
+
height: Math.max(1, Math.ceil(viewportHeight || best.rect.height || 1))
|
|
1280
|
+
},
|
|
1281
|
+
edgeOcclusion: resolveEdgeOcclusion()
|
|
1282
|
+
};
|
|
1283
|
+
}, {
|
|
1284
|
+
attrName: STITCH_SCROLL_TARGET_ATTR,
|
|
1285
|
+
force: stitchMode === "force",
|
|
1286
|
+
minScrollRatio: MIN_VIRTUALIZED_SCROLL_RATIO,
|
|
1287
|
+
minSparseRatio: MIN_SPARSE_SCROLL_RATIO
|
|
1288
|
+
}).catch((error) => {
|
|
1289
|
+
logger.warning(`\u865A\u62DF\u6EDA\u52A8\u622A\u56FE\u63A2\u6D4B\u5931\u8D25: ${error?.message || error}`);
|
|
1290
|
+
return null;
|
|
1291
|
+
});
|
|
1292
|
+
};
|
|
1293
|
+
var setStitchScrollTop = async (page, target, scrollTop) => {
|
|
1294
|
+
await page.evaluate(({ attrName, kind, top }) => {
|
|
1295
|
+
const el = kind === "document" ? document.scrollingElement || document.documentElement || document.body : document.querySelector(`[${attrName}="1"]`);
|
|
1296
|
+
if (!el) return;
|
|
1297
|
+
el.scrollTop = Math.max(0, Math.round(Number(top) || 0));
|
|
1298
|
+
el.dispatchEvent(new Event("scroll", { bubbles: true }));
|
|
1299
|
+
window.dispatchEvent(new Event("scroll"));
|
|
1300
|
+
}, {
|
|
1301
|
+
attrName: STITCH_SCROLL_TARGET_ATTR,
|
|
1302
|
+
kind: target.kind,
|
|
1303
|
+
top: scrollTop
|
|
1304
|
+
});
|
|
1305
|
+
};
|
|
1306
|
+
var cleanupStitchTarget = async (page) => {
|
|
1307
|
+
await page.evaluate((attrName) => {
|
|
1308
|
+
document.querySelectorAll(`[${attrName}="1"]`).forEach((node) => {
|
|
1309
|
+
node.removeAttribute(attrName);
|
|
1310
|
+
});
|
|
1311
|
+
}, STITCH_SCROLL_TARGET_ATTR).catch(() => {
|
|
1312
|
+
});
|
|
1313
|
+
};
|
|
1314
|
+
var cropImage = (image, crop) => image.clone().crop({
|
|
1315
|
+
x: Math.max(0, Math.round(crop.x || 0)),
|
|
1316
|
+
y: Math.max(0, Math.round(crop.y || 0)),
|
|
1317
|
+
w: Math.max(1, Math.round(crop.w || 1)),
|
|
1318
|
+
h: Math.max(1, Math.round(crop.h || 1))
|
|
1319
|
+
});
|
|
1320
|
+
var captureStitchedScrollableScreenshot = async (page, target, options = {}) => {
|
|
1321
|
+
const maxHeight = toPositiveInteger(options.maxHeight, DEFAULT_MAX_HEIGHT);
|
|
1322
|
+
const settleMs = Math.max(0, Number(options.stitchSettleMs ?? DEFAULT_STITCH_SETTLE_MS) || 0);
|
|
1323
|
+
const overlapPx = Math.max(0, Math.round(Number(options.stitchOverlapPx ?? DEFAULT_STITCH_OVERLAP_PX) || 0));
|
|
1324
|
+
const viewport = target.viewport || await resolveCurrentViewportSize(page);
|
|
1325
|
+
const rect = target.rect || {
|
|
1326
|
+
top: 0,
|
|
1327
|
+
bottom: viewport.height,
|
|
1328
|
+
left: 0,
|
|
1329
|
+
width: viewport.width,
|
|
1330
|
+
height: viewport.height
|
|
1331
|
+
};
|
|
1332
|
+
const edge = target.edgeOcclusion || { top: 0, bottom: 0 };
|
|
1333
|
+
const topCrop = Math.max(0, Math.min(rect.top, viewport.height - 1));
|
|
1334
|
+
const topOverlay = Math.max(topCrop, Math.min(edge.top || 0, viewport.height - 1));
|
|
1335
|
+
const bottomOverlay = Math.max(0, Math.min(edge.bottom || 0, viewport.height - topOverlay - 1));
|
|
1336
|
+
const middleCropY = Math.max(topCrop, topOverlay);
|
|
1337
|
+
const middleCropBottom = Math.max(
|
|
1338
|
+
middleCropY + 1,
|
|
1339
|
+
Math.min(rect.bottom || viewport.height, viewport.height - bottomOverlay)
|
|
1340
|
+
);
|
|
1341
|
+
const middleCropHeight = Math.max(1, middleCropBottom - middleCropY);
|
|
1342
|
+
const scrollStep = Math.max(120, middleCropHeight - overlapPx);
|
|
1343
|
+
const maxScrollTop = Math.max(0, Math.ceil(target.scrollHeight - target.clientHeight));
|
|
1344
|
+
const positions = [];
|
|
1345
|
+
for (let top = 0; top < maxScrollTop; top += scrollStep) {
|
|
1346
|
+
positions.push(Math.round(top));
|
|
1347
|
+
}
|
|
1348
|
+
if (!positions.includes(maxScrollTop)) {
|
|
1349
|
+
positions.push(maxScrollTop);
|
|
1350
|
+
}
|
|
1351
|
+
if (positions.length === 0) {
|
|
1352
|
+
positions.push(0);
|
|
1353
|
+
}
|
|
1354
|
+
const segments = [];
|
|
1355
|
+
let totalHeight = 0;
|
|
1356
|
+
let outputWidth = Math.max(1, Math.round(viewport.width || rect.width || 1));
|
|
1357
|
+
try {
|
|
1358
|
+
for (let index = 0; index < positions.length && totalHeight < maxHeight; index += 1) {
|
|
1359
|
+
const isFirst = index === 0;
|
|
1360
|
+
const isLast = index === positions.length - 1;
|
|
1361
|
+
await setStitchScrollTop(page, target, positions[index]);
|
|
1362
|
+
if (settleMs > 0) {
|
|
1363
|
+
await delay(settleMs);
|
|
1364
|
+
}
|
|
1365
|
+
const buffer = await capturePageScreenshot(page, {
|
|
1366
|
+
type: options.type || "png",
|
|
1367
|
+
quality: options.quality,
|
|
1368
|
+
timeout: options.timeout
|
|
1369
|
+
});
|
|
1370
|
+
const image = await Jimp.read(buffer);
|
|
1371
|
+
outputWidth = Math.min(outputWidth, image.bitmap.width);
|
|
1372
|
+
const cropY = isFirst ? 0 : middleCropY;
|
|
1373
|
+
const cropBottom = isLast ? image.bitmap.height : middleCropBottom;
|
|
1374
|
+
let cropHeight = Math.max(1, Math.min(image.bitmap.height, cropBottom) - cropY);
|
|
1375
|
+
const remaining = maxHeight - totalHeight;
|
|
1376
|
+
if (cropHeight > remaining) {
|
|
1377
|
+
cropHeight = remaining;
|
|
1378
|
+
}
|
|
1379
|
+
segments.push(cropImage(image, {
|
|
1380
|
+
x: 0,
|
|
1381
|
+
y: cropY,
|
|
1382
|
+
w: outputWidth,
|
|
1383
|
+
h: cropHeight
|
|
1384
|
+
}));
|
|
1385
|
+
totalHeight += cropHeight;
|
|
1386
|
+
}
|
|
1387
|
+
const canvas = new Jimp({
|
|
1388
|
+
width: outputWidth,
|
|
1389
|
+
height: Math.max(1, totalHeight),
|
|
1390
|
+
color: 4294967295
|
|
1391
|
+
});
|
|
1392
|
+
let y = 0;
|
|
1393
|
+
for (const segment of segments) {
|
|
1394
|
+
canvas.composite(segment, 0, y);
|
|
1395
|
+
y += segment.bitmap.height;
|
|
1396
|
+
}
|
|
1397
|
+
logger.info(
|
|
1398
|
+
`\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)}`
|
|
1399
|
+
);
|
|
1400
|
+
return await canvas.getBuffer(JimpMime.png);
|
|
1401
|
+
} finally {
|
|
1402
|
+
await setStitchScrollTop(page, target, target.scrollTop || 0).catch(() => {
|
|
1403
|
+
});
|
|
1404
|
+
await cleanupStitchTarget(page);
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
var isLightBlankPixel = ({ r, g, b }) => {
|
|
1408
|
+
const max = Math.max(r, g, b);
|
|
1409
|
+
const min = Math.min(r, g, b);
|
|
1410
|
+
return max >= 238 && max - min <= 18;
|
|
1411
|
+
};
|
|
1412
|
+
var isDarkBlankPixel = ({ r, g, b }) => {
|
|
1413
|
+
const max = Math.max(r, g, b);
|
|
1414
|
+
const min = Math.min(r, g, b);
|
|
1415
|
+
return max <= 34 && max - min <= 10;
|
|
1416
|
+
};
|
|
1417
|
+
var isLowInfoPixel = ({ r, g, b }) => {
|
|
1418
|
+
const max = Math.max(r, g, b);
|
|
1419
|
+
const min = Math.min(r, g, b);
|
|
1420
|
+
return max - min <= 12;
|
|
1421
|
+
};
|
|
1422
|
+
var analyzeScreenshotBuffer = async (buffer) => {
|
|
1423
|
+
const image = await Jimp.read(buffer);
|
|
1424
|
+
const width = image.bitmap.width;
|
|
1425
|
+
const height = image.bitmap.height;
|
|
1426
|
+
const stepY = Math.max(1, Math.floor(height / 900));
|
|
1427
|
+
const stepX = Math.max(1, Math.floor(width / 420));
|
|
1428
|
+
const rows = [];
|
|
1429
|
+
let lightRows = 0;
|
|
1430
|
+
let darkRows = 0;
|
|
1431
|
+
let lowInfoRows = 0;
|
|
1432
|
+
let loadingLikeRows = 0;
|
|
1433
|
+
const rowFlags = [];
|
|
1434
|
+
for (let y = 0; y < height; y += stepY) {
|
|
1435
|
+
let light = 0;
|
|
1436
|
+
let dark = 0;
|
|
1437
|
+
let lowInfo = 0;
|
|
1438
|
+
let edge = 0;
|
|
1439
|
+
let count = 0;
|
|
1440
|
+
let prev = null;
|
|
1441
|
+
for (let x = 0; x < width; x += stepX) {
|
|
1442
|
+
const rgba = intToRGBA(image.getPixelColor(x, y));
|
|
1443
|
+
count += 1;
|
|
1444
|
+
if (isLightBlankPixel(rgba)) light += 1;
|
|
1445
|
+
if (isDarkBlankPixel(rgba)) dark += 1;
|
|
1446
|
+
if (isLowInfoPixel(rgba)) lowInfo += 1;
|
|
1447
|
+
if (prev) {
|
|
1448
|
+
const diff = Math.abs(rgba.r - prev.r) + Math.abs(rgba.g - prev.g) + Math.abs(rgba.b - prev.b);
|
|
1449
|
+
if (diff > 45) edge += 1;
|
|
1450
|
+
}
|
|
1451
|
+
prev = rgba;
|
|
1452
|
+
}
|
|
1453
|
+
const stat = {
|
|
1454
|
+
y,
|
|
1455
|
+
lightRatio: light / Math.max(1, count),
|
|
1456
|
+
darkRatio: dark / Math.max(1, count),
|
|
1457
|
+
lowInfoRatio: lowInfo / Math.max(1, count),
|
|
1458
|
+
edgeRatio: edge / Math.max(1, count - 1)
|
|
1459
|
+
};
|
|
1460
|
+
rows.push(stat);
|
|
1461
|
+
const lightBlank = stat.lightRatio >= 0.965 && stat.edgeRatio <= 0.015;
|
|
1462
|
+
const darkBlank = stat.darkRatio >= 0.965 && stat.edgeRatio <= 0.012;
|
|
1463
|
+
const lowInfoBlank = stat.lowInfoRatio >= 0.965 && stat.edgeRatio <= 0.012;
|
|
1464
|
+
const loadingLike = stat.darkRatio >= 0.88 && stat.edgeRatio <= 0.025;
|
|
1465
|
+
if (lightBlank) lightRows += 1;
|
|
1466
|
+
if (darkBlank) darkRows += 1;
|
|
1467
|
+
if (lowInfoBlank) lowInfoRows += 1;
|
|
1468
|
+
if (loadingLike) loadingLikeRows += 1;
|
|
1469
|
+
rowFlags.push({ lightBlank, darkBlank, lowInfoBlank, loadingLike });
|
|
1470
|
+
}
|
|
1471
|
+
const sampledRows = Math.max(1, rows.length);
|
|
1472
|
+
return {
|
|
1473
|
+
image,
|
|
1474
|
+
width,
|
|
1475
|
+
height,
|
|
1476
|
+
lightBlankRowsRatio: lightRows / sampledRows,
|
|
1477
|
+
darkBlankRowsRatio: darkRows / sampledRows,
|
|
1478
|
+
lowInfoRowsRatio: lowInfoRows / sampledRows,
|
|
1479
|
+
loadingLikeRowsRatio: loadingLikeRows / sampledRows,
|
|
1480
|
+
rowFlags
|
|
1481
|
+
};
|
|
1482
|
+
};
|
|
1483
|
+
var resolveScreenshotQualityIssue = (analysis) => {
|
|
1484
|
+
if (!analysis) return null;
|
|
1485
|
+
if (analysis.loadingLikeRowsRatio >= 0.92 || analysis.darkBlankRowsRatio >= 0.72) {
|
|
1486
|
+
return "loading-like-dark-screenshot";
|
|
1487
|
+
}
|
|
1488
|
+
return null;
|
|
1489
|
+
};
|
|
1490
|
+
var cropBufferToContentBounds = async (buffer, bounds, options = {}) => {
|
|
1491
|
+
if (!bounds || bounds.nodes <= 0) return buffer;
|
|
1492
|
+
const image = options.image || await Jimp.read(buffer);
|
|
1493
|
+
const width = image.bitmap.width;
|
|
1494
|
+
const height = image.bitmap.height;
|
|
1495
|
+
const safeLeft = Math.max(0, Math.min(width - 1, Math.floor(bounds.left || 0)));
|
|
1496
|
+
const safeRight = Math.max(safeLeft + 1, Math.min(width, Math.ceil(bounds.right || width)));
|
|
1497
|
+
const safeTop = Math.max(0, Math.min(height - 1, Math.floor(bounds.top || 0)));
|
|
1498
|
+
const safeBottom = Math.max(safeTop + 1, Math.min(height, Math.ceil(bounds.bottom || height)));
|
|
1499
|
+
const cropW = safeRight - safeLeft;
|
|
1500
|
+
const cropH = safeBottom - safeTop;
|
|
1501
|
+
const shouldCropX = cropW > 80 && cropW <= width * 0.82 && (safeLeft > width * 0.04 || width - safeRight > width * 0.04);
|
|
1502
|
+
const shouldCropY = cropH > 160 && cropH <= height * 0.88 && height - safeBottom > Math.max(320, height * 0.1);
|
|
1503
|
+
if (!shouldCropX && !shouldCropY) {
|
|
1504
|
+
return buffer;
|
|
1505
|
+
}
|
|
1506
|
+
const x = shouldCropX ? safeLeft : 0;
|
|
1507
|
+
const y = shouldCropY ? safeTop : 0;
|
|
1508
|
+
const w = shouldCropX ? cropW : width;
|
|
1509
|
+
const h = shouldCropY ? cropH : height;
|
|
1510
|
+
logger.info(`\u5185\u5BB9\u611F\u77E5\u88C1\u526A\u622A\u56FE: x=${x}, y=${y}, w=${w}, h=${h}, original=${width}x${height}`);
|
|
1511
|
+
return await cropImage(image, { x, y, w, h }).getBuffer(JimpMime.png);
|
|
1512
|
+
};
|
|
1513
|
+
var captureExpandedFullPageScreenshotOnce = async (page, options = {}) => {
|
|
1514
|
+
const stitchedTarget = await resolveVirtualizedScrollTarget(page, options);
|
|
1515
|
+
if (stitchedTarget) {
|
|
1516
|
+
return await captureStitchedScrollableScreenshot(page, stitchedTarget, options);
|
|
1517
|
+
}
|
|
1518
|
+
const state2 = await prepareExpandedFullPageScreenshot(page, options);
|
|
1519
|
+
try {
|
|
1520
|
+
const buffer = await capturePageScreenshot(page, {
|
|
1521
|
+
fullPage: true,
|
|
1522
|
+
type: options.type || "png",
|
|
1523
|
+
quality: options.quality,
|
|
1524
|
+
timeout: options.timeout,
|
|
1525
|
+
maxClipHeight: state2.targetHeight
|
|
1526
|
+
});
|
|
1527
|
+
return await cropBufferToContentBounds(buffer, state2.contentBounds);
|
|
1528
|
+
} finally {
|
|
1529
|
+
await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
|
|
1530
|
+
logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
|
|
1531
|
+
});
|
|
1532
|
+
if (options.restore) {
|
|
1533
|
+
await restoreExpandedFullPageScreenshot(page, state2);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
};
|
|
945
1537
|
var capturePageScreenshot = async (page, options = {}) => {
|
|
946
1538
|
const fullPage = Boolean(options.fullPage);
|
|
947
1539
|
const type = fullPage ? FORCED_FULLPAGE_TYPE : normalizeType(options.type);
|
|
@@ -996,23 +1588,35 @@ var capturePageScreenshot = async (page, options = {}) => {
|
|
|
996
1588
|
}
|
|
997
1589
|
};
|
|
998
1590
|
var captureExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
999
|
-
const
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
|
|
1010
|
-
logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
|
|
1591
|
+
const attempts = Math.max(1, toPositiveInteger(options.qualityRetryAttempts, DEFAULT_QUALITY_RETRY_ATTEMPTS));
|
|
1592
|
+
const retryDelayMs = Math.max(0, Number(options.qualityRetryDelayMs ?? DEFAULT_QUALITY_RETRY_DELAY_MS) || 0);
|
|
1593
|
+
let lastBuffer = null;
|
|
1594
|
+
let lastAnalysis = null;
|
|
1595
|
+
let lastIssue = null;
|
|
1596
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
1597
|
+
const buffer = await captureExpandedFullPageScreenshotOnce(page, options);
|
|
1598
|
+
const analysis = await analyzeScreenshotBuffer(buffer).catch((error) => {
|
|
1599
|
+
logger.warning(`\u622A\u56FE\u8D28\u91CF\u5206\u6790\u5931\u8D25: ${error?.message || error}`);
|
|
1600
|
+
return null;
|
|
1011
1601
|
});
|
|
1012
|
-
|
|
1013
|
-
|
|
1602
|
+
const issue = resolveScreenshotQualityIssue(analysis);
|
|
1603
|
+
lastBuffer = buffer;
|
|
1604
|
+
lastAnalysis = analysis;
|
|
1605
|
+
lastIssue = issue;
|
|
1606
|
+
if (!issue) {
|
|
1607
|
+
return buffer;
|
|
1608
|
+
}
|
|
1609
|
+
if (attempt < attempts && retryDelayMs > 0) {
|
|
1610
|
+
logger.warning(`\u622A\u56FE\u7591\u4F3C\u5F02\u5E38(${issue})\uFF0C\u7B49\u5F85\u540E\u91CD\u8BD5: attempt=${attempt}/${attempts}`);
|
|
1611
|
+
await delay(retryDelayMs);
|
|
1014
1612
|
}
|
|
1015
1613
|
}
|
|
1614
|
+
if (lastIssue && lastAnalysis) {
|
|
1615
|
+
logger.warning(
|
|
1616
|
+
`\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)}`
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
return lastBuffer;
|
|
1016
1620
|
};
|
|
1017
1621
|
|
|
1018
1622
|
// src/errors.js
|
|
@@ -9688,7 +10292,7 @@ var watermarkifyScreenshotBuffer = async (buffer, meta, page = null, options = {
|
|
|
9688
10292
|
};
|
|
9689
10293
|
|
|
9690
10294
|
// src/internals/compression.js
|
|
9691
|
-
import { Jimp, JimpMime, ResizeStrategy } from "jimp";
|
|
10295
|
+
import { Jimp as Jimp2, JimpMime as JimpMime2, ResizeStrategy } from "jimp";
|
|
9692
10296
|
var logger15 = createInternalLogger("Compression");
|
|
9693
10297
|
var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
|
|
9694
10298
|
var DEFAULT_SCREENSHOT_OUTPUT_TYPE = "jpeg";
|
|
@@ -9758,7 +10362,7 @@ var encodeJpeg = async (sourceImage, compression, scale, quality) => {
|
|
|
9758
10362
|
mode: ResizeStrategy.BILINEAR
|
|
9759
10363
|
});
|
|
9760
10364
|
}
|
|
9761
|
-
const buffer = await image.getBuffer(
|
|
10365
|
+
const buffer = await image.getBuffer(JimpMime2.jpeg, { quality });
|
|
9762
10366
|
return {
|
|
9763
10367
|
buffer,
|
|
9764
10368
|
bytes: getBase64BytesFromBuffer(buffer),
|
|
@@ -9770,7 +10374,7 @@ var encodeJpeg = async (sourceImage, compression, scale, quality) => {
|
|
|
9770
10374
|
};
|
|
9771
10375
|
};
|
|
9772
10376
|
var compressImageBuffer = async (buffer, compression) => {
|
|
9773
|
-
const sourceImage = await
|
|
10377
|
+
const sourceImage = await Jimp2.read(buffer);
|
|
9774
10378
|
const maxQuality = toJpegQuality(compression.quality);
|
|
9775
10379
|
const minQuality = Math.min(maxQuality, toJpegQuality(compression.minQuality));
|
|
9776
10380
|
let quality = maxQuality;
|