@skrillex1224/playwright-toolkit 3.0.26 → 3.0.28
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 +1 -1
- package/dist/browser.js.map +1 -1
- package/dist/index.cjs +486 -983
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +489 -986
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -202,7 +202,7 @@ var ActorInfo = {
|
|
|
202
202
|
mode: "response",
|
|
203
203
|
prefix: "https://www.doubao.com/thread/",
|
|
204
204
|
xurl: [
|
|
205
|
-
"/
|
|
205
|
+
"/share/save",
|
|
206
206
|
"data",
|
|
207
207
|
"share_id"
|
|
208
208
|
]
|
|
@@ -467,10 +467,7 @@ function createInternalLogger(moduleName, explicitLogger) {
|
|
|
467
467
|
|
|
468
468
|
// src/internals/screenshot.js
|
|
469
469
|
import delay from "delay";
|
|
470
|
-
import { Jimp,
|
|
471
|
-
|
|
472
|
-
// src/internals/constants.js
|
|
473
|
-
var PageRuntimeStateKey = "__playwright_toolkit_runtime_state__";
|
|
470
|
+
import { Jimp, intToRGBA } from "jimp";
|
|
474
471
|
|
|
475
472
|
// src/internals/viewport.js
|
|
476
473
|
var toPositiveInt = (value) => {
|
|
@@ -502,35 +499,22 @@ var resolveCurrentViewportSize = async (page, fallback = {}) => {
|
|
|
502
499
|
// src/internals/screenshot.js
|
|
503
500
|
var logger = createInternalLogger("Screenshot");
|
|
504
501
|
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
505
|
-
var FORCED_FULLPAGE_TYPE = "jpeg";
|
|
506
|
-
var FORCED_FULLPAGE_QUALITY = 50;
|
|
507
|
-
var SUPPORTED_TYPES = /* @__PURE__ */ new Set(["png", "jpeg", "webp"]);
|
|
508
|
-
var EXPANDED_SCROLLABLE_CLASS = "__pk_expanded__";
|
|
509
|
-
var STITCH_SCROLL_TARGET_ATTR = "data-pk-stitch-scroll-target";
|
|
510
502
|
var DEFAULT_MAX_HEIGHT = 8e3;
|
|
511
|
-
var DEFAULT_SETTLE_MS =
|
|
512
|
-
var
|
|
513
|
-
var DEFAULT_STITCH_SETTLE_MS = 120;
|
|
514
|
-
var DEFAULT_STITCH_OVERLAP_PX = 24;
|
|
515
|
-
var MIN_VIRTUALIZED_SCROLL_RATIO = 2.2;
|
|
516
|
-
var MIN_SPARSE_SCROLL_RATIO = 4;
|
|
517
|
-
var MIN_STITCH_VISIBLE_HEIGHT_PX = 120;
|
|
518
|
-
var MIN_STITCH_VISIBLE_HEIGHT_RATIO = 0.22;
|
|
519
|
-
var MIN_STITCH_OUTPUT_HEIGHT_RATIO = 0.35;
|
|
520
|
-
var MIN_STITCH_OUTPUT_VIEWPORT_RATIO = 1.15;
|
|
521
|
-
var MOBILE_VIEWPORT_WIDTH_THRESHOLD = 520;
|
|
522
|
-
var DEFAULT_QUALITY_RETRY_ATTEMPTS = 2;
|
|
503
|
+
var DEFAULT_SETTLE_MS = 260;
|
|
504
|
+
var DEFAULT_QUALITY_RETRY_ATTEMPTS = 4;
|
|
523
505
|
var DEFAULT_QUALITY_RETRY_DELAY_MS = 2500;
|
|
524
|
-
var
|
|
525
|
-
var
|
|
506
|
+
var EXPANDED_ATTR = "data-pk-screenshot-expanded";
|
|
507
|
+
var SUPPORTED_TYPES = /* @__PURE__ */ new Set(["png", "jpeg", "webp"]);
|
|
508
|
+
var FULLPAGE_FALLBACK_TYPE = "jpeg";
|
|
509
|
+
var FULLPAGE_FALLBACK_QUALITY = 50;
|
|
526
510
|
var toPositiveNumber = (value, fallback = 0) => {
|
|
527
511
|
const n = Number(value);
|
|
528
512
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
529
513
|
return n;
|
|
530
514
|
};
|
|
531
515
|
var toPositiveInteger = (value, fallback = 0) => {
|
|
532
|
-
const
|
|
533
|
-
return
|
|
516
|
+
const n = Math.round(Number(value) || 0);
|
|
517
|
+
return n > 0 ? n : fallback;
|
|
534
518
|
};
|
|
535
519
|
var normalizeType = (value) => {
|
|
536
520
|
const raw = String(value || "png").trim().toLowerCase();
|
|
@@ -545,964 +529,585 @@ var normalizeQuality = (value, type) => {
|
|
|
545
529
|
if (rounded < 0 || rounded > 100) return void 0;
|
|
546
530
|
return rounded;
|
|
547
531
|
};
|
|
548
|
-
var resolvePageDevice = async (page) => {
|
|
549
|
-
const declared = normalizeDevice(page?.[PageRuntimeStateKey]?.device);
|
|
550
|
-
if (declared === Device.Mobile) return Device.Mobile;
|
|
551
|
-
const viewport = await resolveCurrentViewportSize(page).catch(() => null);
|
|
552
|
-
const viewportWidth = Math.round(Number(viewport?.width) || 0);
|
|
553
|
-
if (viewportWidth > 0 && viewportWidth <= MOBILE_VIEWPORT_WIDTH_THRESHOLD) {
|
|
554
|
-
return Device.Mobile;
|
|
555
|
-
}
|
|
556
|
-
return declared;
|
|
557
|
-
};
|
|
558
|
-
var resolveStitchMode = (options = {}) => {
|
|
559
|
-
const raw = options.stitch ?? options.stitching ?? options.strategy;
|
|
560
|
-
if (raw === false || raw === "off" || raw === "none" || raw === "single") return "off";
|
|
561
|
-
if (raw === true || raw === "force" || raw === "stitched") return "force";
|
|
562
|
-
return "auto";
|
|
563
|
-
};
|
|
564
532
|
var buildFullPageClip = (metrics, viewport, maxClipHeight) => {
|
|
565
533
|
const contentSize = metrics && typeof metrics === "object" ? metrics.contentSize || null : null;
|
|
566
534
|
const width = Math.max(1, Math.ceil(viewport.width || contentSize?.width || 1));
|
|
567
535
|
let height = Math.max(1, Math.ceil(contentSize?.height || viewport.height || 1));
|
|
568
|
-
if (maxClipHeight > 0)
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
536
|
+
if (maxClipHeight > 0) height = Math.min(height, maxClipHeight);
|
|
537
|
+
return { x: 0, y: 0, width, height, scale: 1 };
|
|
538
|
+
};
|
|
539
|
+
var capturePageScreenshot = async (page, options = {}) => {
|
|
540
|
+
const fullPage = Boolean(options.fullPage);
|
|
541
|
+
const type = fullPage ? FULLPAGE_FALLBACK_TYPE : normalizeType(options.type);
|
|
542
|
+
const quality = fullPage ? FULLPAGE_FALLBACK_QUALITY : normalizeQuality(options.quality, type);
|
|
543
|
+
const timeout = toPositiveNumber(options.timeout, DEFAULT_TIMEOUT_MS);
|
|
544
|
+
const maxClipHeight = Math.round(toPositiveNumber(options.maxClipHeight, 0));
|
|
545
|
+
const fallbackOptions = {
|
|
546
|
+
type: type === "webp" ? "png" : type,
|
|
547
|
+
fullPage
|
|
577
548
|
};
|
|
549
|
+
if (quality !== void 0 && fallbackOptions.type === "jpeg") fallbackOptions.quality = quality;
|
|
550
|
+
if (timeout > 0) fallbackOptions.timeout = timeout;
|
|
551
|
+
try {
|
|
552
|
+
const context = page && typeof page.context === "function" ? page.context() : null;
|
|
553
|
+
if (!context || typeof context.newCDPSession !== "function") {
|
|
554
|
+
throw new Error("CDP session is not available");
|
|
555
|
+
}
|
|
556
|
+
const session = await context.newCDPSession(page);
|
|
557
|
+
try {
|
|
558
|
+
const viewport = await resolveCurrentViewportSize(page);
|
|
559
|
+
const captureParams = {
|
|
560
|
+
format: type,
|
|
561
|
+
fromSurface: true,
|
|
562
|
+
captureBeyondViewport: fullPage,
|
|
563
|
+
optimizeForSpeed: true
|
|
564
|
+
};
|
|
565
|
+
if (quality !== void 0) captureParams.quality = quality;
|
|
566
|
+
if (fullPage) {
|
|
567
|
+
const metrics = await session.send("Page.getLayoutMetrics");
|
|
568
|
+
captureParams.clip = buildFullPageClip(metrics, viewport, maxClipHeight);
|
|
569
|
+
}
|
|
570
|
+
const result = await session.send("Page.captureScreenshot", captureParams);
|
|
571
|
+
if (!result || typeof result.data !== "string" || !result.data) {
|
|
572
|
+
throw new Error("CDP returned empty screenshot data");
|
|
573
|
+
}
|
|
574
|
+
return Buffer.from(result.data, "base64");
|
|
575
|
+
} finally {
|
|
576
|
+
if (typeof session.detach === "function") {
|
|
577
|
+
await session.detach().catch(() => {
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
} catch (error) {
|
|
582
|
+
logger.warning(`CDP \u622A\u56FE\u5931\u8D25\uFF0C\u56DE\u9000 page.screenshot: ${error?.message || error}`);
|
|
583
|
+
return await page.screenshot(fallbackOptions);
|
|
584
|
+
}
|
|
578
585
|
};
|
|
579
|
-
var
|
|
580
|
-
return await page.evaluate((
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
586
|
+
var readMainFrameScroll = async (page) => {
|
|
587
|
+
return await page.evaluate(() => ({
|
|
588
|
+
x: Math.max(0, Math.round(window.scrollX || document.scrollingElement?.scrollLeft || 0)),
|
|
589
|
+
y: Math.max(0, Math.round(window.scrollY || document.scrollingElement?.scrollTop || 0))
|
|
590
|
+
})).catch(() => ({ x: 0, y: 0 }));
|
|
591
|
+
};
|
|
592
|
+
var restoreMainFrameScroll = async (page, state2) => {
|
|
593
|
+
if (!state2) return;
|
|
594
|
+
await page.evaluate(({ x, y }) => {
|
|
595
|
+
window.scrollTo(Math.max(0, Math.round(Number(x) || 0)), Math.max(0, Math.round(Number(y) || 0)));
|
|
596
|
+
}, state2).catch(() => {
|
|
597
|
+
});
|
|
598
|
+
};
|
|
599
|
+
var freezeViewportMetrics = async (page) => {
|
|
600
|
+
const viewport = await resolveCurrentViewportSize(page).catch(() => null);
|
|
601
|
+
if (!viewport?.width || !viewport?.height) return false;
|
|
602
|
+
return await page.evaluate(({ width, height }) => {
|
|
603
|
+
const key = "__pkScreenshotViewportFreeze";
|
|
604
|
+
if (window[key]?.active) return true;
|
|
605
|
+
const state2 = {
|
|
606
|
+
active: true,
|
|
607
|
+
entries: []
|
|
608
|
+
};
|
|
609
|
+
const override = (target, prop, value) => {
|
|
610
|
+
if (!target) return;
|
|
611
|
+
try {
|
|
612
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, prop);
|
|
613
|
+
state2.entries.push({
|
|
614
|
+
target,
|
|
615
|
+
prop,
|
|
616
|
+
hadOwn: Boolean(descriptor),
|
|
617
|
+
descriptor
|
|
618
|
+
});
|
|
619
|
+
Object.defineProperty(target, prop, {
|
|
620
|
+
configurable: true,
|
|
621
|
+
get: () => value
|
|
622
|
+
});
|
|
623
|
+
} catch {
|
|
624
|
+
}
|
|
591
625
|
};
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
626
|
+
override(window, "innerWidth", width);
|
|
627
|
+
override(window, "innerHeight", height);
|
|
628
|
+
if (window.visualViewport) {
|
|
629
|
+
override(window.visualViewport, "width", width);
|
|
630
|
+
override(window.visualViewport, "height", height);
|
|
631
|
+
}
|
|
632
|
+
window[key] = state2;
|
|
633
|
+
return true;
|
|
634
|
+
}, {
|
|
635
|
+
width: Math.max(1, Math.round(viewport.width)),
|
|
636
|
+
height: Math.max(1, Math.round(viewport.height))
|
|
637
|
+
}).catch((error) => {
|
|
638
|
+
logger.warning(`\u51BB\u7ED3 viewport \u6307\u6807\u5931\u8D25: ${error?.message || error}`);
|
|
639
|
+
return false;
|
|
640
|
+
});
|
|
641
|
+
};
|
|
642
|
+
var restoreViewportMetrics = async (page) => {
|
|
643
|
+
await page.evaluate(() => {
|
|
644
|
+
const key = "__pkScreenshotViewportFreeze";
|
|
645
|
+
const state2 = window[key];
|
|
646
|
+
if (!state2?.active) return;
|
|
647
|
+
for (const entry of [...state2.entries].reverse()) {
|
|
648
|
+
try {
|
|
649
|
+
if (entry.hadOwn && entry.descriptor) {
|
|
650
|
+
Object.defineProperty(entry.target, entry.prop, entry.descriptor);
|
|
651
|
+
} else {
|
|
652
|
+
delete entry.target[entry.prop];
|
|
653
|
+
}
|
|
654
|
+
} catch {
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
delete window[key];
|
|
658
|
+
}).catch(() => {
|
|
659
|
+
});
|
|
660
|
+
};
|
|
661
|
+
var expandScrollableContentInContext = async (context, options = {}) => {
|
|
662
|
+
return await context.evaluate(async ({
|
|
663
|
+
expandedAttr,
|
|
664
|
+
maxHeight,
|
|
665
|
+
preloadLazyContent,
|
|
666
|
+
preloadSettleMs,
|
|
667
|
+
visibleOnly
|
|
668
|
+
}) => {
|
|
669
|
+
const root = document.documentElement;
|
|
670
|
+
const body = document.body;
|
|
671
|
+
const scrollingElement = document.scrollingElement || root || body;
|
|
672
|
+
const viewportWidth = window.innerWidth || root?.clientWidth || body?.clientWidth || 1;
|
|
673
|
+
const viewportHeight = window.innerHeight || root?.clientHeight || body?.clientHeight || 1;
|
|
596
674
|
const scrollableOverflow = /* @__PURE__ */ new Set(["auto", "scroll", "overlay"]);
|
|
597
675
|
const clippingOverflow = /* @__PURE__ */ new Set(["hidden", "clip"]);
|
|
676
|
+
const candidates = [];
|
|
677
|
+
const changed = [];
|
|
678
|
+
const push = (el) => {
|
|
679
|
+
if (el && !candidates.includes(el)) candidates.push(el);
|
|
680
|
+
};
|
|
681
|
+
push(scrollingElement);
|
|
682
|
+
push(root);
|
|
683
|
+
push(body);
|
|
684
|
+
document.querySelectorAll("*").forEach(push);
|
|
598
685
|
const isDocumentElement = (el) => el === root || el === body || el === scrollingElement;
|
|
686
|
+
const delayMs = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, Number(ms) || 0)));
|
|
687
|
+
const styleValue = (el, prop) => el.style.getPropertyValue(prop) || "";
|
|
688
|
+
const stylePriority = (el, prop) => el.style.getPropertyPriority(prop) || "";
|
|
599
689
|
const isVisible = (el, style, rect) => {
|
|
600
690
|
if (!el || !style || !rect) return false;
|
|
601
|
-
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse")
|
|
602
|
-
|
|
603
|
-
}
|
|
604
|
-
if (Number(style.opacity) === 0) return false;
|
|
691
|
+
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") return false;
|
|
692
|
+
if (Number(style.opacity || 1) === 0) return false;
|
|
605
693
|
return rect.width > 0 && rect.height > 0;
|
|
606
694
|
};
|
|
695
|
+
const intersectsViewport = (rect) => {
|
|
696
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return false;
|
|
697
|
+
return rect.bottom > 0 && rect.right > 0 && rect.top < viewportHeight && rect.left < viewportWidth;
|
|
698
|
+
};
|
|
699
|
+
const isVisibleInViewport = (el, style, rect) => isVisible(el, style, rect) && intersectsViewport(rect);
|
|
607
700
|
const hasOverflowValue = (style, values) => {
|
|
608
|
-
const overflowY = String(style?.overflowY || "").toLowerCase();
|
|
609
701
|
const overflow = String(style?.overflow || "").toLowerCase();
|
|
610
|
-
|
|
702
|
+
const overflowY = String(style?.overflowY || "").toLowerCase();
|
|
703
|
+
return values.has(overflow) || values.has(overflowY);
|
|
611
704
|
};
|
|
612
705
|
const isLargeVerticalClipper = (el, rect) => {
|
|
613
706
|
if (!el || !rect) return false;
|
|
614
|
-
const
|
|
615
|
-
const visibleHeight = Math.ceil(rect.height || 0);
|
|
616
|
-
const clientHeight = Math.ceil(el.clientHeight || 0);
|
|
617
|
-
const boxHeight = Math.max(visibleHeight, clientHeight);
|
|
707
|
+
const boxHeight = Math.max(Math.ceil(rect.height || 0), Math.ceil(el.clientHeight || 0));
|
|
618
708
|
return boxHeight >= Math.max(320, viewportHeight * 0.45);
|
|
619
709
|
};
|
|
620
|
-
const hasLargeVerticalOverflow = (el, rect) => {
|
|
621
|
-
if (!isLargeVerticalClipper(el, rect)) return false;
|
|
622
|
-
const viewportHeight = window.innerHeight || 0;
|
|
623
|
-
const scrollHeight = Math.ceil(el.scrollHeight || 0);
|
|
624
|
-
const clientHeight = Math.ceil(el.clientHeight || 0);
|
|
625
|
-
const overflowDelta = scrollHeight - clientHeight;
|
|
626
|
-
return overflowDelta >= Math.max(160, viewportHeight * 0.18);
|
|
627
|
-
};
|
|
628
|
-
const rememberOriginalBoxStyles = (el, { includeDocumentElement = false } = {}) => {
|
|
629
|
-
if (!el || !includeDocumentElement && isDocumentElement(el) || el.classList.contains(className)) {
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
el.dataset.pkOrigOverflow = el.style.overflow;
|
|
633
|
-
el.dataset.pkOrigOverflowPriority = el.style.getPropertyPriority("overflow") || "";
|
|
634
|
-
el.dataset.pkOrigHeight = el.style.height;
|
|
635
|
-
el.dataset.pkOrigHeightPriority = el.style.getPropertyPriority("height") || "";
|
|
636
|
-
el.dataset.pkOrigMinHeight = el.style.minHeight;
|
|
637
|
-
el.dataset.pkOrigMinHeightPriority = el.style.getPropertyPriority("min-height") || "";
|
|
638
|
-
el.dataset.pkOrigMaxHeight = el.style.maxHeight;
|
|
639
|
-
el.dataset.pkOrigMaxHeightPriority = el.style.getPropertyPriority("max-height") || "";
|
|
640
|
-
el.classList.add(className);
|
|
641
|
-
};
|
|
642
|
-
const expandDocumentElementsToHeight = (height) => {
|
|
643
|
-
if (!expandDocumentElements) return;
|
|
644
|
-
const targetHeight = Math.ceil(Number(height) || 0);
|
|
645
|
-
if (targetHeight <= 0) return;
|
|
646
|
-
[root, body, scrollingElement].forEach((el) => {
|
|
647
|
-
if (!el) return;
|
|
648
|
-
rememberOriginalBoxStyles(el, { includeDocumentElement: true });
|
|
649
|
-
el.style.setProperty("overflow", "visible", "important");
|
|
650
|
-
el.style.setProperty("height", `${targetHeight}px`, "important");
|
|
651
|
-
el.style.setProperty("min-height", `${targetHeight}px`, "important");
|
|
652
|
-
el.style.setProperty("max-height", "none", "important");
|
|
653
|
-
});
|
|
654
|
-
};
|
|
655
710
|
const isScrollableY = (el, style, rect) => {
|
|
656
|
-
if (!el || el.scrollHeight <= el.clientHeight +
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
if (
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
if (hasOverflowValue(style, scrollableOverflow)) {
|
|
663
|
-
return true;
|
|
664
|
-
}
|
|
665
|
-
return hasOverflowValue(style, clippingOverflow) && hasLargeVerticalOverflow(el, rect);
|
|
711
|
+
if (!el || Math.ceil(el.scrollHeight || 0) <= Math.ceil(el.clientHeight || 0) + 2) return false;
|
|
712
|
+
if (isDocumentElement(el)) return true;
|
|
713
|
+
if (hasOverflowValue(style, scrollableOverflow)) return true;
|
|
714
|
+
if (!hasOverflowValue(style, clippingOverflow)) return false;
|
|
715
|
+
const overflowDelta = Math.ceil(el.scrollHeight || 0) - Math.ceil(el.clientHeight || 0);
|
|
716
|
+
return isLargeVerticalClipper(el, rect) && overflowDelta >= Math.max(160, viewportHeight * 0.18);
|
|
666
717
|
};
|
|
667
|
-
const
|
|
668
|
-
if (!el) return
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
718
|
+
const rememberBox = (el) => {
|
|
719
|
+
if (!el || el.getAttribute(expandedAttr) === "1") return;
|
|
720
|
+
el.setAttribute(expandedAttr, "1");
|
|
721
|
+
el.dataset.pkSsOverflow = styleValue(el, "overflow");
|
|
722
|
+
el.dataset.pkSsOverflowPriority = stylePriority(el, "overflow");
|
|
723
|
+
el.dataset.pkSsOverflowX = styleValue(el, "overflow-x");
|
|
724
|
+
el.dataset.pkSsOverflowXPriority = stylePriority(el, "overflow-x");
|
|
725
|
+
el.dataset.pkSsOverflowY = styleValue(el, "overflow-y");
|
|
726
|
+
el.dataset.pkSsOverflowYPriority = stylePriority(el, "overflow-y");
|
|
727
|
+
el.dataset.pkSsHeight = styleValue(el, "height");
|
|
728
|
+
el.dataset.pkSsHeightPriority = stylePriority(el, "height");
|
|
729
|
+
el.dataset.pkSsMinHeight = styleValue(el, "min-height");
|
|
730
|
+
el.dataset.pkSsMinHeightPriority = stylePriority(el, "min-height");
|
|
731
|
+
el.dataset.pkSsMaxHeight = styleValue(el, "max-height");
|
|
732
|
+
el.dataset.pkSsMaxHeightPriority = stylePriority(el, "max-height");
|
|
733
|
+
el.dataset.pkSsWidth = styleValue(el, "width");
|
|
734
|
+
el.dataset.pkSsWidthPriority = stylePriority(el, "width");
|
|
735
|
+
el.dataset.pkSsBoxSizing = styleValue(el, "box-sizing");
|
|
736
|
+
el.dataset.pkSsBoxSizingPriority = stylePriority(el, "box-sizing");
|
|
737
|
+
el.dataset.pkSsAlignSelf = styleValue(el, "align-self");
|
|
738
|
+
el.dataset.pkSsAlignSelfPriority = stylePriority(el, "align-self");
|
|
739
|
+
el.dataset.pkSsJustifySelf = styleValue(el, "justify-self");
|
|
740
|
+
el.dataset.pkSsJustifySelfPriority = stylePriority(el, "justify-self");
|
|
741
|
+
el.dataset.pkSsScrollTop = String(Math.max(0, Math.round(el.scrollTop || 0)));
|
|
742
|
+
el.dataset.pkSsScrollLeft = String(Math.max(0, Math.round(el.scrollLeft || 0)));
|
|
743
|
+
changed.push(el);
|
|
680
744
|
};
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
if (!el || isDocumentElement(el)) return;
|
|
685
|
-
const scrollHeight = Math.ceil(el.scrollHeight || 0);
|
|
686
|
-
if (scrollHeight <= 0) return;
|
|
687
|
-
rememberOriginalBoxStyles(el);
|
|
745
|
+
const setExpandedBox = (el, { height, width }) => {
|
|
746
|
+
if (!el) return;
|
|
747
|
+
rememberBox(el);
|
|
688
748
|
el.style.setProperty("overflow", "visible", "important");
|
|
689
|
-
el.style.setProperty("
|
|
690
|
-
el.style.setProperty("min-height", `${scrollHeight}px`, "important");
|
|
749
|
+
el.style.setProperty("overflow-y", "visible", "important");
|
|
691
750
|
el.style.setProperty("max-height", "none", "important");
|
|
751
|
+
if (height > 0) {
|
|
752
|
+
const cssHeight = `${Math.ceil(height)}px`;
|
|
753
|
+
el.style.setProperty("height", cssHeight, "important");
|
|
754
|
+
el.style.setProperty("min-height", cssHeight, "important");
|
|
755
|
+
}
|
|
756
|
+
if (!isDocumentElement(el) && width > 0) {
|
|
757
|
+
el.style.setProperty("box-sizing", "border-box", "important");
|
|
758
|
+
el.style.setProperty("width", `${Math.ceil(width)}px`, "important");
|
|
759
|
+
el.style.setProperty("align-self", "flex-start", "important");
|
|
760
|
+
el.style.setProperty("justify-self", "start", "important");
|
|
761
|
+
}
|
|
762
|
+
el.scrollTop = 0;
|
|
763
|
+
el.scrollLeft = 0;
|
|
692
764
|
};
|
|
693
765
|
const expandClippingAncestors = (el) => {
|
|
694
766
|
for (let node = el?.parentElement; node && !isDocumentElement(node); node = node.parentElement) {
|
|
695
767
|
const style = window.getComputedStyle(node);
|
|
696
768
|
const rect = node.getBoundingClientRect();
|
|
697
769
|
if (!isVisible(node, style, rect)) continue;
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
rememberOriginalBoxStyles(node);
|
|
770
|
+
const clips = hasOverflowValue(style, scrollableOverflow) || hasOverflowValue(style, clippingOverflow);
|
|
771
|
+
if (!clips) continue;
|
|
772
|
+
rememberBox(node);
|
|
702
773
|
node.style.setProperty("overflow", "visible", "important");
|
|
774
|
+
node.style.setProperty("overflow-y", "visible", "important");
|
|
703
775
|
node.style.setProperty("max-height", "none", "important");
|
|
704
|
-
expandedAncestors.push(node);
|
|
705
776
|
}
|
|
706
777
|
};
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
778
|
+
const preloadCandidates = [];
|
|
779
|
+
for (const el of candidates) {
|
|
780
|
+
const style = window.getComputedStyle(el);
|
|
781
|
+
const rect = isDocumentElement(el) ? { top: 0, left: 0, width: viewportWidth, height: viewportHeight } : el.getBoundingClientRect();
|
|
782
|
+
if (visibleOnly && !isDocumentElement(el) && !isVisibleInViewport(el, style, rect)) continue;
|
|
783
|
+
if (!isScrollableY(el, style, rect)) continue;
|
|
784
|
+
preloadCandidates.push(el);
|
|
785
|
+
}
|
|
786
|
+
if (preloadLazyContent && preloadCandidates.length > 0) {
|
|
787
|
+
preloadCandidates.forEach((el) => {
|
|
788
|
+
rememberBox(el);
|
|
789
|
+
const maxTop = Math.max(0, Math.ceil((el.scrollHeight || 0) - (el.clientHeight || viewportHeight || 0)));
|
|
790
|
+
if (isDocumentElement(el)) {
|
|
791
|
+
window.scrollTo(window.scrollX || 0, maxTop);
|
|
792
|
+
} else {
|
|
793
|
+
el.scrollTop = maxTop;
|
|
794
|
+
el.dispatchEvent(new Event("scroll", { bubbles: true }));
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
window.dispatchEvent(new Event("scroll"));
|
|
798
|
+
await delayMs(preloadSettleMs);
|
|
799
|
+
}
|
|
800
|
+
let targetHeight = viewportHeight;
|
|
801
|
+
let scrollableCount = 0;
|
|
802
|
+
let expandedCount = 0;
|
|
803
|
+
for (const el of preloadCandidates) {
|
|
804
|
+
const rect = isDocumentElement(el) ? { top: 0, left: 0, width: viewportWidth, height: viewportHeight } : el.getBoundingClientRect();
|
|
805
|
+
const scrollHeight = Math.ceil(el.scrollHeight || 0);
|
|
806
|
+
const clientHeight = Math.ceil(el.clientHeight || viewportHeight || 0);
|
|
807
|
+
if (scrollHeight <= clientHeight + 2) continue;
|
|
808
|
+
scrollableCount += 1;
|
|
809
|
+
if (isDocumentElement(el)) {
|
|
810
|
+
targetHeight = Math.max(targetHeight, scrollHeight);
|
|
811
|
+
el.scrollTop = 0;
|
|
812
|
+
continue;
|
|
719
813
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
814
|
+
const documentTop = Math.max(0, Math.ceil(rect.top + (window.scrollY || window.pageYOffset || 0)));
|
|
815
|
+
const width = Math.max(1, Math.ceil(rect.width || el.clientWidth || viewportWidth || 1));
|
|
816
|
+
targetHeight = Math.max(targetHeight, documentTop + scrollHeight);
|
|
817
|
+
expandClippingAncestors(el);
|
|
818
|
+
setExpandedBox(el, { height: scrollHeight, width });
|
|
819
|
+
expandedCount += 1;
|
|
820
|
+
}
|
|
821
|
+
for (let pass = 0; pass < 2; pass += 1) {
|
|
822
|
+
for (const el of changed) {
|
|
823
|
+
if (!el || isDocumentElement(el)) continue;
|
|
824
|
+
const height = Math.ceil(el.scrollHeight || 0);
|
|
825
|
+
if (height > Math.ceil(el.clientHeight || 0) + 2) {
|
|
826
|
+
el.style.setProperty("height", `${height}px`, "important");
|
|
827
|
+
el.style.setProperty("min-height", `${height}px`, "important");
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
const measureDocumentHeight = () => Math.max(
|
|
832
|
+
viewportHeight,
|
|
833
|
+
Math.ceil(root?.scrollHeight || 0),
|
|
834
|
+
Math.ceil(body?.scrollHeight || 0),
|
|
835
|
+
Math.ceil(scrollingElement?.scrollHeight || 0)
|
|
836
|
+
);
|
|
837
|
+
const measureExpandedBottom = () => {
|
|
838
|
+
let bottom = viewportHeight;
|
|
723
839
|
const scrollY = window.scrollY || window.pageYOffset || 0;
|
|
724
|
-
|
|
840
|
+
document.querySelectorAll(`[${expandedAttr}="1"]`).forEach((el) => {
|
|
725
841
|
if (!el || isDocumentElement(el)) return;
|
|
726
|
-
if (!el.classList.contains(className) && !el.closest(`.${className}`)) return;
|
|
727
|
-
const style = window.getComputedStyle(el);
|
|
728
842
|
const rect = el.getBoundingClientRect();
|
|
729
|
-
if (!
|
|
730
|
-
|
|
843
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return;
|
|
844
|
+
bottom = Math.max(bottom, Math.ceil(rect.bottom + scrollY));
|
|
731
845
|
});
|
|
732
|
-
return
|
|
846
|
+
return bottom;
|
|
733
847
|
};
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
Math.ceil(scrollingElement?.scrollHeight || 0)
|
|
740
|
-
);
|
|
741
|
-
};
|
|
742
|
-
candidates.forEach((el) => {
|
|
743
|
-
const style = window.getComputedStyle(el);
|
|
744
|
-
const rect = el.getBoundingClientRect();
|
|
745
|
-
if (visibleOnly && !isVisible(el, style, rect)) {
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
if (!isScrollableY(el, style, rect)) {
|
|
749
|
-
return;
|
|
750
|
-
}
|
|
751
|
-
const neededHeight = measureNeededHeight(el);
|
|
752
|
-
if (neededHeight <= 0) {
|
|
753
|
-
return;
|
|
754
|
-
}
|
|
755
|
-
if (neededHeight > maxHeight) {
|
|
756
|
-
maxHeight = neededHeight;
|
|
757
|
-
}
|
|
758
|
-
scrollableElements.push(el);
|
|
759
|
-
if (isDocumentElement(el)) {
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
expandClippingAncestors(el);
|
|
763
|
-
if (forceScrollableHeight) {
|
|
764
|
-
expandElementToScrollHeight(el);
|
|
765
|
-
return;
|
|
766
|
-
}
|
|
767
|
-
rememberOriginalBoxStyles(el);
|
|
768
|
-
el.style.overflow = "visible";
|
|
769
|
-
el.style.height = "auto";
|
|
770
|
-
el.style.maxHeight = "none";
|
|
848
|
+
targetHeight = Math.max(targetHeight, measureDocumentHeight(), measureExpandedBottom());
|
|
849
|
+
targetHeight = Math.min(Math.max(1, Math.ceil(targetHeight)), Math.max(1, Math.ceil(maxHeight || targetHeight)));
|
|
850
|
+
[root, body, scrollingElement].forEach((el) => {
|
|
851
|
+
if (!el) return;
|
|
852
|
+
setExpandedBox(el, { height: targetHeight, width: 0 });
|
|
771
853
|
});
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
854
|
+
window.scrollTo(0, 0);
|
|
855
|
+
document.querySelectorAll(`[${expandedAttr}="1"]`).forEach((el) => {
|
|
856
|
+
if (!isDocumentElement(el)) {
|
|
857
|
+
el.scrollTop = 0;
|
|
858
|
+
el.scrollLeft = 0;
|
|
777
859
|
}
|
|
778
860
|
});
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
}
|
|
786
|
-
return Math.ceil(maxHeight);
|
|
861
|
+
window.dispatchEvent(new Event("scroll"));
|
|
862
|
+
await delayMs(preloadSettleMs);
|
|
863
|
+
return {
|
|
864
|
+
height: Math.max(targetHeight, measureDocumentHeight(), measureExpandedBottom()),
|
|
865
|
+
scrollableCount,
|
|
866
|
+
expandedCount
|
|
867
|
+
};
|
|
787
868
|
}, {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
869
|
+
expandedAttr: EXPANDED_ATTR,
|
|
870
|
+
maxHeight: toPositiveInteger(options.maxHeight, DEFAULT_MAX_HEIGHT),
|
|
871
|
+
preloadLazyContent: options.preloadLazyContent !== false,
|
|
872
|
+
preloadSettleMs: Math.max(0, Math.min(500, Number(options.preloadSettleMs ?? options.settleMs ?? 120) || 0)),
|
|
873
|
+
visibleOnly: options.visibleOnly !== false
|
|
874
|
+
}).catch((error) => {
|
|
875
|
+
logger.warning(`\u5C55\u5F00\u6EDA\u52A8\u5BB9\u5668\u5931\u8D25: ${error?.message || error}`);
|
|
876
|
+
return { height: 0, scrollableCount: 0, expandedCount: 0 };
|
|
792
877
|
});
|
|
793
878
|
};
|
|
794
|
-
var
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
const bottom = Number.parseFloat(style.bottom);
|
|
825
|
-
const hasTopRule = Number.isFinite(top) && top <= Math.max(160, viewportHeight * 0.25);
|
|
826
|
-
const hasBottomRule = Number.isFinite(bottom) && bottom <= Math.max(160, viewportHeight * 0.25);
|
|
827
|
-
const isNearViewportTop = rect.top <= edgeThreshold;
|
|
828
|
-
const isNearViewportBottom = rect.bottom >= viewportHeight - edgeThreshold;
|
|
829
|
-
const edge = (hasBottomRule || isNearViewportBottom) && !(hasTopRule || isNearViewportTop) ? "bottom" : "top";
|
|
830
|
-
if (position === "fixed" && edge === "top" && !hasTopRule && !isNearViewportTop) return;
|
|
831
|
-
if (position === "fixed" && edge === "bottom" && !hasBottomRule && !isNearViewportBottom) return;
|
|
832
|
-
if (position === "sticky" && !hasTopRule && !hasBottomRule && !isNearViewportTop && !isNearViewportBottom) return;
|
|
833
|
-
candidates.push({ el, position, edge });
|
|
834
|
-
});
|
|
835
|
-
const candidateSet = new Set(candidates.map(({ el }) => el));
|
|
836
|
-
const topLevelCandidates = candidates.filter(({ el }) => {
|
|
837
|
-
for (let parent = el.parentElement; parent; parent = parent.parentElement) {
|
|
838
|
-
if (candidateSet.has(parent)) return false;
|
|
879
|
+
var expandFrameElement = async (frame, height) => {
|
|
880
|
+
const handle = await frame.frameElement?.().catch(() => null);
|
|
881
|
+
if (!handle) return false;
|
|
882
|
+
try {
|
|
883
|
+
return await handle.evaluate((el, { expandedAttr, targetHeight }) => {
|
|
884
|
+
if (!el || targetHeight <= 0) return false;
|
|
885
|
+
if (el.getAttribute(expandedAttr) !== "1") {
|
|
886
|
+
el.setAttribute(expandedAttr, "1");
|
|
887
|
+
el.dataset.pkSsOverflow = el.style.getPropertyValue("overflow") || "";
|
|
888
|
+
el.dataset.pkSsOverflowPriority = el.style.getPropertyPriority("overflow") || "";
|
|
889
|
+
el.dataset.pkSsOverflowX = el.style.getPropertyValue("overflow-x") || "";
|
|
890
|
+
el.dataset.pkSsOverflowXPriority = el.style.getPropertyPriority("overflow-x") || "";
|
|
891
|
+
el.dataset.pkSsOverflowY = el.style.getPropertyValue("overflow-y") || "";
|
|
892
|
+
el.dataset.pkSsOverflowYPriority = el.style.getPropertyPriority("overflow-y") || "";
|
|
893
|
+
el.dataset.pkSsHeight = el.style.getPropertyValue("height") || "";
|
|
894
|
+
el.dataset.pkSsHeightPriority = el.style.getPropertyPriority("height") || "";
|
|
895
|
+
el.dataset.pkSsMinHeight = el.style.getPropertyValue("min-height") || "";
|
|
896
|
+
el.dataset.pkSsMinHeightPriority = el.style.getPropertyPriority("min-height") || "";
|
|
897
|
+
el.dataset.pkSsMaxHeight = el.style.getPropertyValue("max-height") || "";
|
|
898
|
+
el.dataset.pkSsMaxHeightPriority = el.style.getPropertyPriority("max-height") || "";
|
|
899
|
+
el.dataset.pkSsWidth = el.style.getPropertyValue("width") || "";
|
|
900
|
+
el.dataset.pkSsWidthPriority = el.style.getPropertyPriority("width") || "";
|
|
901
|
+
el.dataset.pkSsBoxSizing = el.style.getPropertyValue("box-sizing") || "";
|
|
902
|
+
el.dataset.pkSsBoxSizingPriority = el.style.getPropertyPriority("box-sizing") || "";
|
|
903
|
+
el.dataset.pkSsAlignSelf = el.style.getPropertyValue("align-self") || "";
|
|
904
|
+
el.dataset.pkSsAlignSelfPriority = el.style.getPropertyPriority("align-self") || "";
|
|
905
|
+
el.dataset.pkSsJustifySelf = el.style.getPropertyValue("justify-self") || "";
|
|
906
|
+
el.dataset.pkSsJustifySelfPriority = el.style.getPropertyPriority("justify-self") || "";
|
|
907
|
+
el.dataset.pkSsScrollTop = String(Math.max(0, Math.round(el.scrollTop || 0)));
|
|
908
|
+
el.dataset.pkSsScrollLeft = String(Math.max(0, Math.round(el.scrollLeft || 0)));
|
|
839
909
|
}
|
|
910
|
+
el.style.setProperty("overflow", "visible", "important");
|
|
911
|
+
el.style.setProperty("overflow-y", "visible", "important");
|
|
912
|
+
el.style.setProperty("height", `${Math.ceil(targetHeight)}px`, "important");
|
|
913
|
+
el.style.setProperty("min-height", `${Math.ceil(targetHeight)}px`, "important");
|
|
914
|
+
el.style.setProperty("max-height", "none", "important");
|
|
915
|
+
el.style.setProperty("align-self", "flex-start", "important");
|
|
916
|
+
el.style.setProperty("justify-self", "start", "important");
|
|
840
917
|
return true;
|
|
918
|
+
}, { expandedAttr: EXPANDED_ATTR, targetHeight: Math.ceil(height) });
|
|
919
|
+
} finally {
|
|
920
|
+
await handle.dispose?.().catch(() => {
|
|
841
921
|
});
|
|
842
|
-
|
|
843
|
-
if (!hasOwn2(el.dataset, "pkAffixedAdjusted")) {
|
|
844
|
-
el.dataset.pkAffixedAdjusted = "1";
|
|
845
|
-
el.dataset.pkOrigPosition = el.style.getPropertyValue("position") || "";
|
|
846
|
-
el.dataset.pkOrigPositionPriority = el.style.getPropertyPriority("position") || "";
|
|
847
|
-
el.dataset.pkOrigTop = el.style.getPropertyValue("top") || "";
|
|
848
|
-
el.dataset.pkOrigTopPriority = el.style.getPropertyPriority("top") || "";
|
|
849
|
-
el.dataset.pkOrigBottom = el.style.getPropertyValue("bottom") || "";
|
|
850
|
-
el.dataset.pkOrigBottomPriority = el.style.getPropertyPriority("bottom") || "";
|
|
851
|
-
el.dataset.pkOrigTranslate = el.style.getPropertyValue("translate") || "";
|
|
852
|
-
el.dataset.pkOrigTranslatePriority = el.style.getPropertyPriority("translate") || "";
|
|
853
|
-
}
|
|
854
|
-
el.classList.add(className);
|
|
855
|
-
if (position === "fixed") {
|
|
856
|
-
const deltaY = edge === "bottom" ? safeTargetHeight - viewportHeight - scrollY : -scrollY;
|
|
857
|
-
if (Math.abs(deltaY) > 1) {
|
|
858
|
-
el.style.setProperty("translate", `0 ${Math.round(deltaY)}px`, "important");
|
|
859
|
-
}
|
|
860
|
-
return;
|
|
861
|
-
}
|
|
862
|
-
el.style.setProperty("position", "relative", "important");
|
|
863
|
-
el.style.setProperty("top", "auto", "important");
|
|
864
|
-
el.style.setProperty("bottom", "auto", "important");
|
|
865
|
-
});
|
|
866
|
-
return topLevelCandidates.length;
|
|
867
|
-
}, {
|
|
868
|
-
className: EXPANDED_SCROLLABLE_CLASS,
|
|
869
|
-
targetHeight: options.targetHeight
|
|
870
|
-
});
|
|
922
|
+
}
|
|
871
923
|
};
|
|
872
|
-
var
|
|
873
|
-
await
|
|
924
|
+
var restoreExpandedStateInContext = async (context) => {
|
|
925
|
+
await context.evaluate(({ expandedAttr }) => {
|
|
874
926
|
const hasOwn2 = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
"
|
|
878
|
-
"
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
el.style.setProperty("position", el.dataset.pkOrigPosition || "", el.dataset.pkOrigPositionPriority || "");
|
|
884
|
-
delete el.dataset.pkOrigPosition;
|
|
885
|
-
delete el.dataset.pkOrigPositionPriority;
|
|
886
|
-
}
|
|
887
|
-
if (hasOwn2(el.dataset, "pkOrigTop")) {
|
|
888
|
-
el.style.setProperty("top", el.dataset.pkOrigTop || "", el.dataset.pkOrigTopPriority || "");
|
|
889
|
-
delete el.dataset.pkOrigTop;
|
|
890
|
-
delete el.dataset.pkOrigTopPriority;
|
|
891
|
-
}
|
|
892
|
-
if (hasOwn2(el.dataset, "pkOrigBottom")) {
|
|
893
|
-
el.style.setProperty("bottom", el.dataset.pkOrigBottom || "", el.dataset.pkOrigBottomPriority || "");
|
|
894
|
-
delete el.dataset.pkOrigBottom;
|
|
895
|
-
delete el.dataset.pkOrigBottomPriority;
|
|
896
|
-
}
|
|
897
|
-
if (hasOwn2(el.dataset, "pkOrigTranslate")) {
|
|
898
|
-
el.style.setProperty("translate", el.dataset.pkOrigTranslate || "", el.dataset.pkOrigTranslatePriority || "");
|
|
899
|
-
delete el.dataset.pkOrigTranslate;
|
|
900
|
-
delete el.dataset.pkOrigTranslatePriority;
|
|
901
|
-
}
|
|
902
|
-
delete el.dataset.pkAffixedAdjusted;
|
|
903
|
-
const stillExpanded = expansionKeys.some((key) => hasOwn2(el.dataset, key));
|
|
904
|
-
if (!stillExpanded) {
|
|
905
|
-
el.classList.remove(className);
|
|
927
|
+
const restoreStyle = (el, prop, valueKey, priorityKey) => {
|
|
928
|
+
if (!hasOwn2(el.dataset, valueKey)) return;
|
|
929
|
+
const value = el.dataset[valueKey] || "";
|
|
930
|
+
const priority = el.dataset[priorityKey] || "";
|
|
931
|
+
if (value) {
|
|
932
|
+
el.style.setProperty(prop, value, priority);
|
|
933
|
+
} else {
|
|
934
|
+
el.style.removeProperty(prop);
|
|
906
935
|
}
|
|
936
|
+
delete el.dataset[valueKey];
|
|
937
|
+
delete el.dataset[priorityKey];
|
|
938
|
+
};
|
|
939
|
+
document.querySelectorAll(`[${expandedAttr}="1"]`).forEach((el) => {
|
|
940
|
+
restoreStyle(el, "overflow", "pkSsOverflow", "pkSsOverflowPriority");
|
|
941
|
+
restoreStyle(el, "overflow-x", "pkSsOverflowX", "pkSsOverflowXPriority");
|
|
942
|
+
restoreStyle(el, "overflow-y", "pkSsOverflowY", "pkSsOverflowYPriority");
|
|
943
|
+
restoreStyle(el, "height", "pkSsHeight", "pkSsHeightPriority");
|
|
944
|
+
restoreStyle(el, "min-height", "pkSsMinHeight", "pkSsMinHeightPriority");
|
|
945
|
+
restoreStyle(el, "max-height", "pkSsMaxHeight", "pkSsMaxHeightPriority");
|
|
946
|
+
restoreStyle(el, "width", "pkSsWidth", "pkSsWidthPriority");
|
|
947
|
+
restoreStyle(el, "box-sizing", "pkSsBoxSizing", "pkSsBoxSizingPriority");
|
|
948
|
+
restoreStyle(el, "align-self", "pkSsAlignSelf", "pkSsAlignSelfPriority");
|
|
949
|
+
restoreStyle(el, "justify-self", "pkSsJustifySelf", "pkSsJustifySelfPriority");
|
|
950
|
+
if (hasOwn2(el.dataset, "pkSsScrollTop")) {
|
|
951
|
+
el.scrollTop = Math.max(0, Math.round(Number(el.dataset.pkSsScrollTop) || 0));
|
|
952
|
+
delete el.dataset.pkSsScrollTop;
|
|
953
|
+
}
|
|
954
|
+
if (hasOwn2(el.dataset, "pkSsScrollLeft")) {
|
|
955
|
+
el.scrollLeft = Math.max(0, Math.round(Number(el.dataset.pkSsScrollLeft) || 0));
|
|
956
|
+
delete el.dataset.pkSsScrollLeft;
|
|
957
|
+
}
|
|
958
|
+
el.removeAttribute(expandedAttr);
|
|
907
959
|
});
|
|
908
|
-
|
|
960
|
+
window.dispatchEvent(new Event("scroll"));
|
|
961
|
+
}, {
|
|
962
|
+
expandedAttr: EXPANDED_ATTR
|
|
963
|
+
}).catch(() => {
|
|
964
|
+
});
|
|
909
965
|
};
|
|
910
|
-
var
|
|
911
|
-
|
|
966
|
+
var resetScrollPositionInContext = async (context) => {
|
|
967
|
+
await context.evaluate(() => {
|
|
968
|
+
const root = document.documentElement;
|
|
912
969
|
const body = document.body;
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
top: Number.POSITIVE_INFINITY,
|
|
921
|
-
right: 0,
|
|
922
|
-
bottom: 0,
|
|
923
|
-
nodes: 0
|
|
924
|
-
};
|
|
925
|
-
const isVisible = (el, style, rect) => {
|
|
926
|
-
if (!el || !style || !rect) return false;
|
|
927
|
-
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") return false;
|
|
928
|
-
if (Number(style.opacity) === 0) return false;
|
|
929
|
-
return rect.width > 1 && rect.height > 1;
|
|
930
|
-
};
|
|
931
|
-
const hasFixedAncestor = (el) => {
|
|
932
|
-
for (let node = el; node && node.nodeType === 1; node = node.parentElement) {
|
|
933
|
-
const position = String(window.getComputedStyle(node).position || "").toLowerCase();
|
|
934
|
-
if (position === "fixed") {
|
|
935
|
-
return true;
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
return false;
|
|
939
|
-
};
|
|
940
|
-
const parseRgbColor = (value) => {
|
|
941
|
-
const raw = String(value || "").trim();
|
|
942
|
-
const match = raw.match(/rgba?\(([^)]+)\)/i);
|
|
943
|
-
if (!match) return null;
|
|
944
|
-
const parts = match[1].replace(/\//g, " ").split(/[,\s]+/).map((part) => part.trim()).filter(Boolean);
|
|
945
|
-
if (parts.length < 3) return null;
|
|
946
|
-
const normalizeChannel = (part) => {
|
|
947
|
-
if (part.endsWith("%")) {
|
|
948
|
-
return Math.max(0, Math.min(255, Number.parseFloat(part) * 2.55));
|
|
949
|
-
}
|
|
950
|
-
return Math.max(0, Math.min(255, Number.parseFloat(part)));
|
|
951
|
-
};
|
|
952
|
-
const alpha = parts.length >= 4 ? parts[3].endsWith("%") ? Number.parseFloat(parts[3]) / 100 : Number.parseFloat(parts[3]) : 1;
|
|
953
|
-
return {
|
|
954
|
-
r: normalizeChannel(parts[0]),
|
|
955
|
-
g: normalizeChannel(parts[1]),
|
|
956
|
-
b: normalizeChannel(parts[2]),
|
|
957
|
-
a: Number.isFinite(alpha) ? Math.max(0, Math.min(1, alpha)) : 1
|
|
958
|
-
};
|
|
959
|
-
};
|
|
960
|
-
const colorLuminance = (color) => {
|
|
961
|
-
const channel = (value) => {
|
|
962
|
-
const ratio = Math.max(0, Math.min(255, value)) / 255;
|
|
963
|
-
return ratio <= 0.03928 ? ratio / 12.92 : ((ratio + 0.055) / 1.055) ** 2.4;
|
|
964
|
-
};
|
|
965
|
-
return 0.2126 * channel(color.r) + 0.7152 * channel(color.g) + 0.0722 * channel(color.b);
|
|
966
|
-
};
|
|
967
|
-
const contrastRatio = (a, b) => {
|
|
968
|
-
const l1 = colorLuminance(a);
|
|
969
|
-
const l2 = colorLuminance(b);
|
|
970
|
-
const lighter = Math.max(l1, l2);
|
|
971
|
-
const darker = Math.min(l1, l2);
|
|
972
|
-
return (lighter + 0.05) / (darker + 0.05);
|
|
973
|
-
};
|
|
974
|
-
const resolveBackgroundColor = (el) => {
|
|
975
|
-
for (let node = el; node && node.nodeType === 1; node = node.parentElement) {
|
|
976
|
-
const background = parseRgbColor(window.getComputedStyle(node).backgroundColor);
|
|
977
|
-
if (background && background.a > 0.2) {
|
|
978
|
-
return background;
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
return { r: 255, g: 255, b: 255, a: 1 };
|
|
982
|
-
};
|
|
983
|
-
const isLowInformationTextPaint = (el, style) => {
|
|
984
|
-
const color = parseRgbColor(style?.color);
|
|
985
|
-
if (!color) return false;
|
|
986
|
-
const opacity = Number(style.opacity);
|
|
987
|
-
if (color.a <= 0.08 || Number.isFinite(opacity) && opacity <= 0.18) {
|
|
988
|
-
return true;
|
|
989
|
-
}
|
|
990
|
-
const max = Math.max(color.r, color.g, color.b);
|
|
991
|
-
const min = Math.min(color.r, color.g, color.b);
|
|
992
|
-
if (!(min >= 224 && max - min <= 24 && color.a >= 0.85)) {
|
|
993
|
-
return false;
|
|
994
|
-
}
|
|
995
|
-
const background = resolveBackgroundColor(el);
|
|
996
|
-
return colorLuminance(background) > 0.76 && contrastRatio(color, background) < 1.35;
|
|
997
|
-
};
|
|
998
|
-
const addRect = (rect) => {
|
|
999
|
-
if (!rect || rect.width <= 1 || rect.height <= 1) return;
|
|
1000
|
-
const left = Math.max(0, Math.floor(rect.left + scrollX));
|
|
1001
|
-
const top = Math.max(0, Math.floor(rect.top + scrollY));
|
|
1002
|
-
const right = Math.ceil(rect.right + scrollX);
|
|
1003
|
-
const bottom = Math.ceil(rect.bottom + scrollY);
|
|
1004
|
-
if (right <= left || bottom <= top) return;
|
|
1005
|
-
bounds.left = Math.min(bounds.left, left);
|
|
1006
|
-
bounds.top = Math.min(bounds.top, top);
|
|
1007
|
-
bounds.right = Math.max(bounds.right, right);
|
|
1008
|
-
bounds.bottom = Math.max(bounds.bottom, bottom);
|
|
1009
|
-
bounds.nodes += 1;
|
|
1010
|
-
};
|
|
1011
|
-
const addElement = (el, { minArea = 12, text = false } = {}) => {
|
|
1012
|
-
if (!el || el.nodeType !== 1 || hasFixedAncestor(el)) return;
|
|
1013
|
-
const style = window.getComputedStyle(el);
|
|
1014
|
-
if (text && isLowInformationTextPaint(el, style)) return;
|
|
1015
|
-
const rects = Array.from(el.getClientRects ? el.getClientRects() : []);
|
|
1016
|
-
rects.forEach((rect) => {
|
|
1017
|
-
if (!isVisible(el, style, rect)) return;
|
|
1018
|
-
if (rect.width * rect.height < minArea) return;
|
|
1019
|
-
addRect(rect);
|
|
1020
|
-
});
|
|
1021
|
-
};
|
|
1022
|
-
const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT);
|
|
1023
|
-
let visitedTextNodes = 0;
|
|
1024
|
-
while (walker.nextNode() && visitedTextNodes < 6e3) {
|
|
1025
|
-
const node = walker.currentNode;
|
|
1026
|
-
visitedTextNodes += 1;
|
|
1027
|
-
const text = String(node.nodeValue || "").replace(/\s+/g, " ").trim();
|
|
1028
|
-
if (text.length < 2) continue;
|
|
1029
|
-
addElement(node.parentElement, { minArea: 8, text: true });
|
|
1030
|
-
}
|
|
1031
|
-
document.querySelectorAll("img,video,canvas,svg,picture").forEach((el) => {
|
|
1032
|
-
addElement(el, { minArea: 900 });
|
|
970
|
+
const scrollingElement = document.scrollingElement || root || body;
|
|
971
|
+
window.scrollTo(0, 0);
|
|
972
|
+
[root, body, scrollingElement].forEach((el) => {
|
|
973
|
+
if (!el) return;
|
|
974
|
+
el.scrollTop = 0;
|
|
975
|
+
el.scrollLeft = 0;
|
|
976
|
+
el.dispatchEvent(new Event("scroll", { bubbles: true }));
|
|
1033
977
|
});
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
const paddingY = Math.max(24, Math.min(160, viewportHeight * 0.18));
|
|
1037
|
-
return {
|
|
1038
|
-
left: Math.max(0, Math.floor(bounds.left - paddingX)),
|
|
1039
|
-
top: Math.max(0, Math.floor(bounds.top - paddingY)),
|
|
1040
|
-
right: Math.ceil(bounds.right + paddingX),
|
|
1041
|
-
bottom: Math.ceil(bounds.bottom + paddingY),
|
|
1042
|
-
width: Math.max(1, Math.ceil(bounds.right - bounds.left)),
|
|
1043
|
-
height: Math.max(1, Math.ceil(bounds.bottom - bounds.top)),
|
|
1044
|
-
nodes: bounds.nodes,
|
|
1045
|
-
viewport: {
|
|
1046
|
-
width: Math.max(1, Math.ceil(viewportWidth || 1)),
|
|
1047
|
-
height: Math.max(1, Math.ceil(viewportHeight || 1))
|
|
1048
|
-
}
|
|
1049
|
-
};
|
|
1050
|
-
}).catch((error) => {
|
|
1051
|
-
logger.warning(`\u622A\u56FE\u5185\u5BB9\u8FB9\u754C\u6D4B\u91CF\u5931\u8D25: ${error?.message || error}`);
|
|
1052
|
-
return null;
|
|
978
|
+
window.dispatchEvent(new Event("scroll"));
|
|
979
|
+
}).catch(() => {
|
|
1053
980
|
});
|
|
1054
981
|
};
|
|
1055
982
|
var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
1056
|
-
const originalViewport = await resolveCurrentViewportSize(page);
|
|
1057
983
|
const maxHeight = toPositiveInteger(options.maxHeight, DEFAULT_MAX_HEIGHT);
|
|
1058
|
-
const
|
|
1059
|
-
const
|
|
1060
|
-
const
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
if (contentBounds?.bottom > 0) {
|
|
1071
|
-
const contentBottom = Math.min(Math.ceil(contentBounds.bottom), maxHeight);
|
|
1072
|
-
const trailingGap = targetHeight - contentBottom;
|
|
1073
|
-
const minTrailingGap = Math.max(
|
|
1074
|
-
MIN_TRAILING_BLANK_GAP_PX,
|
|
1075
|
-
Math.floor(targetHeight * MIN_TRAILING_BLANK_GAP_RATIO),
|
|
1076
|
-
Math.floor(originalViewport.height * 0.45)
|
|
1077
|
-
);
|
|
1078
|
-
if (trailingGap >= minTrailingGap && contentBottom >= originalViewport.height * 0.65) {
|
|
1079
|
-
targetHeight = Math.max(1, contentBottom);
|
|
1080
|
-
logger.info(`\u957F\u622A\u56FE\u88C1\u6389\u4F4E\u4FE1\u606F\u5C3E\u90E8: contentBottom=${contentBottom}, originalHeight=${Math.min(maxScrollHeight, maxHeight)}`);
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
let viewportResized = false;
|
|
1084
|
-
if (!preserveViewport) {
|
|
1085
|
-
await page.setViewportSize({
|
|
1086
|
-
width: originalViewport.width,
|
|
1087
|
-
height: targetHeight
|
|
984
|
+
const settleMs = Math.max(0, Number(options.settleMs ?? DEFAULT_SETTLE_MS) || 0);
|
|
985
|
+
const frames = typeof page.frames === "function" ? page.frames() : [page.mainFrame?.()].filter(Boolean);
|
|
986
|
+
const mainFrame = page.mainFrame?.() || frames[0] || null;
|
|
987
|
+
let targetHeight = 0;
|
|
988
|
+
let scrollableCount = 0;
|
|
989
|
+
let expandedCount = 0;
|
|
990
|
+
for (const frame of frames) {
|
|
991
|
+
if (!frame || frame === mainFrame) continue;
|
|
992
|
+
const state2 = await expandScrollableContentInContext(frame, {
|
|
993
|
+
...options,
|
|
994
|
+
maxHeight,
|
|
995
|
+
settleMs
|
|
1088
996
|
});
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
997
|
+
if (state2.height > 0) {
|
|
998
|
+
await expandFrameElement(frame, Math.min(state2.height, maxHeight)).catch(() => false);
|
|
999
|
+
await resetScrollPositionInContext(frame);
|
|
1000
|
+
}
|
|
1001
|
+
targetHeight = Math.max(targetHeight, state2.height || 0);
|
|
1002
|
+
scrollableCount += state2.scrollableCount || 0;
|
|
1003
|
+
expandedCount += state2.expandedCount || 0;
|
|
1004
|
+
}
|
|
1005
|
+
if (mainFrame) {
|
|
1006
|
+
const state2 = await expandScrollableContentInContext(mainFrame, {
|
|
1007
|
+
...options,
|
|
1008
|
+
maxHeight,
|
|
1009
|
+
settleMs
|
|
1093
1010
|
});
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1011
|
+
targetHeight = Math.max(targetHeight, state2.height || 0);
|
|
1012
|
+
scrollableCount += state2.scrollableCount || 0;
|
|
1013
|
+
expandedCount += state2.expandedCount || 0;
|
|
1014
|
+
await resetScrollPositionInContext(mainFrame);
|
|
1015
|
+
}
|
|
1016
|
+
targetHeight = Math.min(Math.max(1, Math.ceil(targetHeight || 0)), maxHeight);
|
|
1017
|
+
if (settleMs > 0) await delay(settleMs);
|
|
1018
|
+
logger.info(`\u5C55\u5F00\u5F0F\u957F\u622A\u56FE\u76EE\u6807: height=${targetHeight}, scrollables=${scrollableCount}, expanded=${expandedCount}`);
|
|
1098
1019
|
return {
|
|
1099
|
-
originalViewport,
|
|
1100
|
-
maxScrollHeight,
|
|
1101
1020
|
targetHeight,
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
viewportResized
|
|
1021
|
+
frames,
|
|
1022
|
+
settleMs
|
|
1105
1023
|
};
|
|
1106
1024
|
};
|
|
1107
1025
|
var restoreExpandedFullPageScreenshot = async (page, state2 = {}) => {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
document.documentElement,
|
|
1112
|
-
document.body,
|
|
1113
|
-
document.scrollingElement
|
|
1114
|
-
].filter((el) => el?.classList?.contains(className)));
|
|
1115
|
-
targets.forEach((el) => {
|
|
1116
|
-
el.style.setProperty("overflow", el.dataset.pkOrigOverflow || "", el.dataset.pkOrigOverflowPriority || "");
|
|
1117
|
-
el.style.setProperty("height", el.dataset.pkOrigHeight || "", el.dataset.pkOrigHeightPriority || "");
|
|
1118
|
-
el.style.setProperty("min-height", el.dataset.pkOrigMinHeight || "", el.dataset.pkOrigMinHeightPriority || "");
|
|
1119
|
-
el.style.setProperty("max-height", el.dataset.pkOrigMaxHeight || "", el.dataset.pkOrigMaxHeightPriority || "");
|
|
1120
|
-
delete el.dataset.pkOrigOverflow;
|
|
1121
|
-
delete el.dataset.pkOrigOverflowPriority;
|
|
1122
|
-
delete el.dataset.pkOrigHeight;
|
|
1123
|
-
delete el.dataset.pkOrigHeightPriority;
|
|
1124
|
-
delete el.dataset.pkOrigMinHeight;
|
|
1125
|
-
delete el.dataset.pkOrigMinHeightPriority;
|
|
1126
|
-
delete el.dataset.pkOrigMaxHeight;
|
|
1127
|
-
delete el.dataset.pkOrigMaxHeightPriority;
|
|
1128
|
-
el.classList.remove(className);
|
|
1129
|
-
});
|
|
1130
|
-
}, EXPANDED_SCROLLABLE_CLASS);
|
|
1131
|
-
if (state2?.originalViewport && state2?.viewportResized) {
|
|
1132
|
-
await page.setViewportSize(state2.originalViewport);
|
|
1026
|
+
const frames = Array.isArray(state2.frames) ? [...state2.frames].reverse() : typeof page.frames === "function" ? [...page.frames()].reverse() : [];
|
|
1027
|
+
for (const frame of frames) {
|
|
1028
|
+
await restoreExpandedStateInContext(frame);
|
|
1133
1029
|
}
|
|
1134
1030
|
};
|
|
1135
|
-
var
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
return null;
|
|
1141
|
-
}
|
|
1142
|
-
return await page.evaluate((config) => {
|
|
1143
|
-
const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || document.body?.clientWidth || 0;
|
|
1144
|
-
const viewportHeight = window.innerHeight || document.documentElement?.clientHeight || document.body?.clientHeight || 0;
|
|
1145
|
-
const root = document.documentElement;
|
|
1146
|
-
const body = document.body;
|
|
1147
|
-
const scrollingElement = document.scrollingElement;
|
|
1148
|
-
const candidates = [];
|
|
1149
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1150
|
-
const scrollableOverflow = /* @__PURE__ */ new Set(["auto", "scroll", "overlay"]);
|
|
1151
|
-
const clippingOverflow = /* @__PURE__ */ new Set(["hidden", "clip"]);
|
|
1152
|
-
const pushCandidate = (el2) => {
|
|
1153
|
-
if (!el2 || seen.has(el2)) return;
|
|
1154
|
-
seen.add(el2);
|
|
1155
|
-
candidates.push(el2);
|
|
1156
|
-
};
|
|
1157
|
-
pushCandidate(scrollingElement);
|
|
1158
|
-
pushCandidate(root);
|
|
1159
|
-
pushCandidate(body);
|
|
1160
|
-
document.querySelectorAll("*").forEach(pushCandidate);
|
|
1161
|
-
const isDocumentElement = (el2) => el2 === root || el2 === body || el2 === scrollingElement;
|
|
1162
|
-
const isVisible = (el2, style, rect) => {
|
|
1163
|
-
if (!el2 || !style || !rect) return false;
|
|
1164
|
-
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") return false;
|
|
1165
|
-
if (Number(style.opacity) === 0) return false;
|
|
1166
|
-
return rect.width > 0 && rect.height > 0;
|
|
1167
|
-
};
|
|
1168
|
-
const hasOverflowValue = (style, values) => {
|
|
1169
|
-
const overflowY = String(style?.overflowY || "").toLowerCase();
|
|
1170
|
-
const overflow = String(style?.overflow || "").toLowerCase();
|
|
1171
|
-
return values.has(overflowY) || values.has(overflow);
|
|
1172
|
-
};
|
|
1173
|
-
const looksScrollable = (el2, style) => {
|
|
1174
|
-
if (!el2 || el2.scrollHeight <= el2.clientHeight + 1) return false;
|
|
1175
|
-
if (isDocumentElement(el2)) return true;
|
|
1176
|
-
return hasOverflowValue(style, scrollableOverflow) || hasOverflowValue(style, clippingOverflow);
|
|
1177
|
-
};
|
|
1178
|
-
const textHeightFor = (el2) => {
|
|
1179
|
-
const nodes = isDocumentElement(el2) ? document.querySelectorAll("body *") : el2.querySelectorAll("*");
|
|
1180
|
-
let textHeight = 0;
|
|
1181
|
-
let textNodes = 0;
|
|
1182
|
-
const maxNodes = 3500;
|
|
1183
|
-
for (const node of nodes) {
|
|
1184
|
-
if (textNodes >= maxNodes) break;
|
|
1185
|
-
const text = String(node.textContent || "").replace(/\s+/g, " ").trim();
|
|
1186
|
-
if (text.length < 2) continue;
|
|
1187
|
-
let childHasText = false;
|
|
1188
|
-
for (const child of node.children || []) {
|
|
1189
|
-
if (String(child.textContent || "").replace(/\s+/g, " ").trim().length >= 2) {
|
|
1190
|
-
childHasText = true;
|
|
1191
|
-
break;
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
if (childHasText) continue;
|
|
1195
|
-
const style = window.getComputedStyle(node);
|
|
1196
|
-
const rect = node.getBoundingClientRect();
|
|
1197
|
-
if (!isVisible(node, style, rect)) continue;
|
|
1198
|
-
textNodes += 1;
|
|
1199
|
-
textHeight += Math.min(Math.ceil(rect.height || 0), 900);
|
|
1200
|
-
}
|
|
1201
|
-
return { textHeight, textNodes };
|
|
1202
|
-
};
|
|
1203
|
-
const resolveEdgeOcclusion = () => {
|
|
1204
|
-
let top = 0;
|
|
1205
|
-
let bottom = 0;
|
|
1206
|
-
const maxHeight = Math.max(48, viewportHeight * 0.45);
|
|
1207
|
-
document.querySelectorAll("*").forEach((el2) => {
|
|
1208
|
-
const style = window.getComputedStyle(el2);
|
|
1209
|
-
const position = String(style.position || "").toLowerCase();
|
|
1210
|
-
if (position !== "fixed" && position !== "sticky") return;
|
|
1211
|
-
const rect = el2.getBoundingClientRect();
|
|
1212
|
-
if (!isVisible(el2, style, rect)) return;
|
|
1213
|
-
if (rect.height <= 0 || rect.height > maxHeight) return;
|
|
1214
|
-
if (rect.width < viewportWidth * 0.35) return;
|
|
1215
|
-
if (rect.top <= Math.max(16, viewportHeight * 0.08)) {
|
|
1216
|
-
top = Math.max(top, Math.ceil(rect.bottom));
|
|
1217
|
-
}
|
|
1218
|
-
if (rect.bottom >= viewportHeight - Math.max(16, viewportHeight * 0.08)) {
|
|
1219
|
-
bottom = Math.max(bottom, Math.ceil(viewportHeight - rect.top));
|
|
1220
|
-
}
|
|
1221
|
-
});
|
|
1222
|
-
return {
|
|
1223
|
-
top: Math.max(0, Math.min(Math.ceil(top), Math.floor(viewportHeight * 0.45))),
|
|
1224
|
-
bottom: Math.max(0, Math.min(Math.ceil(bottom), Math.floor(viewportHeight * 0.45)))
|
|
1225
|
-
};
|
|
1226
|
-
};
|
|
1227
|
-
let best = null;
|
|
1228
|
-
candidates.forEach((el2) => {
|
|
1229
|
-
const style = window.getComputedStyle(el2);
|
|
1230
|
-
const rect = isDocumentElement(el2) ? {
|
|
1231
|
-
top: 0,
|
|
1232
|
-
bottom: viewportHeight,
|
|
1233
|
-
left: 0,
|
|
1234
|
-
right: viewportWidth,
|
|
1235
|
-
width: viewportWidth,
|
|
1236
|
-
height: viewportHeight
|
|
1237
|
-
} : el2.getBoundingClientRect();
|
|
1238
|
-
if (!isDocumentElement(el2) && !isVisible(el2, style, rect)) return;
|
|
1239
|
-
if (!looksScrollable(el2, style)) return;
|
|
1240
|
-
const scrollHeight = Math.ceil(el2.scrollHeight || 0);
|
|
1241
|
-
const clientHeight = Math.ceil(el2.clientHeight || viewportHeight || 0);
|
|
1242
|
-
if (scrollHeight < Math.max(900, clientHeight * config.minScrollRatio)) return;
|
|
1243
|
-
const visibleTop = Math.max(0, Math.round(rect.top || 0));
|
|
1244
|
-
const visibleBottom = Math.min(viewportHeight, Math.round(rect.bottom || viewportHeight));
|
|
1245
|
-
const visibleHeight = Math.max(0, visibleBottom - visibleTop);
|
|
1246
|
-
const minVisibleHeight = Math.max(
|
|
1247
|
-
config.minVisibleHeightPx,
|
|
1248
|
-
Math.floor(viewportHeight * config.minVisibleHeightRatio)
|
|
1249
|
-
);
|
|
1250
|
-
if (!config.force && !isDocumentElement(el2) && visibleHeight < minVisibleHeight) return;
|
|
1251
|
-
const { textHeight, textNodes } = textHeightFor(el2);
|
|
1252
|
-
const sparseRatio = scrollHeight / Math.max(1, textHeight);
|
|
1253
|
-
const sparse = config.force || textNodes > 0 && sparseRatio >= config.minSparseRatio && scrollHeight - textHeight > clientHeight;
|
|
1254
|
-
if (!sparse) return;
|
|
1255
|
-
const visibleWidth = Math.max(1, Math.ceil(rect.width || viewportWidth || 1));
|
|
1256
|
-
const score2 = scrollHeight * Math.min(visibleWidth, viewportWidth || visibleWidth) * Math.min(1, visibleHeight / Math.max(1, viewportHeight));
|
|
1257
|
-
if (!best || score2 > best.score) {
|
|
1258
|
-
best = {
|
|
1259
|
-
el: el2,
|
|
1260
|
-
score: score2,
|
|
1261
|
-
kind: isDocumentElement(el2) ? "document" : "element",
|
|
1262
|
-
scrollHeight,
|
|
1263
|
-
clientHeight,
|
|
1264
|
-
scrollTop: Math.max(0, Math.round(el2.scrollTop || 0)),
|
|
1265
|
-
rect: {
|
|
1266
|
-
top: Math.max(0, Math.round(rect.top || 0)),
|
|
1267
|
-
bottom: Math.min(viewportHeight, Math.round(rect.bottom || viewportHeight)),
|
|
1268
|
-
left: Math.max(0, Math.round(rect.left || 0)),
|
|
1269
|
-
width: Math.max(1, Math.round(rect.width || viewportWidth || 1)),
|
|
1270
|
-
height: Math.max(1, Math.round(rect.height || viewportHeight || 1))
|
|
1271
|
-
},
|
|
1272
|
-
visibleHeight,
|
|
1273
|
-
textHeight,
|
|
1274
|
-
textNodes,
|
|
1275
|
-
sparseRatio
|
|
1276
|
-
};
|
|
1277
|
-
}
|
|
1278
|
-
});
|
|
1279
|
-
if (!best) return null;
|
|
1280
|
-
document.querySelectorAll(`[${config.attrName}="1"]`).forEach((node) => {
|
|
1281
|
-
node.removeAttribute(config.attrName);
|
|
1282
|
-
});
|
|
1283
|
-
if (best.kind === "element") {
|
|
1284
|
-
best.el.setAttribute(config.attrName, "1");
|
|
1285
|
-
}
|
|
1286
|
-
const { el, score, ...target } = best;
|
|
1287
|
-
return {
|
|
1288
|
-
...target,
|
|
1289
|
-
viewport: {
|
|
1290
|
-
width: Math.max(1, Math.ceil(viewportWidth || best.rect.width || 1)),
|
|
1291
|
-
height: Math.max(1, Math.ceil(viewportHeight || best.rect.height || 1))
|
|
1292
|
-
},
|
|
1293
|
-
edgeOcclusion: resolveEdgeOcclusion()
|
|
1294
|
-
};
|
|
1295
|
-
}, {
|
|
1296
|
-
attrName: STITCH_SCROLL_TARGET_ATTR,
|
|
1297
|
-
force: stitchMode === "force",
|
|
1298
|
-
minScrollRatio: MIN_VIRTUALIZED_SCROLL_RATIO,
|
|
1299
|
-
minSparseRatio: MIN_SPARSE_SCROLL_RATIO,
|
|
1300
|
-
minVisibleHeightPx: MIN_STITCH_VISIBLE_HEIGHT_PX,
|
|
1301
|
-
minVisibleHeightRatio: MIN_STITCH_VISIBLE_HEIGHT_RATIO
|
|
1302
|
-
}).catch((error) => {
|
|
1303
|
-
logger.warning(`\u865A\u62DF\u6EDA\u52A8\u622A\u56FE\u63A2\u6D4B\u5931\u8D25: ${error?.message || error}`);
|
|
1304
|
-
return null;
|
|
1305
|
-
});
|
|
1306
|
-
};
|
|
1307
|
-
var setStitchScrollTop = async (page, target, scrollTop) => {
|
|
1308
|
-
await page.evaluate(({ attrName, kind, top }) => {
|
|
1309
|
-
const el = kind === "document" ? document.scrollingElement || document.documentElement || document.body : document.querySelector(`[${attrName}="1"]`);
|
|
1310
|
-
if (!el) return;
|
|
1311
|
-
el.scrollTop = Math.max(0, Math.round(Number(top) || 0));
|
|
1312
|
-
el.dispatchEvent(new Event("scroll", { bubbles: true }));
|
|
1313
|
-
window.dispatchEvent(new Event("scroll"));
|
|
1314
|
-
}, {
|
|
1315
|
-
attrName: STITCH_SCROLL_TARGET_ATTR,
|
|
1316
|
-
kind: target.kind,
|
|
1317
|
-
top: scrollTop
|
|
1318
|
-
});
|
|
1319
|
-
};
|
|
1320
|
-
var cleanupStitchTarget = async (page) => {
|
|
1321
|
-
await page.evaluate((attrName) => {
|
|
1322
|
-
document.querySelectorAll(`[${attrName}="1"]`).forEach((node) => {
|
|
1323
|
-
node.removeAttribute(attrName);
|
|
1324
|
-
});
|
|
1325
|
-
}, STITCH_SCROLL_TARGET_ATTR).catch(() => {
|
|
1326
|
-
});
|
|
1327
|
-
};
|
|
1328
|
-
var cropImage = (image, crop) => image.clone().crop({
|
|
1329
|
-
x: Math.max(0, Math.round(crop.x || 0)),
|
|
1330
|
-
y: Math.max(0, Math.round(crop.y || 0)),
|
|
1331
|
-
w: Math.max(1, Math.round(crop.w || 1)),
|
|
1332
|
-
h: Math.max(1, Math.round(crop.h || 1))
|
|
1333
|
-
});
|
|
1334
|
-
var captureStitchedScrollableScreenshot = async (page, target, options = {}) => {
|
|
1335
|
-
const maxHeight = toPositiveInteger(options.maxHeight, DEFAULT_MAX_HEIGHT);
|
|
1336
|
-
const settleMs = Math.max(0, Number(options.stitchSettleMs ?? DEFAULT_STITCH_SETTLE_MS) || 0);
|
|
1337
|
-
const overlapPx = Math.max(0, Math.round(Number(options.stitchOverlapPx ?? DEFAULT_STITCH_OVERLAP_PX) || 0));
|
|
1338
|
-
const viewport = target.viewport || await resolveCurrentViewportSize(page);
|
|
1339
|
-
const rect = target.rect || {
|
|
1340
|
-
top: 0,
|
|
1341
|
-
bottom: viewport.height,
|
|
1342
|
-
left: 0,
|
|
1343
|
-
width: viewport.width,
|
|
1344
|
-
height: viewport.height
|
|
1345
|
-
};
|
|
1346
|
-
const edge = target.edgeOcclusion || { top: 0, bottom: 0 };
|
|
1347
|
-
const topCrop = Math.max(0, Math.min(rect.top, viewport.height - 1));
|
|
1348
|
-
const topOverlay = Math.max(topCrop, Math.min(edge.top || 0, viewport.height - 1));
|
|
1349
|
-
const bottomOverlay = Math.max(0, Math.min(edge.bottom || 0, viewport.height - topOverlay - 1));
|
|
1350
|
-
const middleCropY = Math.max(topCrop, topOverlay);
|
|
1351
|
-
const middleCropBottom = Math.max(
|
|
1352
|
-
middleCropY + 1,
|
|
1353
|
-
Math.min(rect.bottom || viewport.height, viewport.height - bottomOverlay)
|
|
1354
|
-
);
|
|
1355
|
-
const middleCropHeight = Math.max(1, middleCropBottom - middleCropY);
|
|
1356
|
-
const scrollStep = Math.max(120, middleCropHeight - overlapPx);
|
|
1357
|
-
const maxScrollTop = Math.max(0, Math.ceil(target.scrollHeight - target.clientHeight));
|
|
1358
|
-
const positions = [];
|
|
1359
|
-
for (let top = 0; top < maxScrollTop; top += scrollStep) {
|
|
1360
|
-
positions.push(Math.round(top));
|
|
1361
|
-
}
|
|
1362
|
-
if (!positions.includes(maxScrollTop)) {
|
|
1363
|
-
positions.push(maxScrollTop);
|
|
1364
|
-
}
|
|
1365
|
-
if (positions.length === 0) {
|
|
1366
|
-
positions.push(0);
|
|
1367
|
-
}
|
|
1368
|
-
const segments = [];
|
|
1369
|
-
let totalHeight = 0;
|
|
1370
|
-
let outputWidth = Math.max(1, Math.round(viewport.width || rect.width || 1));
|
|
1031
|
+
var captureExpandedFullPageScreenshotOnce = async (page, options = {}) => {
|
|
1032
|
+
const originalViewport = await resolveCurrentViewportSize(page).catch(() => null);
|
|
1033
|
+
const originalMainScroll = await readMainFrameScroll(page);
|
|
1034
|
+
let state2 = null;
|
|
1035
|
+
let viewportResized = false;
|
|
1371
1036
|
try {
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
const buffer = await capturePageScreenshot(page, {
|
|
1380
|
-
type: options.type || "png",
|
|
1381
|
-
quality: options.quality,
|
|
1382
|
-
timeout: options.timeout
|
|
1037
|
+
await freezeViewportMetrics(page);
|
|
1038
|
+
state2 = await prepareExpandedFullPageScreenshot(page, options);
|
|
1039
|
+
const targetHeight = Math.max(1, Math.ceil(state2.targetHeight || options.maxHeight || DEFAULT_MAX_HEIGHT));
|
|
1040
|
+
if (originalViewport?.width && originalViewport?.height && targetHeight > Math.ceil(originalViewport.height) + 1) {
|
|
1041
|
+
await page.setViewportSize({
|
|
1042
|
+
width: Math.ceil(originalViewport.width),
|
|
1043
|
+
height: targetHeight
|
|
1383
1044
|
});
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
const cropY = isFirst ? 0 : middleCropY;
|
|
1387
|
-
const cropBottom = isLast ? image.bitmap.height : middleCropBottom;
|
|
1388
|
-
let cropHeight = Math.max(1, Math.min(image.bitmap.height, cropBottom) - cropY);
|
|
1389
|
-
const remaining = maxHeight - totalHeight;
|
|
1390
|
-
if (cropHeight > remaining) {
|
|
1391
|
-
cropHeight = remaining;
|
|
1392
|
-
}
|
|
1393
|
-
segments.push(cropImage(image, {
|
|
1394
|
-
x: 0,
|
|
1395
|
-
y: cropY,
|
|
1396
|
-
w: outputWidth,
|
|
1397
|
-
h: cropHeight
|
|
1398
|
-
}));
|
|
1399
|
-
totalHeight += cropHeight;
|
|
1045
|
+
viewportResized = true;
|
|
1046
|
+
if (state2.settleMs > 0) await delay(state2.settleMs);
|
|
1400
1047
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1048
|
+
return await capturePageScreenshot(page, {
|
|
1049
|
+
...options,
|
|
1050
|
+
fullPage: true,
|
|
1051
|
+
maxClipHeight: targetHeight
|
|
1405
1052
|
});
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1053
|
+
} finally {
|
|
1054
|
+
if (options.restore) {
|
|
1055
|
+
await restoreExpandedFullPageScreenshot(page, state2);
|
|
1056
|
+
if (viewportResized && originalViewport?.width && originalViewport?.height) {
|
|
1057
|
+
await page.setViewportSize({
|
|
1058
|
+
width: Math.ceil(originalViewport.width),
|
|
1059
|
+
height: Math.ceil(originalViewport.height)
|
|
1060
|
+
}).catch((error) => {
|
|
1061
|
+
logger.warning(`\u6062\u590D viewport \u5931\u8D25: ${error?.message || error}`);
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1410
1064
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
const expectedHeight = Math.min(maxHeight, Math.max(target.scrollHeight || 0, viewport.height || 0));
|
|
1415
|
-
const minPlausibleHeight = Math.max(
|
|
1416
|
-
Math.floor((viewport.height || rect.height || 0) * MIN_STITCH_OUTPUT_VIEWPORT_RATIO),
|
|
1417
|
-
Math.floor(expectedHeight * MIN_STITCH_OUTPUT_HEIGHT_RATIO)
|
|
1418
|
-
);
|
|
1419
|
-
if (positions.length > 1 && expectedHeight > (viewport.height || rect.height || 0) * 1.3 && totalHeight < minPlausibleHeight) {
|
|
1420
|
-
logger.warning(
|
|
1421
|
-
`\u865A\u62DF\u6EDA\u52A8\u5206\u6BB5\u622A\u56FE\u7ED3\u679C\u8FC7\u77ED\uFF0C\u964D\u7EA7\u666E\u901A\u957F\u622A\u56FE: segments=${segments.length}, height=${totalHeight}, expectedMin=${minPlausibleHeight}, scrollHeight=${target.scrollHeight}, rect=${rect.width}x${rect.height}@${rect.top}`
|
|
1422
|
-
);
|
|
1423
|
-
return null;
|
|
1065
|
+
await restoreViewportMetrics(page);
|
|
1066
|
+
if (options.restore) {
|
|
1067
|
+
await restoreMainFrameScroll(page, originalMainScroll);
|
|
1424
1068
|
}
|
|
1425
|
-
return await canvas.getBuffer(JimpMime.png);
|
|
1426
|
-
} finally {
|
|
1427
|
-
await setStitchScrollTop(page, target, target.scrollTop || 0).catch(() => {
|
|
1428
|
-
});
|
|
1429
|
-
await cleanupStitchTarget(page);
|
|
1430
1069
|
}
|
|
1431
1070
|
};
|
|
1432
|
-
var isLightBlankPixel = ({ r, g, b }) => {
|
|
1433
|
-
const max = Math.max(r, g, b);
|
|
1434
|
-
const min = Math.min(r, g, b);
|
|
1435
|
-
return max >= 238 && max - min <= 18;
|
|
1436
|
-
};
|
|
1437
1071
|
var isDarkBlankPixel = ({ r, g, b }) => {
|
|
1438
1072
|
const max = Math.max(r, g, b);
|
|
1439
1073
|
const min = Math.min(r, g, b);
|
|
1440
1074
|
return max <= 34 && max - min <= 10;
|
|
1441
1075
|
};
|
|
1442
|
-
var isLowInfoPixel = ({ r, g, b }) => {
|
|
1443
|
-
const max = Math.max(r, g, b);
|
|
1444
|
-
const min = Math.min(r, g, b);
|
|
1445
|
-
return max - min <= 12;
|
|
1446
|
-
};
|
|
1447
1076
|
var analyzeScreenshotBuffer = async (buffer) => {
|
|
1448
1077
|
const image = await Jimp.read(buffer);
|
|
1449
|
-
const width = image.bitmap
|
|
1450
|
-
const height = image.bitmap.height;
|
|
1078
|
+
const { width, height } = image.bitmap;
|
|
1451
1079
|
const stepY = Math.max(1, Math.floor(height / 900));
|
|
1452
1080
|
const stepX = Math.max(1, Math.floor(width / 420));
|
|
1453
|
-
const rows = [];
|
|
1454
|
-
let lightRows = 0;
|
|
1455
1081
|
let darkRows = 0;
|
|
1456
|
-
let lowInfoRows = 0;
|
|
1457
1082
|
let loadingLikeRows = 0;
|
|
1458
|
-
|
|
1083
|
+
let rows = 0;
|
|
1459
1084
|
for (let y = 0; y < height; y += stepY) {
|
|
1460
|
-
|
|
1085
|
+
rows += 1;
|
|
1461
1086
|
let dark = 0;
|
|
1462
|
-
let lowInfo = 0;
|
|
1463
1087
|
let edge = 0;
|
|
1464
1088
|
let count = 0;
|
|
1465
1089
|
let prev = null;
|
|
1466
1090
|
for (let x = 0; x < width; x += stepX) {
|
|
1467
1091
|
const rgba = intToRGBA(image.getPixelColor(x, y));
|
|
1468
1092
|
count += 1;
|
|
1469
|
-
if (isLightBlankPixel(rgba)) light += 1;
|
|
1470
1093
|
if (isDarkBlankPixel(rgba)) dark += 1;
|
|
1471
|
-
if (isLowInfoPixel(rgba)) lowInfo += 1;
|
|
1472
1094
|
if (prev) {
|
|
1473
1095
|
const diff = Math.abs(rgba.r - prev.r) + Math.abs(rgba.g - prev.g) + Math.abs(rgba.b - prev.b);
|
|
1474
1096
|
if (diff > 45) edge += 1;
|
|
1475
1097
|
}
|
|
1476
1098
|
prev = rgba;
|
|
1477
1099
|
}
|
|
1478
|
-
const
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
};
|
|
1485
|
-
rows.push(stat);
|
|
1486
|
-
const lightBlank = stat.lightRatio >= 0.965 && stat.edgeRatio <= 0.015;
|
|
1487
|
-
const darkBlank = stat.darkRatio >= 0.965 && stat.edgeRatio <= 0.012;
|
|
1488
|
-
const lowInfoBlank = stat.lowInfoRatio >= 0.965 && stat.edgeRatio <= 0.012;
|
|
1489
|
-
const loadingLike = stat.darkRatio >= 0.88 && stat.edgeRatio <= 0.025;
|
|
1490
|
-
if (lightBlank) lightRows += 1;
|
|
1491
|
-
if (darkBlank) darkRows += 1;
|
|
1492
|
-
if (lowInfoBlank) lowInfoRows += 1;
|
|
1493
|
-
if (loadingLike) loadingLikeRows += 1;
|
|
1494
|
-
rowFlags.push({ lightBlank, darkBlank, lowInfoBlank, loadingLike });
|
|
1495
|
-
}
|
|
1496
|
-
const sampledRows = Math.max(1, rows.length);
|
|
1100
|
+
const darkRatio = dark / Math.max(1, count);
|
|
1101
|
+
const edgeRatio = edge / Math.max(1, count - 1);
|
|
1102
|
+
if (darkRatio >= 0.965 && edgeRatio <= 0.012) darkRows += 1;
|
|
1103
|
+
if (darkRatio >= 0.88 && edgeRatio <= 0.025) loadingLikeRows += 1;
|
|
1104
|
+
}
|
|
1105
|
+
const sampledRows = Math.max(1, rows);
|
|
1497
1106
|
return {
|
|
1498
|
-
image,
|
|
1499
1107
|
width,
|
|
1500
1108
|
height,
|
|
1501
|
-
lightBlankRowsRatio: lightRows / sampledRows,
|
|
1502
1109
|
darkBlankRowsRatio: darkRows / sampledRows,
|
|
1503
|
-
|
|
1504
|
-
loadingLikeRowsRatio: loadingLikeRows / sampledRows,
|
|
1505
|
-
rowFlags
|
|
1110
|
+
loadingLikeRowsRatio: loadingLikeRows / sampledRows
|
|
1506
1111
|
};
|
|
1507
1112
|
};
|
|
1508
1113
|
var resolveScreenshotQualityIssue = (analysis) => {
|
|
@@ -1512,109 +1117,6 @@ var resolveScreenshotQualityIssue = (analysis) => {
|
|
|
1512
1117
|
}
|
|
1513
1118
|
return null;
|
|
1514
1119
|
};
|
|
1515
|
-
var cropBufferToContentBounds = async (buffer, bounds, options = {}) => {
|
|
1516
|
-
if (!bounds || bounds.nodes <= 0) return buffer;
|
|
1517
|
-
const image = options.image || await Jimp.read(buffer);
|
|
1518
|
-
const width = image.bitmap.width;
|
|
1519
|
-
const height = image.bitmap.height;
|
|
1520
|
-
const safeLeft = Math.max(0, Math.min(width - 1, Math.floor(bounds.left || 0)));
|
|
1521
|
-
const safeRight = Math.max(safeLeft + 1, Math.min(width, Math.ceil(bounds.right || width)));
|
|
1522
|
-
const safeTop = Math.max(0, Math.min(height - 1, Math.floor(bounds.top || 0)));
|
|
1523
|
-
const safeBottom = Math.max(safeTop + 1, Math.min(height, Math.ceil(bounds.bottom || height)));
|
|
1524
|
-
const cropW = safeRight - safeLeft;
|
|
1525
|
-
const cropH = safeBottom - safeTop;
|
|
1526
|
-
const shouldCropX = cropW > 80 && cropW <= width * 0.82 && (safeLeft > width * 0.04 || width - safeRight > width * 0.04);
|
|
1527
|
-
const shouldCropY = cropH > 160 && cropH <= height * 0.88 && height - safeBottom > Math.max(320, height * 0.1);
|
|
1528
|
-
if (!shouldCropX && !shouldCropY) {
|
|
1529
|
-
return buffer;
|
|
1530
|
-
}
|
|
1531
|
-
const x = shouldCropX ? safeLeft : 0;
|
|
1532
|
-
const y = shouldCropY ? safeTop : 0;
|
|
1533
|
-
const w = shouldCropX ? cropW : width;
|
|
1534
|
-
const h = shouldCropY ? cropH : height;
|
|
1535
|
-
logger.info(`\u5185\u5BB9\u611F\u77E5\u88C1\u526A\u622A\u56FE: x=${x}, y=${y}, w=${w}, h=${h}, original=${width}x${height}`);
|
|
1536
|
-
return await cropImage(image, { x, y, w, h }).getBuffer(JimpMime.png);
|
|
1537
|
-
};
|
|
1538
|
-
var captureExpandedFullPageScreenshotOnce = async (page, options = {}) => {
|
|
1539
|
-
const stitchedTarget = await resolveVirtualizedScrollTarget(page, options);
|
|
1540
|
-
if (stitchedTarget) {
|
|
1541
|
-
const buffer = await captureStitchedScrollableScreenshot(page, stitchedTarget, options);
|
|
1542
|
-
if (buffer) {
|
|
1543
|
-
return buffer;
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
const state2 = await prepareExpandedFullPageScreenshot(page, options);
|
|
1547
|
-
try {
|
|
1548
|
-
const buffer = await capturePageScreenshot(page, {
|
|
1549
|
-
fullPage: true,
|
|
1550
|
-
type: options.type || "png",
|
|
1551
|
-
quality: options.quality,
|
|
1552
|
-
timeout: options.timeout,
|
|
1553
|
-
maxClipHeight: state2.targetHeight
|
|
1554
|
-
});
|
|
1555
|
-
return await cropBufferToContentBounds(buffer, state2.contentBounds);
|
|
1556
|
-
} finally {
|
|
1557
|
-
await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
|
|
1558
|
-
logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
|
|
1559
|
-
});
|
|
1560
|
-
if (options.restore) {
|
|
1561
|
-
await restoreExpandedFullPageScreenshot(page, state2);
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
};
|
|
1565
|
-
var capturePageScreenshot = async (page, options = {}) => {
|
|
1566
|
-
const fullPage = Boolean(options.fullPage);
|
|
1567
|
-
const type = fullPage ? FORCED_FULLPAGE_TYPE : normalizeType(options.type);
|
|
1568
|
-
const quality = fullPage ? FORCED_FULLPAGE_QUALITY : normalizeQuality(options.quality, type);
|
|
1569
|
-
const timeout = toPositiveNumber(options.timeout, DEFAULT_TIMEOUT_MS);
|
|
1570
|
-
const maxClipHeight = Math.round(toPositiveNumber(options.maxClipHeight, 0));
|
|
1571
|
-
const fallbackOptions = {
|
|
1572
|
-
type: type === "webp" ? "png" : type,
|
|
1573
|
-
fullPage
|
|
1574
|
-
};
|
|
1575
|
-
if (quality !== void 0 && fallbackOptions.type === "jpeg") {
|
|
1576
|
-
fallbackOptions.quality = quality;
|
|
1577
|
-
}
|
|
1578
|
-
if (timeout > 0) {
|
|
1579
|
-
fallbackOptions.timeout = timeout;
|
|
1580
|
-
}
|
|
1581
|
-
try {
|
|
1582
|
-
const context = page && typeof page.context === "function" ? page.context() : null;
|
|
1583
|
-
if (!context || typeof context.newCDPSession !== "function") {
|
|
1584
|
-
throw new Error("CDP session is not available");
|
|
1585
|
-
}
|
|
1586
|
-
const session = await context.newCDPSession(page);
|
|
1587
|
-
try {
|
|
1588
|
-
const metrics = await session.send("Page.getLayoutMetrics");
|
|
1589
|
-
const viewport = await resolveCurrentViewportSize(page);
|
|
1590
|
-
const captureParams = {
|
|
1591
|
-
format: type,
|
|
1592
|
-
fromSurface: true,
|
|
1593
|
-
captureBeyondViewport: fullPage,
|
|
1594
|
-
optimizeForSpeed: true
|
|
1595
|
-
};
|
|
1596
|
-
if (quality !== void 0) {
|
|
1597
|
-
captureParams.quality = quality;
|
|
1598
|
-
}
|
|
1599
|
-
if (fullPage) {
|
|
1600
|
-
captureParams.clip = buildFullPageClip(metrics, viewport, maxClipHeight);
|
|
1601
|
-
}
|
|
1602
|
-
const result = await session.send("Page.captureScreenshot", captureParams);
|
|
1603
|
-
if (!result || typeof result.data !== "string" || !result.data) {
|
|
1604
|
-
throw new Error("CDP returned empty screenshot data");
|
|
1605
|
-
}
|
|
1606
|
-
return Buffer.from(result.data, "base64");
|
|
1607
|
-
} finally {
|
|
1608
|
-
if (typeof session.detach === "function") {
|
|
1609
|
-
await session.detach().catch(() => {
|
|
1610
|
-
});
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
} catch (error) {
|
|
1614
|
-
logger.warning(`CDP \u622A\u56FE\u5931\u8D25\uFF0C\u56DE\u9000 page.screenshot: ${error?.message || error}`);
|
|
1615
|
-
return await page.screenshot(fallbackOptions);
|
|
1616
|
-
}
|
|
1617
|
-
};
|
|
1618
1120
|
var captureExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
1619
1121
|
const attempts = Math.max(1, toPositiveInteger(options.qualityRetryAttempts, DEFAULT_QUALITY_RETRY_ATTEMPTS));
|
|
1620
1122
|
const retryDelayMs = Math.max(0, Number(options.qualityRetryDelayMs ?? DEFAULT_QUALITY_RETRY_DELAY_MS) || 0);
|
|
@@ -1631,9 +1133,7 @@ var captureExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
1631
1133
|
lastBuffer = buffer;
|
|
1632
1134
|
lastAnalysis = analysis;
|
|
1633
1135
|
lastIssue = issue;
|
|
1634
|
-
if (!issue)
|
|
1635
|
-
return buffer;
|
|
1636
|
-
}
|
|
1136
|
+
if (!issue) return buffer;
|
|
1637
1137
|
if (attempt < attempts && retryDelayMs > 0) {
|
|
1638
1138
|
logger.warning(`\u622A\u56FE\u7591\u4F3C\u5F02\u5E38(${issue})\uFF0C\u7B49\u5F85\u540E\u91CD\u8BD5: attempt=${attempt}/${attempts}`);
|
|
1639
1139
|
await delay(retryDelayMs);
|
|
@@ -1641,7 +1141,7 @@ var captureExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
1641
1141
|
}
|
|
1642
1142
|
if (lastIssue && lastAnalysis) {
|
|
1643
1143
|
logger.warning(
|
|
1644
|
-
`\u622A\u56FE\u8D28\u91CF\u4ECD\u5F02\u5E38(${lastIssue})\uFF0C\u8FD4\u56DE\u6700\u4F73\u53EF\u5F97\u7ED3\u679C:
|
|
1144
|
+
`\u622A\u56FE\u8D28\u91CF\u4ECD\u5F02\u5E38(${lastIssue})\uFF0C\u8FD4\u56DE\u6700\u4F73\u53EF\u5F97\u7ED3\u679C: dark=${lastAnalysis.darkBlankRowsRatio.toFixed(2)}, loading=${lastAnalysis.loadingLikeRowsRatio.toFixed(2)}`
|
|
1645
1145
|
);
|
|
1646
1146
|
}
|
|
1647
1147
|
return lastBuffer;
|
|
@@ -2093,6 +1593,9 @@ var ProxyMeterRuntime = {
|
|
|
2093
1593
|
getProxyMeterSnapshot
|
|
2094
1594
|
};
|
|
2095
1595
|
|
|
1596
|
+
// src/internals/constants.js
|
|
1597
|
+
var PageRuntimeStateKey = "__playwright_toolkit_runtime_state__";
|
|
1598
|
+
|
|
2096
1599
|
// src/runtime-env.js
|
|
2097
1600
|
var BROWSER_PROFILE_SCHEMA_VERSION = 1;
|
|
2098
1601
|
var SUPPORTED_CLOAK_FINGERPRINT_PLATFORMS = /* @__PURE__ */ new Set(["linux", "macos", "windows"]);
|
|
@@ -9672,11 +9175,11 @@ var resolveScreenshotWatermarkifyMeta = async (page, options = {}) => {
|
|
|
9672
9175
|
watermark: options.watermark,
|
|
9673
9176
|
strip: options.strip,
|
|
9674
9177
|
stripLogoSrc,
|
|
9675
|
-
device:
|
|
9178
|
+
device: resolvePageDevice(page)
|
|
9676
9179
|
};
|
|
9677
9180
|
};
|
|
9678
9181
|
var buildFontFamily = () => 'MiSans, "SF Pro Display", "PingFang SC", "Helvetica Neue", Arial, sans-serif';
|
|
9679
|
-
var
|
|
9182
|
+
var resolvePageDevice = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
|
|
9680
9183
|
var findStripSegment = (segments, kind) => {
|
|
9681
9184
|
const found = Array.isArray(segments) ? segments.find((segment) => segment?.kind === kind) : null;
|
|
9682
9185
|
return found && typeof found === "object" ? found : {};
|
|
@@ -10328,7 +9831,7 @@ var watermarkifyScreenshotBuffer = async (buffer, meta, page = null, options = {
|
|
|
10328
9831
|
};
|
|
10329
9832
|
|
|
10330
9833
|
// src/internals/compression.js
|
|
10331
|
-
import { Jimp as Jimp2, JimpMime
|
|
9834
|
+
import { Jimp as Jimp2, JimpMime, ResizeStrategy } from "jimp";
|
|
10332
9835
|
var logger15 = createInternalLogger("Compression");
|
|
10333
9836
|
var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
|
|
10334
9837
|
var DEFAULT_SCREENSHOT_OUTPUT_TYPE = "jpeg";
|
|
@@ -10398,7 +9901,7 @@ var encodeJpeg = async (sourceImage, compression, scale, quality) => {
|
|
|
10398
9901
|
mode: ResizeStrategy.BILINEAR
|
|
10399
9902
|
});
|
|
10400
9903
|
}
|
|
10401
|
-
const buffer = await image.getBuffer(
|
|
9904
|
+
const buffer = await image.getBuffer(JimpMime.jpeg, { quality });
|
|
10402
9905
|
return {
|
|
10403
9906
|
buffer,
|
|
10404
9907
|
bytes: getBase64BytesFromBuffer(buffer),
|