@skrillex1224/playwright-toolkit 3.0.25 → 3.0.27

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 CHANGED
@@ -229,7 +229,7 @@ var ActorInfo = {
229
229
  mode: "response",
230
230
  prefix: "https://www.doubao.com/thread/",
231
231
  xurl: [
232
- "/samantha/thread/share/save",
232
+ "/share/save",
233
233
  "data",
234
234
  "share_id"
235
235
  ]
@@ -256,7 +256,6 @@ var ActorInfo = {
256
256
  name: "\u6587\u5FC3\u4E00\u8A00",
257
257
  domain: "wenxin.baidu.com",
258
258
  path: "/",
259
- device: Device.Mobile,
260
259
  share: {
261
260
  mode: "custom",
262
261
  prefix: "",
@@ -495,9 +494,7 @@ function createInternalLogger(moduleName, explicitLogger) {
495
494
 
496
495
  // src/internals/screenshot.js
497
496
  var import_delay = __toESM(require("delay"), 1);
498
-
499
- // src/internals/constants.js
500
- var PageRuntimeStateKey = "__playwright_toolkit_runtime_state__";
497
+ var import_jimp = require("jimp");
501
498
 
502
499
  // src/internals/viewport.js
503
500
  var toPositiveInt = (value) => {
@@ -529,21 +526,22 @@ var resolveCurrentViewportSize = async (page, fallback = {}) => {
529
526
  // src/internals/screenshot.js
530
527
  var logger = createInternalLogger("Screenshot");
531
528
  var DEFAULT_TIMEOUT_MS = 5e3;
532
- var FORCED_FULLPAGE_TYPE = "jpeg";
533
- var FORCED_FULLPAGE_QUALITY = 50;
534
- var SUPPORTED_TYPES = /* @__PURE__ */ new Set(["png", "jpeg", "webp"]);
535
- var EXPANDED_SCROLLABLE_CLASS = "__pk_expanded__";
536
529
  var DEFAULT_MAX_HEIGHT = 8e3;
537
- var DEFAULT_SETTLE_MS = 1e3;
538
- var DEFAULT_MOBILE_SETTLE_MS = 50;
530
+ var DEFAULT_SETTLE_MS = 260;
531
+ var DEFAULT_QUALITY_RETRY_ATTEMPTS = 4;
532
+ var DEFAULT_QUALITY_RETRY_DELAY_MS = 2500;
533
+ var EXPANDED_ATTR = "data-pk-screenshot-expanded";
534
+ var SUPPORTED_TYPES = /* @__PURE__ */ new Set(["png", "jpeg", "webp"]);
535
+ var FULLPAGE_FALLBACK_TYPE = "jpeg";
536
+ var FULLPAGE_FALLBACK_QUALITY = 50;
539
537
  var toPositiveNumber = (value, fallback = 0) => {
540
538
  const n = Number(value);
541
539
  if (!Number.isFinite(n) || n <= 0) return fallback;
542
540
  return n;
543
541
  };
544
542
  var toPositiveInteger = (value, fallback = 0) => {
545
- const number = Math.round(Number(value) || 0);
546
- return number > 0 ? number : fallback;
543
+ const n = Math.round(Number(value) || 0);
544
+ return n > 0 ? n : fallback;
547
545
  };
548
546
  var normalizeType = (value) => {
549
547
  const raw = String(value || "png").trim().toLowerCase();
@@ -558,488 +556,622 @@ var normalizeQuality = (value, type) => {
558
556
  if (rounded < 0 || rounded > 100) return void 0;
559
557
  return rounded;
560
558
  };
561
- var resolvePageDevice = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
562
559
  var buildFullPageClip = (metrics, viewport, maxClipHeight) => {
563
560
  const contentSize = metrics && typeof metrics === "object" ? metrics.contentSize || null : null;
564
561
  const width = Math.max(1, Math.ceil(viewport.width || contentSize?.width || 1));
565
562
  let height = Math.max(1, Math.ceil(contentSize?.height || viewport.height || 1));
566
- if (maxClipHeight > 0) {
567
- height = Math.min(height, maxClipHeight);
568
- }
569
- return {
570
- x: 0,
571
- y: 0,
572
- width,
573
- height,
574
- scale: 1
563
+ if (maxClipHeight > 0) height = Math.min(height, maxClipHeight);
564
+ return { x: 0, y: 0, width, height, scale: 1 };
565
+ };
566
+ var capturePageScreenshot = async (page, options = {}) => {
567
+ const fullPage = Boolean(options.fullPage);
568
+ const type = fullPage ? FULLPAGE_FALLBACK_TYPE : normalizeType(options.type);
569
+ const quality = fullPage ? FULLPAGE_FALLBACK_QUALITY : normalizeQuality(options.quality, type);
570
+ const timeout = toPositiveNumber(options.timeout, DEFAULT_TIMEOUT_MS);
571
+ const maxClipHeight = Math.round(toPositiveNumber(options.maxClipHeight, 0));
572
+ const fallbackOptions = {
573
+ type: type === "webp" ? "png" : type,
574
+ fullPage
575
575
  };
576
+ if (quality !== void 0 && fallbackOptions.type === "jpeg") fallbackOptions.quality = quality;
577
+ if (timeout > 0) fallbackOptions.timeout = timeout;
578
+ try {
579
+ const context = page && typeof page.context === "function" ? page.context() : null;
580
+ if (!context || typeof context.newCDPSession !== "function") {
581
+ throw new Error("CDP session is not available");
582
+ }
583
+ const session = await context.newCDPSession(page);
584
+ try {
585
+ const viewport = await resolveCurrentViewportSize(page);
586
+ const captureParams = {
587
+ format: type,
588
+ fromSurface: true,
589
+ captureBeyondViewport: fullPage,
590
+ optimizeForSpeed: true
591
+ };
592
+ if (quality !== void 0) captureParams.quality = quality;
593
+ if (fullPage) {
594
+ const metrics = await session.send("Page.getLayoutMetrics");
595
+ captureParams.clip = buildFullPageClip(metrics, viewport, maxClipHeight);
596
+ }
597
+ const result = await session.send("Page.captureScreenshot", captureParams);
598
+ if (!result || typeof result.data !== "string" || !result.data) {
599
+ throw new Error("CDP returned empty screenshot data");
600
+ }
601
+ return Buffer.from(result.data, "base64");
602
+ } finally {
603
+ if (typeof session.detach === "function") {
604
+ await session.detach().catch(() => {
605
+ });
606
+ }
607
+ }
608
+ } catch (error) {
609
+ logger.warning(`CDP \u622A\u56FE\u5931\u8D25\uFF0C\u56DE\u9000 page.screenshot: ${error?.message || error}`);
610
+ return await page.screenshot(fallbackOptions);
611
+ }
576
612
  };
577
- var expandScrollableContent = async (page, options = {}) => {
578
- return await page.evaluate(({ className, forceScrollableHeight, visibleOnly, expandDocumentElements }) => {
579
- const body = document.body;
580
- const root = document.documentElement;
581
- const scrollingElement = document.scrollingElement;
582
- const candidates = [];
583
- const seen = /* @__PURE__ */ new Set();
584
- let maxHeight = window.innerHeight || 0;
585
- const pushCandidate = (el) => {
586
- if (!el || seen.has(el)) return;
587
- seen.add(el);
588
- candidates.push(el);
613
+ var readMainFrameScroll = async (page) => {
614
+ return await page.evaluate(() => ({
615
+ x: Math.max(0, Math.round(window.scrollX || document.scrollingElement?.scrollLeft || 0)),
616
+ y: Math.max(0, Math.round(window.scrollY || document.scrollingElement?.scrollTop || 0))
617
+ })).catch(() => ({ x: 0, y: 0 }));
618
+ };
619
+ var restoreMainFrameScroll = async (page, state2) => {
620
+ if (!state2) return;
621
+ await page.evaluate(({ x, y }) => {
622
+ window.scrollTo(Math.max(0, Math.round(Number(x) || 0)), Math.max(0, Math.round(Number(y) || 0)));
623
+ }, state2).catch(() => {
624
+ });
625
+ };
626
+ var freezeViewportMetrics = async (page) => {
627
+ const viewport = await resolveCurrentViewportSize(page).catch(() => null);
628
+ if (!viewport?.width || !viewport?.height) return false;
629
+ return await page.evaluate(({ width, height }) => {
630
+ const key = "__pkScreenshotViewportFreeze";
631
+ if (window[key]?.active) return true;
632
+ const state2 = {
633
+ active: true,
634
+ entries: []
589
635
  };
590
- pushCandidate(scrollingElement);
591
- pushCandidate(root);
592
- pushCandidate(body);
593
- document.querySelectorAll("*").forEach(pushCandidate);
636
+ const override = (target, prop, value) => {
637
+ if (!target) return;
638
+ try {
639
+ const descriptor = Object.getOwnPropertyDescriptor(target, prop);
640
+ state2.entries.push({
641
+ target,
642
+ prop,
643
+ hadOwn: Boolean(descriptor),
644
+ descriptor
645
+ });
646
+ Object.defineProperty(target, prop, {
647
+ configurable: true,
648
+ get: () => value
649
+ });
650
+ } catch {
651
+ }
652
+ };
653
+ override(window, "innerWidth", width);
654
+ override(window, "innerHeight", height);
655
+ if (window.visualViewport) {
656
+ override(window.visualViewport, "width", width);
657
+ override(window.visualViewport, "height", height);
658
+ }
659
+ window[key] = state2;
660
+ return true;
661
+ }, {
662
+ width: Math.max(1, Math.round(viewport.width)),
663
+ height: Math.max(1, Math.round(viewport.height))
664
+ }).catch((error) => {
665
+ logger.warning(`\u51BB\u7ED3 viewport \u6307\u6807\u5931\u8D25: ${error?.message || error}`);
666
+ return false;
667
+ });
668
+ };
669
+ var restoreViewportMetrics = async (page) => {
670
+ await page.evaluate(() => {
671
+ const key = "__pkScreenshotViewportFreeze";
672
+ const state2 = window[key];
673
+ if (!state2?.active) return;
674
+ for (const entry of [...state2.entries].reverse()) {
675
+ try {
676
+ if (entry.hadOwn && entry.descriptor) {
677
+ Object.defineProperty(entry.target, entry.prop, entry.descriptor);
678
+ } else {
679
+ delete entry.target[entry.prop];
680
+ }
681
+ } catch {
682
+ }
683
+ }
684
+ delete window[key];
685
+ }).catch(() => {
686
+ });
687
+ };
688
+ var expandScrollableContentInContext = async (context, options = {}) => {
689
+ return await context.evaluate(async ({
690
+ expandedAttr,
691
+ maxHeight,
692
+ preloadLazyContent,
693
+ preloadSettleMs,
694
+ visibleOnly
695
+ }) => {
696
+ const root = document.documentElement;
697
+ const body = document.body;
698
+ const scrollingElement = document.scrollingElement || root || body;
699
+ const viewportWidth = window.innerWidth || root?.clientWidth || body?.clientWidth || 1;
700
+ const viewportHeight = window.innerHeight || root?.clientHeight || body?.clientHeight || 1;
594
701
  const scrollableOverflow = /* @__PURE__ */ new Set(["auto", "scroll", "overlay"]);
595
702
  const clippingOverflow = /* @__PURE__ */ new Set(["hidden", "clip"]);
703
+ const candidates = [];
704
+ const changed = [];
705
+ const push = (el) => {
706
+ if (el && !candidates.includes(el)) candidates.push(el);
707
+ };
708
+ push(scrollingElement);
709
+ push(root);
710
+ push(body);
711
+ document.querySelectorAll("*").forEach(push);
596
712
  const isDocumentElement = (el) => el === root || el === body || el === scrollingElement;
713
+ const delayMs = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, Number(ms) || 0)));
714
+ const styleValue = (el, prop) => el.style.getPropertyValue(prop) || "";
715
+ const stylePriority = (el, prop) => el.style.getPropertyPriority(prop) || "";
597
716
  const isVisible = (el, style, rect) => {
598
717
  if (!el || !style || !rect) return false;
599
- if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") {
600
- return false;
601
- }
602
- if (Number(style.opacity) === 0) return false;
718
+ if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") return false;
719
+ if (Number(style.opacity || 1) === 0) return false;
603
720
  return rect.width > 0 && rect.height > 0;
604
721
  };
722
+ const intersectsViewport = (rect) => {
723
+ if (!rect || rect.width <= 0 || rect.height <= 0) return false;
724
+ return rect.bottom > 0 && rect.right > 0 && rect.top < viewportHeight && rect.left < viewportWidth;
725
+ };
726
+ const isVisibleInViewport = (el, style, rect) => isVisible(el, style, rect) && intersectsViewport(rect);
605
727
  const hasOverflowValue = (style, values) => {
606
- const overflowY = String(style?.overflowY || "").toLowerCase();
607
728
  const overflow = String(style?.overflow || "").toLowerCase();
608
- return values.has(overflowY) || values.has(overflow);
729
+ const overflowY = String(style?.overflowY || "").toLowerCase();
730
+ return values.has(overflow) || values.has(overflowY);
609
731
  };
610
732
  const isLargeVerticalClipper = (el, rect) => {
611
733
  if (!el || !rect) return false;
612
- const viewportHeight = window.innerHeight || 0;
613
- const visibleHeight = Math.ceil(rect.height || 0);
614
- const clientHeight = Math.ceil(el.clientHeight || 0);
615
- const boxHeight = Math.max(visibleHeight, clientHeight);
734
+ const boxHeight = Math.max(Math.ceil(rect.height || 0), Math.ceil(el.clientHeight || 0));
616
735
  return boxHeight >= Math.max(320, viewportHeight * 0.45);
617
736
  };
618
- const hasLargeVerticalOverflow = (el, rect) => {
619
- if (!isLargeVerticalClipper(el, rect)) return false;
620
- const viewportHeight = window.innerHeight || 0;
621
- const scrollHeight = Math.ceil(el.scrollHeight || 0);
622
- const clientHeight = Math.ceil(el.clientHeight || 0);
623
- const overflowDelta = scrollHeight - clientHeight;
624
- return overflowDelta >= Math.max(160, viewportHeight * 0.18);
625
- };
626
- const rememberOriginalBoxStyles = (el, { includeDocumentElement = false } = {}) => {
627
- if (!el || !includeDocumentElement && isDocumentElement(el) || el.classList.contains(className)) {
628
- return;
629
- }
630
- el.dataset.pkOrigOverflow = el.style.overflow;
631
- el.dataset.pkOrigOverflowPriority = el.style.getPropertyPriority("overflow") || "";
632
- el.dataset.pkOrigHeight = el.style.height;
633
- el.dataset.pkOrigHeightPriority = el.style.getPropertyPriority("height") || "";
634
- el.dataset.pkOrigMinHeight = el.style.minHeight;
635
- el.dataset.pkOrigMinHeightPriority = el.style.getPropertyPriority("min-height") || "";
636
- el.dataset.pkOrigMaxHeight = el.style.maxHeight;
637
- el.dataset.pkOrigMaxHeightPriority = el.style.getPropertyPriority("max-height") || "";
638
- el.classList.add(className);
639
- };
640
- const expandDocumentElementsToHeight = (height) => {
641
- if (!expandDocumentElements) return;
642
- const targetHeight = Math.ceil(Number(height) || 0);
643
- if (targetHeight <= 0) return;
644
- [root, body, scrollingElement].forEach((el) => {
645
- if (!el) return;
646
- rememberOriginalBoxStyles(el, { includeDocumentElement: true });
647
- el.style.setProperty("overflow", "visible", "important");
648
- el.style.setProperty("height", `${targetHeight}px`, "important");
649
- el.style.setProperty("min-height", `${targetHeight}px`, "important");
650
- el.style.setProperty("max-height", "none", "important");
651
- });
652
- };
653
737
  const isScrollableY = (el, style, rect) => {
654
- if (!el || el.scrollHeight <= el.clientHeight + 1) {
655
- return false;
656
- }
657
- if (isDocumentElement(el)) {
658
- return true;
659
- }
660
- if (hasOverflowValue(style, scrollableOverflow)) {
661
- return true;
662
- }
663
- return hasOverflowValue(style, clippingOverflow) && hasLargeVerticalOverflow(el, rect);
738
+ if (!el || Math.ceil(el.scrollHeight || 0) <= Math.ceil(el.clientHeight || 0) + 2) return false;
739
+ if (isDocumentElement(el)) return true;
740
+ if (hasOverflowValue(style, scrollableOverflow)) return true;
741
+ if (!hasOverflowValue(style, clippingOverflow)) return false;
742
+ const overflowDelta = Math.ceil(el.scrollHeight || 0) - Math.ceil(el.clientHeight || 0);
743
+ return isLargeVerticalClipper(el, rect) && overflowDelta >= Math.max(160, viewportHeight * 0.18);
664
744
  };
665
- const measureNeededHeight = (el) => {
666
- if (!el) return 0;
667
- const scrollHeight = Math.ceil(el.scrollHeight || 0);
668
- if (scrollHeight <= 0) return 0;
669
- if (isDocumentElement(el)) {
670
- return scrollHeight;
671
- }
672
- const rect = el.getBoundingClientRect();
673
- if (!rect || rect.width <= 0 || rect.height <= 0) {
674
- return 0;
675
- }
676
- const documentTop = Math.max(0, Math.ceil(rect.top + (window.scrollY || window.pageYOffset || 0)));
677
- return documentTop + scrollHeight;
745
+ const rememberBox = (el) => {
746
+ if (!el || el.getAttribute(expandedAttr) === "1") return;
747
+ el.setAttribute(expandedAttr, "1");
748
+ el.dataset.pkSsOverflow = styleValue(el, "overflow");
749
+ el.dataset.pkSsOverflowPriority = stylePriority(el, "overflow");
750
+ el.dataset.pkSsOverflowX = styleValue(el, "overflow-x");
751
+ el.dataset.pkSsOverflowXPriority = stylePriority(el, "overflow-x");
752
+ el.dataset.pkSsOverflowY = styleValue(el, "overflow-y");
753
+ el.dataset.pkSsOverflowYPriority = stylePriority(el, "overflow-y");
754
+ el.dataset.pkSsHeight = styleValue(el, "height");
755
+ el.dataset.pkSsHeightPriority = stylePriority(el, "height");
756
+ el.dataset.pkSsMinHeight = styleValue(el, "min-height");
757
+ el.dataset.pkSsMinHeightPriority = stylePriority(el, "min-height");
758
+ el.dataset.pkSsMaxHeight = styleValue(el, "max-height");
759
+ el.dataset.pkSsMaxHeightPriority = stylePriority(el, "max-height");
760
+ el.dataset.pkSsWidth = styleValue(el, "width");
761
+ el.dataset.pkSsWidthPriority = stylePriority(el, "width");
762
+ el.dataset.pkSsBoxSizing = styleValue(el, "box-sizing");
763
+ el.dataset.pkSsBoxSizingPriority = stylePriority(el, "box-sizing");
764
+ el.dataset.pkSsAlignSelf = styleValue(el, "align-self");
765
+ el.dataset.pkSsAlignSelfPriority = stylePriority(el, "align-self");
766
+ el.dataset.pkSsJustifySelf = styleValue(el, "justify-self");
767
+ el.dataset.pkSsJustifySelfPriority = stylePriority(el, "justify-self");
768
+ el.dataset.pkSsScrollTop = String(Math.max(0, Math.round(el.scrollTop || 0)));
769
+ el.dataset.pkSsScrollLeft = String(Math.max(0, Math.round(el.scrollLeft || 0)));
770
+ changed.push(el);
678
771
  };
679
- const scrollableElements = [];
680
- const expandedAncestors = [];
681
- const expandElementToScrollHeight = (el) => {
682
- if (!el || isDocumentElement(el)) return;
683
- const scrollHeight = Math.ceil(el.scrollHeight || 0);
684
- if (scrollHeight <= 0) return;
685
- rememberOriginalBoxStyles(el);
772
+ const setExpandedBox = (el, { height, width }) => {
773
+ if (!el) return;
774
+ rememberBox(el);
686
775
  el.style.setProperty("overflow", "visible", "important");
687
- el.style.setProperty("height", `${scrollHeight}px`, "important");
688
- el.style.setProperty("min-height", `${scrollHeight}px`, "important");
776
+ el.style.setProperty("overflow-y", "visible", "important");
689
777
  el.style.setProperty("max-height", "none", "important");
778
+ if (height > 0) {
779
+ const cssHeight = `${Math.ceil(height)}px`;
780
+ el.style.setProperty("height", cssHeight, "important");
781
+ el.style.setProperty("min-height", cssHeight, "important");
782
+ }
783
+ if (!isDocumentElement(el) && width > 0) {
784
+ el.style.setProperty("box-sizing", "border-box", "important");
785
+ el.style.setProperty("width", `${Math.ceil(width)}px`, "important");
786
+ el.style.setProperty("align-self", "flex-start", "important");
787
+ el.style.setProperty("justify-self", "start", "important");
788
+ }
789
+ el.scrollTop = 0;
790
+ el.scrollLeft = 0;
690
791
  };
691
792
  const expandClippingAncestors = (el) => {
692
793
  for (let node = el?.parentElement; node && !isDocumentElement(node); node = node.parentElement) {
693
794
  const style = window.getComputedStyle(node);
694
795
  const rect = node.getBoundingClientRect();
695
796
  if (!isVisible(node, style, rect)) continue;
696
- const scrollableClip = hasOverflowValue(style, scrollableOverflow);
697
- const visualClip = hasOverflowValue(style, clippingOverflow);
698
- if (!scrollableClip && (!visualClip || !isLargeVerticalClipper(node, rect))) continue;
699
- rememberOriginalBoxStyles(node);
797
+ const clips = hasOverflowValue(style, scrollableOverflow) || hasOverflowValue(style, clippingOverflow);
798
+ if (!clips) continue;
799
+ rememberBox(node);
700
800
  node.style.setProperty("overflow", "visible", "important");
801
+ node.style.setProperty("overflow-y", "visible", "important");
701
802
  node.style.setProperty("max-height", "none", "important");
702
- expandedAncestors.push(node);
703
803
  }
704
804
  };
705
- const expandAncestorsToCurrentScrollHeight = () => {
706
- const ancestors = [...new Set(expandedAncestors)];
707
- for (let pass = 0; pass < 2; pass += 1) {
708
- ancestors.forEach((node) => {
709
- if (!node || isDocumentElement(node)) return;
710
- const scrollHeight = Math.ceil(node.scrollHeight || 0);
711
- if (scrollHeight <= node.clientHeight + 1) return;
712
- rememberOriginalBoxStyles(node);
713
- node.style.setProperty("height", `${scrollHeight}px`, "important");
714
- node.style.setProperty("min-height", `${scrollHeight}px`, "important");
715
- node.style.setProperty("max-height", "none", "important");
716
- });
805
+ const preloadCandidates = [];
806
+ for (const el of candidates) {
807
+ const style = window.getComputedStyle(el);
808
+ const rect = isDocumentElement(el) ? { top: 0, left: 0, width: viewportWidth, height: viewportHeight } : el.getBoundingClientRect();
809
+ if (visibleOnly && !isDocumentElement(el) && !isVisibleInViewport(el, style, rect)) continue;
810
+ if (!isScrollableY(el, style, rect)) continue;
811
+ preloadCandidates.push(el);
812
+ }
813
+ if (preloadLazyContent && preloadCandidates.length > 0) {
814
+ preloadCandidates.forEach((el) => {
815
+ rememberBox(el);
816
+ const maxTop = Math.max(0, Math.ceil((el.scrollHeight || 0) - (el.clientHeight || viewportHeight || 0)));
817
+ if (isDocumentElement(el)) {
818
+ window.scrollTo(window.scrollX || 0, maxTop);
819
+ } else {
820
+ el.scrollTop = maxTop;
821
+ el.dispatchEvent(new Event("scroll", { bubbles: true }));
822
+ }
823
+ });
824
+ window.dispatchEvent(new Event("scroll"));
825
+ await delayMs(preloadSettleMs);
826
+ }
827
+ let targetHeight = viewportHeight;
828
+ let scrollableCount = 0;
829
+ let expandedCount = 0;
830
+ for (const el of preloadCandidates) {
831
+ const rect = isDocumentElement(el) ? { top: 0, left: 0, width: viewportWidth, height: viewportHeight } : el.getBoundingClientRect();
832
+ const scrollHeight = Math.ceil(el.scrollHeight || 0);
833
+ const clientHeight = Math.ceil(el.clientHeight || viewportHeight || 0);
834
+ if (scrollHeight <= clientHeight + 2) continue;
835
+ scrollableCount += 1;
836
+ if (isDocumentElement(el)) {
837
+ targetHeight = Math.max(targetHeight, scrollHeight);
838
+ el.scrollTop = 0;
839
+ continue;
717
840
  }
718
- };
719
- const measureExpandedRenderedBottom = () => {
720
- let renderedBottom = maxHeight;
841
+ const documentTop = Math.max(0, Math.ceil(rect.top + (window.scrollY || window.pageYOffset || 0)));
842
+ const width = Math.max(1, Math.ceil(rect.width || el.clientWidth || viewportWidth || 1));
843
+ targetHeight = Math.max(targetHeight, documentTop + scrollHeight);
844
+ expandClippingAncestors(el);
845
+ setExpandedBox(el, { height: scrollHeight, width });
846
+ expandedCount += 1;
847
+ }
848
+ for (let pass = 0; pass < 2; pass += 1) {
849
+ for (const el of changed) {
850
+ if (!el || isDocumentElement(el)) continue;
851
+ const height = Math.ceil(el.scrollHeight || 0);
852
+ if (height > Math.ceil(el.clientHeight || 0) + 2) {
853
+ el.style.setProperty("height", `${height}px`, "important");
854
+ el.style.setProperty("min-height", `${height}px`, "important");
855
+ }
856
+ }
857
+ }
858
+ const measureDocumentHeight = () => Math.max(
859
+ viewportHeight,
860
+ Math.ceil(root?.scrollHeight || 0),
861
+ Math.ceil(body?.scrollHeight || 0),
862
+ Math.ceil(scrollingElement?.scrollHeight || 0)
863
+ );
864
+ const measureExpandedBottom = () => {
865
+ let bottom = viewportHeight;
721
866
  const scrollY = window.scrollY || window.pageYOffset || 0;
722
- candidates.forEach((el) => {
867
+ document.querySelectorAll(`[${expandedAttr}="1"]`).forEach((el) => {
723
868
  if (!el || isDocumentElement(el)) return;
724
- if (!el.classList.contains(className) && !el.closest(`.${className}`)) return;
725
- const style = window.getComputedStyle(el);
726
869
  const rect = el.getBoundingClientRect();
727
- if (!isVisible(el, style, rect)) return;
728
- renderedBottom = Math.max(renderedBottom, Math.ceil(rect.bottom + scrollY));
870
+ if (!rect || rect.width <= 0 || rect.height <= 0) return;
871
+ bottom = Math.max(bottom, Math.ceil(rect.bottom + scrollY));
729
872
  });
730
- return renderedBottom;
731
- };
732
- const measureDocumentScrollHeight = () => {
733
- return Math.max(
734
- maxHeight,
735
- Math.ceil(root?.scrollHeight || 0),
736
- Math.ceil(body?.scrollHeight || 0),
737
- Math.ceil(scrollingElement?.scrollHeight || 0)
738
- );
873
+ return bottom;
739
874
  };
740
- candidates.forEach((el) => {
741
- const style = window.getComputedStyle(el);
742
- const rect = el.getBoundingClientRect();
743
- if (visibleOnly && !isVisible(el, style, rect)) {
744
- return;
745
- }
746
- if (!isScrollableY(el, style, rect)) {
747
- return;
748
- }
749
- const neededHeight = measureNeededHeight(el);
750
- if (neededHeight <= 0) {
751
- return;
752
- }
753
- if (neededHeight > maxHeight) {
754
- maxHeight = neededHeight;
755
- }
756
- scrollableElements.push(el);
757
- if (isDocumentElement(el)) {
758
- return;
759
- }
760
- expandClippingAncestors(el);
761
- if (forceScrollableHeight) {
762
- expandElementToScrollHeight(el);
763
- return;
764
- }
765
- rememberOriginalBoxStyles(el);
766
- el.style.overflow = "visible";
767
- el.style.height = "auto";
768
- el.style.maxHeight = "none";
875
+ targetHeight = Math.max(targetHeight, measureDocumentHeight(), measureExpandedBottom());
876
+ targetHeight = Math.min(Math.max(1, Math.ceil(targetHeight)), Math.max(1, Math.ceil(maxHeight || targetHeight)));
877
+ [root, body, scrollingElement].forEach((el) => {
878
+ if (!el) return;
879
+ setExpandedBox(el, { height: targetHeight, width: 0 });
769
880
  });
770
- expandAncestorsToCurrentScrollHeight();
771
- scrollableElements.forEach((el) => {
772
- const neededHeight = measureNeededHeight(el);
773
- if (neededHeight > maxHeight) {
774
- maxHeight = neededHeight;
881
+ window.scrollTo(0, 0);
882
+ document.querySelectorAll(`[${expandedAttr}="1"]`).forEach((el) => {
883
+ if (!isDocumentElement(el)) {
884
+ el.scrollTop = 0;
885
+ el.scrollLeft = 0;
775
886
  }
776
887
  });
777
- maxHeight = Math.max(maxHeight, measureDocumentScrollHeight(), measureExpandedRenderedBottom());
778
- if (expandDocumentElements) {
779
- for (let pass = 0; pass < 2; pass += 1) {
780
- expandDocumentElementsToHeight(maxHeight);
781
- maxHeight = Math.max(maxHeight, measureDocumentScrollHeight(), measureExpandedRenderedBottom());
782
- }
783
- }
784
- return Math.ceil(maxHeight);
888
+ window.dispatchEvent(new Event("scroll"));
889
+ await delayMs(preloadSettleMs);
890
+ return {
891
+ height: Math.max(targetHeight, measureDocumentHeight(), measureExpandedBottom()),
892
+ scrollableCount,
893
+ expandedCount
894
+ };
785
895
  }, {
786
- className: EXPANDED_SCROLLABLE_CLASS,
787
- forceScrollableHeight: options.forceScrollableHeight !== false,
788
- visibleOnly: options.visibleOnly !== false,
789
- expandDocumentElements: options.expandDocumentElements === true
896
+ expandedAttr: EXPANDED_ATTR,
897
+ maxHeight: toPositiveInteger(options.maxHeight, DEFAULT_MAX_HEIGHT),
898
+ preloadLazyContent: options.preloadLazyContent !== false,
899
+ preloadSettleMs: Math.max(0, Math.min(500, Number(options.preloadSettleMs ?? options.settleMs ?? 120) || 0)),
900
+ visibleOnly: options.visibleOnly !== false
901
+ }).catch((error) => {
902
+ logger.warning(`\u5C55\u5F00\u6EDA\u52A8\u5BB9\u5668\u5931\u8D25: ${error?.message || error}`);
903
+ return { height: 0, scrollableCount: 0, expandedCount: 0 };
790
904
  });
791
905
  };
792
- var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
793
- return await page.evaluate(({ className, targetHeight }) => {
794
- const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || document.body?.clientWidth || 0;
795
- const viewportHeight = window.innerHeight || document.documentElement?.clientHeight || document.body?.clientHeight || 0;
796
- const scrollY = window.scrollY || window.pageYOffset || 0;
797
- const safeTargetHeight = Math.ceil(Number(targetHeight) || 0);
798
- if (safeTargetHeight <= viewportHeight + 1) {
799
- return 0;
800
- }
801
- const hasOwn2 = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
802
- const isVisible = (el, style, rect) => {
803
- if (!el || !style || !rect) return false;
804
- if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") {
805
- return false;
806
- }
807
- if (Number(style.opacity) === 0) return false;
808
- return rect.width > 0 && rect.height > 0;
809
- };
810
- const edgeThreshold = Math.max(24, Math.min(96, viewportHeight * 0.12));
811
- const maxAffixedHeight = Math.max(56, viewportHeight * 0.65);
812
- const candidates = [];
813
- document.querySelectorAll("*").forEach((el) => {
814
- const style = window.getComputedStyle(el);
815
- const position = String(style.position || "").toLowerCase();
816
- if (position !== "fixed" && position !== "sticky") return;
817
- const rect = el.getBoundingClientRect();
818
- if (!isVisible(el, style, rect)) return;
819
- if (rect.height > maxAffixedHeight) return;
820
- if (rect.width < viewportWidth * 0.25 && rect.height < 80) return;
821
- const top = Number.parseFloat(style.top);
822
- const bottom = Number.parseFloat(style.bottom);
823
- const hasTopRule = Number.isFinite(top) && top <= Math.max(160, viewportHeight * 0.25);
824
- const hasBottomRule = Number.isFinite(bottom) && bottom <= Math.max(160, viewportHeight * 0.25);
825
- const isNearViewportTop = rect.top <= edgeThreshold;
826
- const isNearViewportBottom = rect.bottom >= viewportHeight - edgeThreshold;
827
- const edge = (hasBottomRule || isNearViewportBottom) && !(hasTopRule || isNearViewportTop) ? "bottom" : "top";
828
- if (position === "fixed" && edge === "top" && !hasTopRule && !isNearViewportTop) return;
829
- if (position === "fixed" && edge === "bottom" && !hasBottomRule && !isNearViewportBottom) return;
830
- if (position === "sticky" && !hasTopRule && !hasBottomRule && !isNearViewportTop && !isNearViewportBottom) return;
831
- candidates.push({ el, position, edge });
832
- });
833
- const candidateSet = new Set(candidates.map(({ el }) => el));
834
- const topLevelCandidates = candidates.filter(({ el }) => {
835
- for (let parent = el.parentElement; parent; parent = parent.parentElement) {
836
- if (candidateSet.has(parent)) return false;
906
+ var expandFrameElement = async (frame, height) => {
907
+ const handle = await frame.frameElement?.().catch(() => null);
908
+ if (!handle) return false;
909
+ try {
910
+ return await handle.evaluate((el, { expandedAttr, targetHeight }) => {
911
+ if (!el || targetHeight <= 0) return false;
912
+ if (el.getAttribute(expandedAttr) !== "1") {
913
+ el.setAttribute(expandedAttr, "1");
914
+ el.dataset.pkSsOverflow = el.style.getPropertyValue("overflow") || "";
915
+ el.dataset.pkSsOverflowPriority = el.style.getPropertyPriority("overflow") || "";
916
+ el.dataset.pkSsOverflowX = el.style.getPropertyValue("overflow-x") || "";
917
+ el.dataset.pkSsOverflowXPriority = el.style.getPropertyPriority("overflow-x") || "";
918
+ el.dataset.pkSsOverflowY = el.style.getPropertyValue("overflow-y") || "";
919
+ el.dataset.pkSsOverflowYPriority = el.style.getPropertyPriority("overflow-y") || "";
920
+ el.dataset.pkSsHeight = el.style.getPropertyValue("height") || "";
921
+ el.dataset.pkSsHeightPriority = el.style.getPropertyPriority("height") || "";
922
+ el.dataset.pkSsMinHeight = el.style.getPropertyValue("min-height") || "";
923
+ el.dataset.pkSsMinHeightPriority = el.style.getPropertyPriority("min-height") || "";
924
+ el.dataset.pkSsMaxHeight = el.style.getPropertyValue("max-height") || "";
925
+ el.dataset.pkSsMaxHeightPriority = el.style.getPropertyPriority("max-height") || "";
926
+ el.dataset.pkSsWidth = el.style.getPropertyValue("width") || "";
927
+ el.dataset.pkSsWidthPriority = el.style.getPropertyPriority("width") || "";
928
+ el.dataset.pkSsBoxSizing = el.style.getPropertyValue("box-sizing") || "";
929
+ el.dataset.pkSsBoxSizingPriority = el.style.getPropertyPriority("box-sizing") || "";
930
+ el.dataset.pkSsAlignSelf = el.style.getPropertyValue("align-self") || "";
931
+ el.dataset.pkSsAlignSelfPriority = el.style.getPropertyPriority("align-self") || "";
932
+ el.dataset.pkSsJustifySelf = el.style.getPropertyValue("justify-self") || "";
933
+ el.dataset.pkSsJustifySelfPriority = el.style.getPropertyPriority("justify-self") || "";
934
+ el.dataset.pkSsScrollTop = String(Math.max(0, Math.round(el.scrollTop || 0)));
935
+ el.dataset.pkSsScrollLeft = String(Math.max(0, Math.round(el.scrollLeft || 0)));
837
936
  }
937
+ el.style.setProperty("overflow", "visible", "important");
938
+ el.style.setProperty("overflow-y", "visible", "important");
939
+ el.style.setProperty("height", `${Math.ceil(targetHeight)}px`, "important");
940
+ el.style.setProperty("min-height", `${Math.ceil(targetHeight)}px`, "important");
941
+ el.style.setProperty("max-height", "none", "important");
942
+ el.style.setProperty("align-self", "flex-start", "important");
943
+ el.style.setProperty("justify-self", "start", "important");
838
944
  return true;
945
+ }, { expandedAttr: EXPANDED_ATTR, targetHeight: Math.ceil(height) });
946
+ } finally {
947
+ await handle.dispose?.().catch(() => {
839
948
  });
840
- topLevelCandidates.forEach(({ el, position, edge }) => {
841
- if (!hasOwn2(el.dataset, "pkAffixedAdjusted")) {
842
- el.dataset.pkAffixedAdjusted = "1";
843
- el.dataset.pkOrigPosition = el.style.getPropertyValue("position") || "";
844
- el.dataset.pkOrigPositionPriority = el.style.getPropertyPriority("position") || "";
845
- el.dataset.pkOrigTop = el.style.getPropertyValue("top") || "";
846
- el.dataset.pkOrigTopPriority = el.style.getPropertyPriority("top") || "";
847
- el.dataset.pkOrigBottom = el.style.getPropertyValue("bottom") || "";
848
- el.dataset.pkOrigBottomPriority = el.style.getPropertyPriority("bottom") || "";
849
- el.dataset.pkOrigTranslate = el.style.getPropertyValue("translate") || "";
850
- el.dataset.pkOrigTranslatePriority = el.style.getPropertyPriority("translate") || "";
851
- }
852
- el.classList.add(className);
853
- if (position === "fixed") {
854
- const deltaY = edge === "bottom" ? safeTargetHeight - viewportHeight - scrollY : -scrollY;
855
- if (Math.abs(deltaY) > 1) {
856
- el.style.setProperty("translate", `0 ${Math.round(deltaY)}px`, "important");
857
- }
858
- return;
949
+ }
950
+ };
951
+ var restoreExpandedStateInContext = async (context) => {
952
+ await context.evaluate(({ expandedAttr }) => {
953
+ const hasOwn2 = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
954
+ const restoreStyle = (el, prop, valueKey, priorityKey) => {
955
+ if (!hasOwn2(el.dataset, valueKey)) return;
956
+ const value = el.dataset[valueKey] || "";
957
+ const priority = el.dataset[priorityKey] || "";
958
+ if (value) {
959
+ el.style.setProperty(prop, value, priority);
960
+ } else {
961
+ el.style.removeProperty(prop);
859
962
  }
860
- el.style.setProperty("position", "relative", "important");
861
- el.style.setProperty("top", "auto", "important");
862
- el.style.setProperty("bottom", "auto", "important");
963
+ delete el.dataset[valueKey];
964
+ delete el.dataset[priorityKey];
965
+ };
966
+ document.querySelectorAll(`[${expandedAttr}="1"]`).forEach((el) => {
967
+ restoreStyle(el, "overflow", "pkSsOverflow", "pkSsOverflowPriority");
968
+ restoreStyle(el, "overflow-x", "pkSsOverflowX", "pkSsOverflowXPriority");
969
+ restoreStyle(el, "overflow-y", "pkSsOverflowY", "pkSsOverflowYPriority");
970
+ restoreStyle(el, "height", "pkSsHeight", "pkSsHeightPriority");
971
+ restoreStyle(el, "min-height", "pkSsMinHeight", "pkSsMinHeightPriority");
972
+ restoreStyle(el, "max-height", "pkSsMaxHeight", "pkSsMaxHeightPriority");
973
+ restoreStyle(el, "width", "pkSsWidth", "pkSsWidthPriority");
974
+ restoreStyle(el, "box-sizing", "pkSsBoxSizing", "pkSsBoxSizingPriority");
975
+ restoreStyle(el, "align-self", "pkSsAlignSelf", "pkSsAlignSelfPriority");
976
+ restoreStyle(el, "justify-self", "pkSsJustifySelf", "pkSsJustifySelfPriority");
977
+ if (hasOwn2(el.dataset, "pkSsScrollTop")) {
978
+ el.scrollTop = Math.max(0, Math.round(Number(el.dataset.pkSsScrollTop) || 0));
979
+ delete el.dataset.pkSsScrollTop;
980
+ }
981
+ if (hasOwn2(el.dataset, "pkSsScrollLeft")) {
982
+ el.scrollLeft = Math.max(0, Math.round(Number(el.dataset.pkSsScrollLeft) || 0));
983
+ delete el.dataset.pkSsScrollLeft;
984
+ }
985
+ el.removeAttribute(expandedAttr);
863
986
  });
864
- return topLevelCandidates.length;
987
+ window.dispatchEvent(new Event("scroll"));
865
988
  }, {
866
- className: EXPANDED_SCROLLABLE_CLASS,
867
- targetHeight: options.targetHeight
989
+ expandedAttr: EXPANDED_ATTR
990
+ }).catch(() => {
868
991
  });
869
992
  };
870
- var restoreAffixedElementsForExpandedScreenshot = async (page) => {
871
- await page.evaluate((className) => {
872
- const hasOwn2 = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
873
- const expansionKeys = [
874
- "pkOrigOverflow",
875
- "pkOrigHeight",
876
- "pkOrigMinHeight",
877
- "pkOrigMaxHeight"
878
- ];
879
- document.querySelectorAll('[data-pk-affixed-adjusted="1"]').forEach((el) => {
880
- if (hasOwn2(el.dataset, "pkOrigPosition")) {
881
- el.style.setProperty("position", el.dataset.pkOrigPosition || "", el.dataset.pkOrigPositionPriority || "");
882
- delete el.dataset.pkOrigPosition;
883
- delete el.dataset.pkOrigPositionPriority;
884
- }
885
- if (hasOwn2(el.dataset, "pkOrigTop")) {
886
- el.style.setProperty("top", el.dataset.pkOrigTop || "", el.dataset.pkOrigTopPriority || "");
887
- delete el.dataset.pkOrigTop;
888
- delete el.dataset.pkOrigTopPriority;
889
- }
890
- if (hasOwn2(el.dataset, "pkOrigBottom")) {
891
- el.style.setProperty("bottom", el.dataset.pkOrigBottom || "", el.dataset.pkOrigBottomPriority || "");
892
- delete el.dataset.pkOrigBottom;
893
- delete el.dataset.pkOrigBottomPriority;
894
- }
895
- if (hasOwn2(el.dataset, "pkOrigTranslate")) {
896
- el.style.setProperty("translate", el.dataset.pkOrigTranslate || "", el.dataset.pkOrigTranslatePriority || "");
897
- delete el.dataset.pkOrigTranslate;
898
- delete el.dataset.pkOrigTranslatePriority;
899
- }
900
- delete el.dataset.pkAffixedAdjusted;
901
- const stillExpanded = expansionKeys.some((key) => hasOwn2(el.dataset, key));
902
- if (!stillExpanded) {
903
- el.classList.remove(className);
904
- }
993
+ var resetScrollPositionInContext = async (context) => {
994
+ await context.evaluate(() => {
995
+ const root = document.documentElement;
996
+ const body = document.body;
997
+ const scrollingElement = document.scrollingElement || root || body;
998
+ window.scrollTo(0, 0);
999
+ [root, body, scrollingElement].forEach((el) => {
1000
+ if (!el) return;
1001
+ el.scrollTop = 0;
1002
+ el.scrollLeft = 0;
1003
+ el.dispatchEvent(new Event("scroll", { bubbles: true }));
905
1004
  });
906
- }, EXPANDED_SCROLLABLE_CLASS);
1005
+ window.dispatchEvent(new Event("scroll"));
1006
+ }).catch(() => {
1007
+ });
907
1008
  };
908
1009
  var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
909
- const originalViewport = await resolveCurrentViewportSize(page);
910
1010
  const maxHeight = toPositiveInteger(options.maxHeight, DEFAULT_MAX_HEIGHT);
911
- const preserveViewport = options.preserveViewport ?? resolvePageDevice(page) === Device.Mobile;
912
- const defaultSettleMs = preserveViewport ? DEFAULT_MOBILE_SETTLE_MS : DEFAULT_SETTLE_MS;
913
- const requestedSettleMs = Math.max(0, Number(options.settleMs ?? defaultSettleMs) || 0);
914
- const settleMs = preserveViewport ? Math.min(requestedSettleMs, DEFAULT_MOBILE_SETTLE_MS) : requestedSettleMs;
915
- const maxScrollHeight = await expandScrollableContent(page, {
916
- forceScrollableHeight: options.forceScrollableHeight,
917
- visibleOnly: options.visibleOnly !== false,
918
- expandDocumentElements: preserveViewport
919
- });
920
- const targetHeight = Math.min(maxScrollHeight, maxHeight);
921
- let viewportResized = false;
922
- if (!preserveViewport) {
923
- await page.setViewportSize({
924
- width: originalViewport.width,
925
- height: targetHeight
1011
+ const settleMs = Math.max(0, Number(options.settleMs ?? DEFAULT_SETTLE_MS) || 0);
1012
+ const frames = typeof page.frames === "function" ? page.frames() : [page.mainFrame?.()].filter(Boolean);
1013
+ const mainFrame = page.mainFrame?.() || frames[0] || null;
1014
+ let targetHeight = 0;
1015
+ let scrollableCount = 0;
1016
+ let expandedCount = 0;
1017
+ for (const frame of frames) {
1018
+ if (!frame || frame === mainFrame) continue;
1019
+ const state2 = await expandScrollableContentInContext(frame, {
1020
+ ...options,
1021
+ maxHeight,
1022
+ settleMs
926
1023
  });
927
- viewportResized = true;
928
- } else {
929
- await adjustAffixedElementsForExpandedScreenshot(page, { targetHeight }).catch((error) => {
930
- logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u91CD\u5B9A\u4F4D\u5931\u8D25: ${error?.message || error}`);
1024
+ if (state2.height > 0) {
1025
+ await expandFrameElement(frame, Math.min(state2.height, maxHeight)).catch(() => false);
1026
+ await resetScrollPositionInContext(frame);
1027
+ }
1028
+ targetHeight = Math.max(targetHeight, state2.height || 0);
1029
+ scrollableCount += state2.scrollableCount || 0;
1030
+ expandedCount += state2.expandedCount || 0;
1031
+ }
1032
+ if (mainFrame) {
1033
+ const state2 = await expandScrollableContentInContext(mainFrame, {
1034
+ ...options,
1035
+ maxHeight,
1036
+ settleMs
931
1037
  });
932
- }
933
- if (settleMs > 0) {
934
- await (0, import_delay.default)(settleMs);
935
- }
1038
+ targetHeight = Math.max(targetHeight, state2.height || 0);
1039
+ scrollableCount += state2.scrollableCount || 0;
1040
+ expandedCount += state2.expandedCount || 0;
1041
+ await resetScrollPositionInContext(mainFrame);
1042
+ }
1043
+ targetHeight = Math.min(Math.max(1, Math.ceil(targetHeight || 0)), maxHeight);
1044
+ if (settleMs > 0) await (0, import_delay.default)(settleMs);
1045
+ logger.info(`\u5C55\u5F00\u5F0F\u957F\u622A\u56FE\u76EE\u6807: height=${targetHeight}, scrollables=${scrollableCount}, expanded=${expandedCount}`);
936
1046
  return {
937
- originalViewport,
938
- maxScrollHeight,
939
1047
  targetHeight,
940
- preserveViewport,
941
- viewportResized
1048
+ frames,
1049
+ settleMs
942
1050
  };
943
1051
  };
944
1052
  var restoreExpandedFullPageScreenshot = async (page, state2 = {}) => {
945
- await page.evaluate((className) => {
946
- const targets = new Set([
947
- ...document.querySelectorAll(`.${className}`),
948
- document.documentElement,
949
- document.body,
950
- document.scrollingElement
951
- ].filter((el) => el?.classList?.contains(className)));
952
- targets.forEach((el) => {
953
- el.style.setProperty("overflow", el.dataset.pkOrigOverflow || "", el.dataset.pkOrigOverflowPriority || "");
954
- el.style.setProperty("height", el.dataset.pkOrigHeight || "", el.dataset.pkOrigHeightPriority || "");
955
- el.style.setProperty("min-height", el.dataset.pkOrigMinHeight || "", el.dataset.pkOrigMinHeightPriority || "");
956
- el.style.setProperty("max-height", el.dataset.pkOrigMaxHeight || "", el.dataset.pkOrigMaxHeightPriority || "");
957
- delete el.dataset.pkOrigOverflow;
958
- delete el.dataset.pkOrigOverflowPriority;
959
- delete el.dataset.pkOrigHeight;
960
- delete el.dataset.pkOrigHeightPriority;
961
- delete el.dataset.pkOrigMinHeight;
962
- delete el.dataset.pkOrigMinHeightPriority;
963
- delete el.dataset.pkOrigMaxHeight;
964
- delete el.dataset.pkOrigMaxHeightPriority;
965
- el.classList.remove(className);
966
- });
967
- }, EXPANDED_SCROLLABLE_CLASS);
968
- if (state2?.originalViewport && state2?.viewportResized) {
969
- await page.setViewportSize(state2.originalViewport);
1053
+ const frames = Array.isArray(state2.frames) ? [...state2.frames].reverse() : typeof page.frames === "function" ? [...page.frames()].reverse() : [];
1054
+ for (const frame of frames) {
1055
+ await restoreExpandedStateInContext(frame);
970
1056
  }
971
1057
  };
972
- var capturePageScreenshot = async (page, options = {}) => {
973
- const fullPage = Boolean(options.fullPage);
974
- const type = fullPage ? FORCED_FULLPAGE_TYPE : normalizeType(options.type);
975
- const quality = fullPage ? FORCED_FULLPAGE_QUALITY : normalizeQuality(options.quality, type);
976
- const timeout = toPositiveNumber(options.timeout, DEFAULT_TIMEOUT_MS);
977
- const maxClipHeight = Math.round(toPositiveNumber(options.maxClipHeight, 0));
978
- const fallbackOptions = {
979
- type: type === "webp" ? "png" : type,
980
- fullPage
981
- };
982
- if (quality !== void 0 && fallbackOptions.type === "jpeg") {
983
- fallbackOptions.quality = quality;
984
- }
985
- if (timeout > 0) {
986
- fallbackOptions.timeout = timeout;
987
- }
1058
+ var captureExpandedFullPageScreenshotOnce = async (page, options = {}) => {
1059
+ const originalViewport = await resolveCurrentViewportSize(page).catch(() => null);
1060
+ const originalMainScroll = await readMainFrameScroll(page);
1061
+ let state2 = null;
1062
+ let viewportResized = false;
988
1063
  try {
989
- const context = page && typeof page.context === "function" ? page.context() : null;
990
- if (!context || typeof context.newCDPSession !== "function") {
991
- throw new Error("CDP session is not available");
992
- }
993
- const session = await context.newCDPSession(page);
994
- try {
995
- const metrics = await session.send("Page.getLayoutMetrics");
996
- const viewport = await resolveCurrentViewportSize(page);
997
- const captureParams = {
998
- format: type,
999
- fromSurface: true,
1000
- captureBeyondViewport: fullPage,
1001
- optimizeForSpeed: true
1002
- };
1003
- if (quality !== void 0) {
1004
- captureParams.quality = quality;
1005
- }
1006
- if (fullPage) {
1007
- captureParams.clip = buildFullPageClip(metrics, viewport, maxClipHeight);
1008
- }
1009
- const result = await session.send("Page.captureScreenshot", captureParams);
1010
- if (!result || typeof result.data !== "string" || !result.data) {
1011
- throw new Error("CDP returned empty screenshot data");
1012
- }
1013
- return Buffer.from(result.data, "base64");
1014
- } finally {
1015
- if (typeof session.detach === "function") {
1016
- await session.detach().catch(() => {
1017
- });
1018
- }
1064
+ await freezeViewportMetrics(page);
1065
+ state2 = await prepareExpandedFullPageScreenshot(page, options);
1066
+ const targetHeight = Math.max(1, Math.ceil(state2.targetHeight || options.maxHeight || DEFAULT_MAX_HEIGHT));
1067
+ if (originalViewport?.width && originalViewport?.height && targetHeight > Math.ceil(originalViewport.height) + 1) {
1068
+ await page.setViewportSize({
1069
+ width: Math.ceil(originalViewport.width),
1070
+ height: targetHeight
1071
+ });
1072
+ viewportResized = true;
1073
+ if (state2.settleMs > 0) await (0, import_delay.default)(state2.settleMs);
1019
1074
  }
1020
- } catch (error) {
1021
- logger.warning(`CDP \u622A\u56FE\u5931\u8D25\uFF0C\u56DE\u9000 page.screenshot: ${error?.message || error}`);
1022
- return await page.screenshot(fallbackOptions);
1023
- }
1024
- };
1025
- var captureExpandedFullPageScreenshot = async (page, options = {}) => {
1026
- const state2 = await prepareExpandedFullPageScreenshot(page, options);
1027
- try {
1028
1075
  return await capturePageScreenshot(page, {
1076
+ ...options,
1029
1077
  fullPage: true,
1030
- type: options.type || "png",
1031
- quality: options.quality,
1032
- timeout: options.timeout,
1033
- maxClipHeight: state2.targetHeight
1078
+ maxClipHeight: targetHeight
1034
1079
  });
1035
1080
  } finally {
1036
- await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
1037
- logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
1038
- });
1039
1081
  if (options.restore) {
1040
1082
  await restoreExpandedFullPageScreenshot(page, state2);
1083
+ if (viewportResized && originalViewport?.width && originalViewport?.height) {
1084
+ await page.setViewportSize({
1085
+ width: Math.ceil(originalViewport.width),
1086
+ height: Math.ceil(originalViewport.height)
1087
+ }).catch((error) => {
1088
+ logger.warning(`\u6062\u590D viewport \u5931\u8D25: ${error?.message || error}`);
1089
+ });
1090
+ }
1041
1091
  }
1092
+ await restoreViewportMetrics(page);
1093
+ if (options.restore) {
1094
+ await restoreMainFrameScroll(page, originalMainScroll);
1095
+ }
1096
+ }
1097
+ };
1098
+ var isDarkBlankPixel = ({ r, g, b }) => {
1099
+ const max = Math.max(r, g, b);
1100
+ const min = Math.min(r, g, b);
1101
+ return max <= 34 && max - min <= 10;
1102
+ };
1103
+ var analyzeScreenshotBuffer = async (buffer) => {
1104
+ const image = await import_jimp.Jimp.read(buffer);
1105
+ const { width, height } = image.bitmap;
1106
+ const stepY = Math.max(1, Math.floor(height / 900));
1107
+ const stepX = Math.max(1, Math.floor(width / 420));
1108
+ let darkRows = 0;
1109
+ let loadingLikeRows = 0;
1110
+ let rows = 0;
1111
+ for (let y = 0; y < height; y += stepY) {
1112
+ rows += 1;
1113
+ let dark = 0;
1114
+ let edge = 0;
1115
+ let count = 0;
1116
+ let prev = null;
1117
+ for (let x = 0; x < width; x += stepX) {
1118
+ const rgba = (0, import_jimp.intToRGBA)(image.getPixelColor(x, y));
1119
+ count += 1;
1120
+ if (isDarkBlankPixel(rgba)) dark += 1;
1121
+ if (prev) {
1122
+ const diff = Math.abs(rgba.r - prev.r) + Math.abs(rgba.g - prev.g) + Math.abs(rgba.b - prev.b);
1123
+ if (diff > 45) edge += 1;
1124
+ }
1125
+ prev = rgba;
1126
+ }
1127
+ const darkRatio = dark / Math.max(1, count);
1128
+ const edgeRatio = edge / Math.max(1, count - 1);
1129
+ if (darkRatio >= 0.965 && edgeRatio <= 0.012) darkRows += 1;
1130
+ if (darkRatio >= 0.88 && edgeRatio <= 0.025) loadingLikeRows += 1;
1131
+ }
1132
+ const sampledRows = Math.max(1, rows);
1133
+ return {
1134
+ width,
1135
+ height,
1136
+ darkBlankRowsRatio: darkRows / sampledRows,
1137
+ loadingLikeRowsRatio: loadingLikeRows / sampledRows
1138
+ };
1139
+ };
1140
+ var resolveScreenshotQualityIssue = (analysis) => {
1141
+ if (!analysis) return null;
1142
+ if (analysis.loadingLikeRowsRatio >= 0.92 || analysis.darkBlankRowsRatio >= 0.72) {
1143
+ return "loading-like-dark-screenshot";
1042
1144
  }
1145
+ return null;
1146
+ };
1147
+ var captureExpandedFullPageScreenshot = async (page, options = {}) => {
1148
+ const attempts = Math.max(1, toPositiveInteger(options.qualityRetryAttempts, DEFAULT_QUALITY_RETRY_ATTEMPTS));
1149
+ const retryDelayMs = Math.max(0, Number(options.qualityRetryDelayMs ?? DEFAULT_QUALITY_RETRY_DELAY_MS) || 0);
1150
+ let lastBuffer = null;
1151
+ let lastAnalysis = null;
1152
+ let lastIssue = null;
1153
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
1154
+ const buffer = await captureExpandedFullPageScreenshotOnce(page, options);
1155
+ const analysis = await analyzeScreenshotBuffer(buffer).catch((error) => {
1156
+ logger.warning(`\u622A\u56FE\u8D28\u91CF\u5206\u6790\u5931\u8D25: ${error?.message || error}`);
1157
+ return null;
1158
+ });
1159
+ const issue = resolveScreenshotQualityIssue(analysis);
1160
+ lastBuffer = buffer;
1161
+ lastAnalysis = analysis;
1162
+ lastIssue = issue;
1163
+ if (!issue) return buffer;
1164
+ if (attempt < attempts && retryDelayMs > 0) {
1165
+ logger.warning(`\u622A\u56FE\u7591\u4F3C\u5F02\u5E38(${issue})\uFF0C\u7B49\u5F85\u540E\u91CD\u8BD5: attempt=${attempt}/${attempts}`);
1166
+ await (0, import_delay.default)(retryDelayMs);
1167
+ }
1168
+ }
1169
+ if (lastIssue && lastAnalysis) {
1170
+ logger.warning(
1171
+ `\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)}`
1172
+ );
1173
+ }
1174
+ return lastBuffer;
1043
1175
  };
1044
1176
 
1045
1177
  // src/errors.js
@@ -1489,6 +1621,9 @@ var ProxyMeterRuntime = {
1489
1621
  getProxyMeterSnapshot
1490
1622
  };
1491
1623
 
1624
+ // src/internals/constants.js
1625
+ var PageRuntimeStateKey = "__playwright_toolkit_runtime_state__";
1626
+
1492
1627
  // src/runtime-env.js
1493
1628
  var BROWSER_PROFILE_SCHEMA_VERSION = 1;
1494
1629
  var SUPPORTED_CLOAK_FINGERPRINT_PLATFORMS = /* @__PURE__ */ new Set(["linux", "macos", "windows"]);
@@ -9068,11 +9203,11 @@ var resolveScreenshotWatermarkifyMeta = async (page, options = {}) => {
9068
9203
  watermark: options.watermark,
9069
9204
  strip: options.strip,
9070
9205
  stripLogoSrc,
9071
- device: resolvePageDevice2(page)
9206
+ device: resolvePageDevice(page)
9072
9207
  };
9073
9208
  };
9074
9209
  var buildFontFamily = () => 'MiSans, "SF Pro Display", "PingFang SC", "Helvetica Neue", Arial, sans-serif';
9075
- var resolvePageDevice2 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
9210
+ var resolvePageDevice = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
9076
9211
  var findStripSegment = (segments, kind) => {
9077
9212
  const found = Array.isArray(segments) ? segments.find((segment) => segment?.kind === kind) : null;
9078
9213
  return found && typeof found === "object" ? found : {};
@@ -9724,7 +9859,7 @@ var watermarkifyScreenshotBuffer = async (buffer, meta, page = null, options = {
9724
9859
  };
9725
9860
 
9726
9861
  // src/internals/compression.js
9727
- var import_jimp = require("jimp");
9862
+ var import_jimp2 = require("jimp");
9728
9863
  var logger15 = createInternalLogger("Compression");
9729
9864
  var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
9730
9865
  var DEFAULT_SCREENSHOT_OUTPUT_TYPE = "jpeg";
@@ -9791,10 +9926,10 @@ var encodeJpeg = async (sourceImage, compression, scale, quality) => {
9791
9926
  image.resize({
9792
9927
  w: width,
9793
9928
  h: height,
9794
- mode: import_jimp.ResizeStrategy.BILINEAR
9929
+ mode: import_jimp2.ResizeStrategy.BILINEAR
9795
9930
  });
9796
9931
  }
9797
- const buffer = await image.getBuffer(import_jimp.JimpMime.jpeg, { quality });
9932
+ const buffer = await image.getBuffer(import_jimp2.JimpMime.jpeg, { quality });
9798
9933
  return {
9799
9934
  buffer,
9800
9935
  bytes: getBase64BytesFromBuffer(buffer),
@@ -9806,7 +9941,7 @@ var encodeJpeg = async (sourceImage, compression, scale, quality) => {
9806
9941
  };
9807
9942
  };
9808
9943
  var compressImageBuffer = async (buffer, compression) => {
9809
- const sourceImage = await import_jimp.Jimp.read(buffer);
9944
+ const sourceImage = await import_jimp2.Jimp.read(buffer);
9810
9945
  const maxQuality = toJpegQuality(compression.quality);
9811
9946
  const minQuality = Math.min(maxQuality, toJpegQuality(compression.minQuality));
9812
9947
  let quality = maxQuality;