@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.js CHANGED
@@ -202,7 +202,7 @@ var ActorInfo = {
202
202
  mode: "response",
203
203
  prefix: "https://www.doubao.com/thread/",
204
204
  xurl: [
205
- "/samantha/thread/share/save",
205
+ "/share/save",
206
206
  "data",
207
207
  "share_id"
208
208
  ]
@@ -229,7 +229,6 @@ var ActorInfo = {
229
229
  name: "\u6587\u5FC3\u4E00\u8A00",
230
230
  domain: "wenxin.baidu.com",
231
231
  path: "/",
232
- device: Device.Mobile,
233
232
  share: {
234
233
  mode: "custom",
235
234
  prefix: "",
@@ -468,9 +467,7 @@ function createInternalLogger(moduleName, explicitLogger) {
468
467
 
469
468
  // src/internals/screenshot.js
470
469
  import delay from "delay";
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,21 +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
502
  var DEFAULT_MAX_HEIGHT = 8e3;
510
- var DEFAULT_SETTLE_MS = 1e3;
511
- var DEFAULT_MOBILE_SETTLE_MS = 50;
503
+ var DEFAULT_SETTLE_MS = 260;
504
+ var DEFAULT_QUALITY_RETRY_ATTEMPTS = 4;
505
+ var DEFAULT_QUALITY_RETRY_DELAY_MS = 2500;
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;
512
510
  var toPositiveNumber = (value, fallback = 0) => {
513
511
  const n = Number(value);
514
512
  if (!Number.isFinite(n) || n <= 0) return fallback;
515
513
  return n;
516
514
  };
517
515
  var toPositiveInteger = (value, fallback = 0) => {
518
- const number = Math.round(Number(value) || 0);
519
- return number > 0 ? number : fallback;
516
+ const n = Math.round(Number(value) || 0);
517
+ return n > 0 ? n : fallback;
520
518
  };
521
519
  var normalizeType = (value) => {
522
520
  const raw = String(value || "png").trim().toLowerCase();
@@ -531,488 +529,622 @@ var normalizeQuality = (value, type) => {
531
529
  if (rounded < 0 || rounded > 100) return void 0;
532
530
  return rounded;
533
531
  };
534
- var resolvePageDevice = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
535
532
  var buildFullPageClip = (metrics, viewport, maxClipHeight) => {
536
533
  const contentSize = metrics && typeof metrics === "object" ? metrics.contentSize || null : null;
537
534
  const width = Math.max(1, Math.ceil(viewport.width || contentSize?.width || 1));
538
535
  let height = Math.max(1, Math.ceil(contentSize?.height || viewport.height || 1));
539
- if (maxClipHeight > 0) {
540
- height = Math.min(height, maxClipHeight);
541
- }
542
- return {
543
- x: 0,
544
- y: 0,
545
- width,
546
- height,
547
- scale: 1
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
548
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
+ }
549
585
  };
550
- var expandScrollableContent = async (page, options = {}) => {
551
- return await page.evaluate(({ className, forceScrollableHeight, visibleOnly, expandDocumentElements }) => {
552
- const body = document.body;
553
- const root = document.documentElement;
554
- const scrollingElement = document.scrollingElement;
555
- const candidates = [];
556
- const seen = /* @__PURE__ */ new Set();
557
- let maxHeight = window.innerHeight || 0;
558
- const pushCandidate = (el) => {
559
- if (!el || seen.has(el)) return;
560
- seen.add(el);
561
- candidates.push(el);
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
+ }
562
625
  };
563
- pushCandidate(scrollingElement);
564
- pushCandidate(root);
565
- pushCandidate(body);
566
- document.querySelectorAll("*").forEach(pushCandidate);
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;
567
674
  const scrollableOverflow = /* @__PURE__ */ new Set(["auto", "scroll", "overlay"]);
568
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);
569
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) || "";
570
689
  const isVisible = (el, style, rect) => {
571
690
  if (!el || !style || !rect) return false;
572
- if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") {
573
- return false;
574
- }
575
- 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;
576
693
  return rect.width > 0 && rect.height > 0;
577
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);
578
700
  const hasOverflowValue = (style, values) => {
579
- const overflowY = String(style?.overflowY || "").toLowerCase();
580
701
  const overflow = String(style?.overflow || "").toLowerCase();
581
- return values.has(overflowY) || values.has(overflow);
702
+ const overflowY = String(style?.overflowY || "").toLowerCase();
703
+ return values.has(overflow) || values.has(overflowY);
582
704
  };
583
705
  const isLargeVerticalClipper = (el, rect) => {
584
706
  if (!el || !rect) return false;
585
- const viewportHeight = window.innerHeight || 0;
586
- const visibleHeight = Math.ceil(rect.height || 0);
587
- const clientHeight = Math.ceil(el.clientHeight || 0);
588
- const boxHeight = Math.max(visibleHeight, clientHeight);
707
+ const boxHeight = Math.max(Math.ceil(rect.height || 0), Math.ceil(el.clientHeight || 0));
589
708
  return boxHeight >= Math.max(320, viewportHeight * 0.45);
590
709
  };
591
- const hasLargeVerticalOverflow = (el, rect) => {
592
- if (!isLargeVerticalClipper(el, rect)) return false;
593
- const viewportHeight = window.innerHeight || 0;
594
- const scrollHeight = Math.ceil(el.scrollHeight || 0);
595
- const clientHeight = Math.ceil(el.clientHeight || 0);
596
- const overflowDelta = scrollHeight - clientHeight;
597
- return overflowDelta >= Math.max(160, viewportHeight * 0.18);
598
- };
599
- const rememberOriginalBoxStyles = (el, { includeDocumentElement = false } = {}) => {
600
- if (!el || !includeDocumentElement && isDocumentElement(el) || el.classList.contains(className)) {
601
- return;
602
- }
603
- el.dataset.pkOrigOverflow = el.style.overflow;
604
- el.dataset.pkOrigOverflowPriority = el.style.getPropertyPriority("overflow") || "";
605
- el.dataset.pkOrigHeight = el.style.height;
606
- el.dataset.pkOrigHeightPriority = el.style.getPropertyPriority("height") || "";
607
- el.dataset.pkOrigMinHeight = el.style.minHeight;
608
- el.dataset.pkOrigMinHeightPriority = el.style.getPropertyPriority("min-height") || "";
609
- el.dataset.pkOrigMaxHeight = el.style.maxHeight;
610
- el.dataset.pkOrigMaxHeightPriority = el.style.getPropertyPriority("max-height") || "";
611
- el.classList.add(className);
612
- };
613
- const expandDocumentElementsToHeight = (height) => {
614
- if (!expandDocumentElements) return;
615
- const targetHeight = Math.ceil(Number(height) || 0);
616
- if (targetHeight <= 0) return;
617
- [root, body, scrollingElement].forEach((el) => {
618
- if (!el) return;
619
- rememberOriginalBoxStyles(el, { includeDocumentElement: true });
620
- el.style.setProperty("overflow", "visible", "important");
621
- el.style.setProperty("height", `${targetHeight}px`, "important");
622
- el.style.setProperty("min-height", `${targetHeight}px`, "important");
623
- el.style.setProperty("max-height", "none", "important");
624
- });
625
- };
626
710
  const isScrollableY = (el, style, rect) => {
627
- if (!el || el.scrollHeight <= el.clientHeight + 1) {
628
- return false;
629
- }
630
- if (isDocumentElement(el)) {
631
- return true;
632
- }
633
- if (hasOverflowValue(style, scrollableOverflow)) {
634
- return true;
635
- }
636
- 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);
637
717
  };
638
- const measureNeededHeight = (el) => {
639
- if (!el) return 0;
640
- const scrollHeight = Math.ceil(el.scrollHeight || 0);
641
- if (scrollHeight <= 0) return 0;
642
- if (isDocumentElement(el)) {
643
- return scrollHeight;
644
- }
645
- const rect = el.getBoundingClientRect();
646
- if (!rect || rect.width <= 0 || rect.height <= 0) {
647
- return 0;
648
- }
649
- const documentTop = Math.max(0, Math.ceil(rect.top + (window.scrollY || window.pageYOffset || 0)));
650
- return documentTop + scrollHeight;
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);
651
744
  };
652
- const scrollableElements = [];
653
- const expandedAncestors = [];
654
- const expandElementToScrollHeight = (el) => {
655
- if (!el || isDocumentElement(el)) return;
656
- const scrollHeight = Math.ceil(el.scrollHeight || 0);
657
- if (scrollHeight <= 0) return;
658
- rememberOriginalBoxStyles(el);
745
+ const setExpandedBox = (el, { height, width }) => {
746
+ if (!el) return;
747
+ rememberBox(el);
659
748
  el.style.setProperty("overflow", "visible", "important");
660
- el.style.setProperty("height", `${scrollHeight}px`, "important");
661
- el.style.setProperty("min-height", `${scrollHeight}px`, "important");
749
+ el.style.setProperty("overflow-y", "visible", "important");
662
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;
663
764
  };
664
765
  const expandClippingAncestors = (el) => {
665
766
  for (let node = el?.parentElement; node && !isDocumentElement(node); node = node.parentElement) {
666
767
  const style = window.getComputedStyle(node);
667
768
  const rect = node.getBoundingClientRect();
668
769
  if (!isVisible(node, style, rect)) continue;
669
- const scrollableClip = hasOverflowValue(style, scrollableOverflow);
670
- const visualClip = hasOverflowValue(style, clippingOverflow);
671
- if (!scrollableClip && (!visualClip || !isLargeVerticalClipper(node, rect))) continue;
672
- rememberOriginalBoxStyles(node);
770
+ const clips = hasOverflowValue(style, scrollableOverflow) || hasOverflowValue(style, clippingOverflow);
771
+ if (!clips) continue;
772
+ rememberBox(node);
673
773
  node.style.setProperty("overflow", "visible", "important");
774
+ node.style.setProperty("overflow-y", "visible", "important");
674
775
  node.style.setProperty("max-height", "none", "important");
675
- expandedAncestors.push(node);
676
776
  }
677
777
  };
678
- const expandAncestorsToCurrentScrollHeight = () => {
679
- const ancestors = [...new Set(expandedAncestors)];
680
- for (let pass = 0; pass < 2; pass += 1) {
681
- ancestors.forEach((node) => {
682
- if (!node || isDocumentElement(node)) return;
683
- const scrollHeight = Math.ceil(node.scrollHeight || 0);
684
- if (scrollHeight <= node.clientHeight + 1) return;
685
- rememberOriginalBoxStyles(node);
686
- node.style.setProperty("height", `${scrollHeight}px`, "important");
687
- node.style.setProperty("min-height", `${scrollHeight}px`, "important");
688
- node.style.setProperty("max-height", "none", "important");
689
- });
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;
690
813
  }
691
- };
692
- const measureExpandedRenderedBottom = () => {
693
- let renderedBottom = maxHeight;
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;
694
839
  const scrollY = window.scrollY || window.pageYOffset || 0;
695
- candidates.forEach((el) => {
840
+ document.querySelectorAll(`[${expandedAttr}="1"]`).forEach((el) => {
696
841
  if (!el || isDocumentElement(el)) return;
697
- if (!el.classList.contains(className) && !el.closest(`.${className}`)) return;
698
- const style = window.getComputedStyle(el);
699
842
  const rect = el.getBoundingClientRect();
700
- if (!isVisible(el, style, rect)) return;
701
- renderedBottom = Math.max(renderedBottom, Math.ceil(rect.bottom + scrollY));
843
+ if (!rect || rect.width <= 0 || rect.height <= 0) return;
844
+ bottom = Math.max(bottom, Math.ceil(rect.bottom + scrollY));
702
845
  });
703
- return renderedBottom;
704
- };
705
- const measureDocumentScrollHeight = () => {
706
- return Math.max(
707
- maxHeight,
708
- Math.ceil(root?.scrollHeight || 0),
709
- Math.ceil(body?.scrollHeight || 0),
710
- Math.ceil(scrollingElement?.scrollHeight || 0)
711
- );
846
+ return bottom;
712
847
  };
713
- candidates.forEach((el) => {
714
- const style = window.getComputedStyle(el);
715
- const rect = el.getBoundingClientRect();
716
- if (visibleOnly && !isVisible(el, style, rect)) {
717
- return;
718
- }
719
- if (!isScrollableY(el, style, rect)) {
720
- return;
721
- }
722
- const neededHeight = measureNeededHeight(el);
723
- if (neededHeight <= 0) {
724
- return;
725
- }
726
- if (neededHeight > maxHeight) {
727
- maxHeight = neededHeight;
728
- }
729
- scrollableElements.push(el);
730
- if (isDocumentElement(el)) {
731
- return;
732
- }
733
- expandClippingAncestors(el);
734
- if (forceScrollableHeight) {
735
- expandElementToScrollHeight(el);
736
- return;
737
- }
738
- rememberOriginalBoxStyles(el);
739
- el.style.overflow = "visible";
740
- el.style.height = "auto";
741
- 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 });
742
853
  });
743
- expandAncestorsToCurrentScrollHeight();
744
- scrollableElements.forEach((el) => {
745
- const neededHeight = measureNeededHeight(el);
746
- if (neededHeight > maxHeight) {
747
- maxHeight = neededHeight;
854
+ window.scrollTo(0, 0);
855
+ document.querySelectorAll(`[${expandedAttr}="1"]`).forEach((el) => {
856
+ if (!isDocumentElement(el)) {
857
+ el.scrollTop = 0;
858
+ el.scrollLeft = 0;
748
859
  }
749
860
  });
750
- maxHeight = Math.max(maxHeight, measureDocumentScrollHeight(), measureExpandedRenderedBottom());
751
- if (expandDocumentElements) {
752
- for (let pass = 0; pass < 2; pass += 1) {
753
- expandDocumentElementsToHeight(maxHeight);
754
- maxHeight = Math.max(maxHeight, measureDocumentScrollHeight(), measureExpandedRenderedBottom());
755
- }
756
- }
757
- 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
+ };
758
868
  }, {
759
- className: EXPANDED_SCROLLABLE_CLASS,
760
- forceScrollableHeight: options.forceScrollableHeight !== false,
761
- visibleOnly: options.visibleOnly !== false,
762
- expandDocumentElements: options.expandDocumentElements === true
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 };
763
877
  });
764
878
  };
765
- var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
766
- return await page.evaluate(({ className, targetHeight }) => {
767
- const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || document.body?.clientWidth || 0;
768
- const viewportHeight = window.innerHeight || document.documentElement?.clientHeight || document.body?.clientHeight || 0;
769
- const scrollY = window.scrollY || window.pageYOffset || 0;
770
- const safeTargetHeight = Math.ceil(Number(targetHeight) || 0);
771
- if (safeTargetHeight <= viewportHeight + 1) {
772
- return 0;
773
- }
774
- const hasOwn2 = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
775
- const isVisible = (el, style, rect) => {
776
- if (!el || !style || !rect) return false;
777
- if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") {
778
- return false;
779
- }
780
- if (Number(style.opacity) === 0) return false;
781
- return rect.width > 0 && rect.height > 0;
782
- };
783
- const edgeThreshold = Math.max(24, Math.min(96, viewportHeight * 0.12));
784
- const maxAffixedHeight = Math.max(56, viewportHeight * 0.65);
785
- const candidates = [];
786
- document.querySelectorAll("*").forEach((el) => {
787
- const style = window.getComputedStyle(el);
788
- const position = String(style.position || "").toLowerCase();
789
- if (position !== "fixed" && position !== "sticky") return;
790
- const rect = el.getBoundingClientRect();
791
- if (!isVisible(el, style, rect)) return;
792
- if (rect.height > maxAffixedHeight) return;
793
- if (rect.width < viewportWidth * 0.25 && rect.height < 80) return;
794
- const top = Number.parseFloat(style.top);
795
- const bottom = Number.parseFloat(style.bottom);
796
- const hasTopRule = Number.isFinite(top) && top <= Math.max(160, viewportHeight * 0.25);
797
- const hasBottomRule = Number.isFinite(bottom) && bottom <= Math.max(160, viewportHeight * 0.25);
798
- const isNearViewportTop = rect.top <= edgeThreshold;
799
- const isNearViewportBottom = rect.bottom >= viewportHeight - edgeThreshold;
800
- const edge = (hasBottomRule || isNearViewportBottom) && !(hasTopRule || isNearViewportTop) ? "bottom" : "top";
801
- if (position === "fixed" && edge === "top" && !hasTopRule && !isNearViewportTop) return;
802
- if (position === "fixed" && edge === "bottom" && !hasBottomRule && !isNearViewportBottom) return;
803
- if (position === "sticky" && !hasTopRule && !hasBottomRule && !isNearViewportTop && !isNearViewportBottom) return;
804
- candidates.push({ el, position, edge });
805
- });
806
- const candidateSet = new Set(candidates.map(({ el }) => el));
807
- const topLevelCandidates = candidates.filter(({ el }) => {
808
- for (let parent = el.parentElement; parent; parent = parent.parentElement) {
809
- 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)));
810
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");
811
917
  return true;
918
+ }, { expandedAttr: EXPANDED_ATTR, targetHeight: Math.ceil(height) });
919
+ } finally {
920
+ await handle.dispose?.().catch(() => {
812
921
  });
813
- topLevelCandidates.forEach(({ el, position, edge }) => {
814
- if (!hasOwn2(el.dataset, "pkAffixedAdjusted")) {
815
- el.dataset.pkAffixedAdjusted = "1";
816
- el.dataset.pkOrigPosition = el.style.getPropertyValue("position") || "";
817
- el.dataset.pkOrigPositionPriority = el.style.getPropertyPriority("position") || "";
818
- el.dataset.pkOrigTop = el.style.getPropertyValue("top") || "";
819
- el.dataset.pkOrigTopPriority = el.style.getPropertyPriority("top") || "";
820
- el.dataset.pkOrigBottom = el.style.getPropertyValue("bottom") || "";
821
- el.dataset.pkOrigBottomPriority = el.style.getPropertyPriority("bottom") || "";
822
- el.dataset.pkOrigTranslate = el.style.getPropertyValue("translate") || "";
823
- el.dataset.pkOrigTranslatePriority = el.style.getPropertyPriority("translate") || "";
824
- }
825
- el.classList.add(className);
826
- if (position === "fixed") {
827
- const deltaY = edge === "bottom" ? safeTargetHeight - viewportHeight - scrollY : -scrollY;
828
- if (Math.abs(deltaY) > 1) {
829
- el.style.setProperty("translate", `0 ${Math.round(deltaY)}px`, "important");
830
- }
831
- return;
922
+ }
923
+ };
924
+ var restoreExpandedStateInContext = async (context) => {
925
+ await context.evaluate(({ expandedAttr }) => {
926
+ const hasOwn2 = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
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);
832
935
  }
833
- el.style.setProperty("position", "relative", "important");
834
- el.style.setProperty("top", "auto", "important");
835
- el.style.setProperty("bottom", "auto", "important");
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);
836
959
  });
837
- return topLevelCandidates.length;
960
+ window.dispatchEvent(new Event("scroll"));
838
961
  }, {
839
- className: EXPANDED_SCROLLABLE_CLASS,
840
- targetHeight: options.targetHeight
962
+ expandedAttr: EXPANDED_ATTR
963
+ }).catch(() => {
841
964
  });
842
965
  };
843
- var restoreAffixedElementsForExpandedScreenshot = async (page) => {
844
- await page.evaluate((className) => {
845
- const hasOwn2 = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
846
- const expansionKeys = [
847
- "pkOrigOverflow",
848
- "pkOrigHeight",
849
- "pkOrigMinHeight",
850
- "pkOrigMaxHeight"
851
- ];
852
- document.querySelectorAll('[data-pk-affixed-adjusted="1"]').forEach((el) => {
853
- if (hasOwn2(el.dataset, "pkOrigPosition")) {
854
- el.style.setProperty("position", el.dataset.pkOrigPosition || "", el.dataset.pkOrigPositionPriority || "");
855
- delete el.dataset.pkOrigPosition;
856
- delete el.dataset.pkOrigPositionPriority;
857
- }
858
- if (hasOwn2(el.dataset, "pkOrigTop")) {
859
- el.style.setProperty("top", el.dataset.pkOrigTop || "", el.dataset.pkOrigTopPriority || "");
860
- delete el.dataset.pkOrigTop;
861
- delete el.dataset.pkOrigTopPriority;
862
- }
863
- if (hasOwn2(el.dataset, "pkOrigBottom")) {
864
- el.style.setProperty("bottom", el.dataset.pkOrigBottom || "", el.dataset.pkOrigBottomPriority || "");
865
- delete el.dataset.pkOrigBottom;
866
- delete el.dataset.pkOrigBottomPriority;
867
- }
868
- if (hasOwn2(el.dataset, "pkOrigTranslate")) {
869
- el.style.setProperty("translate", el.dataset.pkOrigTranslate || "", el.dataset.pkOrigTranslatePriority || "");
870
- delete el.dataset.pkOrigTranslate;
871
- delete el.dataset.pkOrigTranslatePriority;
872
- }
873
- delete el.dataset.pkAffixedAdjusted;
874
- const stillExpanded = expansionKeys.some((key) => hasOwn2(el.dataset, key));
875
- if (!stillExpanded) {
876
- el.classList.remove(className);
877
- }
966
+ var resetScrollPositionInContext = async (context) => {
967
+ await context.evaluate(() => {
968
+ const root = document.documentElement;
969
+ const body = document.body;
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 }));
878
977
  });
879
- }, EXPANDED_SCROLLABLE_CLASS);
978
+ window.dispatchEvent(new Event("scroll"));
979
+ }).catch(() => {
980
+ });
880
981
  };
881
982
  var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
882
- const originalViewport = await resolveCurrentViewportSize(page);
883
983
  const maxHeight = toPositiveInteger(options.maxHeight, DEFAULT_MAX_HEIGHT);
884
- const preserveViewport = options.preserveViewport ?? resolvePageDevice(page) === Device.Mobile;
885
- const defaultSettleMs = preserveViewport ? DEFAULT_MOBILE_SETTLE_MS : DEFAULT_SETTLE_MS;
886
- const requestedSettleMs = Math.max(0, Number(options.settleMs ?? defaultSettleMs) || 0);
887
- const settleMs = preserveViewport ? Math.min(requestedSettleMs, DEFAULT_MOBILE_SETTLE_MS) : requestedSettleMs;
888
- const maxScrollHeight = await expandScrollableContent(page, {
889
- forceScrollableHeight: options.forceScrollableHeight,
890
- visibleOnly: options.visibleOnly !== false,
891
- expandDocumentElements: preserveViewport
892
- });
893
- const targetHeight = Math.min(maxScrollHeight, maxHeight);
894
- let viewportResized = false;
895
- if (!preserveViewport) {
896
- await page.setViewportSize({
897
- width: originalViewport.width,
898
- 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
899
996
  });
900
- viewportResized = true;
901
- } else {
902
- await adjustAffixedElementsForExpandedScreenshot(page, { targetHeight }).catch((error) => {
903
- logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u91CD\u5B9A\u4F4D\u5931\u8D25: ${error?.message || error}`);
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
904
1010
  });
905
- }
906
- if (settleMs > 0) {
907
- await delay(settleMs);
908
- }
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}`);
909
1019
  return {
910
- originalViewport,
911
- maxScrollHeight,
912
1020
  targetHeight,
913
- preserveViewport,
914
- viewportResized
1021
+ frames,
1022
+ settleMs
915
1023
  };
916
1024
  };
917
1025
  var restoreExpandedFullPageScreenshot = async (page, state2 = {}) => {
918
- await page.evaluate((className) => {
919
- const targets = new Set([
920
- ...document.querySelectorAll(`.${className}`),
921
- document.documentElement,
922
- document.body,
923
- document.scrollingElement
924
- ].filter((el) => el?.classList?.contains(className)));
925
- targets.forEach((el) => {
926
- el.style.setProperty("overflow", el.dataset.pkOrigOverflow || "", el.dataset.pkOrigOverflowPriority || "");
927
- el.style.setProperty("height", el.dataset.pkOrigHeight || "", el.dataset.pkOrigHeightPriority || "");
928
- el.style.setProperty("min-height", el.dataset.pkOrigMinHeight || "", el.dataset.pkOrigMinHeightPriority || "");
929
- el.style.setProperty("max-height", el.dataset.pkOrigMaxHeight || "", el.dataset.pkOrigMaxHeightPriority || "");
930
- delete el.dataset.pkOrigOverflow;
931
- delete el.dataset.pkOrigOverflowPriority;
932
- delete el.dataset.pkOrigHeight;
933
- delete el.dataset.pkOrigHeightPriority;
934
- delete el.dataset.pkOrigMinHeight;
935
- delete el.dataset.pkOrigMinHeightPriority;
936
- delete el.dataset.pkOrigMaxHeight;
937
- delete el.dataset.pkOrigMaxHeightPriority;
938
- el.classList.remove(className);
939
- });
940
- }, EXPANDED_SCROLLABLE_CLASS);
941
- if (state2?.originalViewport && state2?.viewportResized) {
942
- 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);
943
1029
  }
944
1030
  };
945
- var capturePageScreenshot = async (page, options = {}) => {
946
- const fullPage = Boolean(options.fullPage);
947
- const type = fullPage ? FORCED_FULLPAGE_TYPE : normalizeType(options.type);
948
- const quality = fullPage ? FORCED_FULLPAGE_QUALITY : normalizeQuality(options.quality, type);
949
- const timeout = toPositiveNumber(options.timeout, DEFAULT_TIMEOUT_MS);
950
- const maxClipHeight = Math.round(toPositiveNumber(options.maxClipHeight, 0));
951
- const fallbackOptions = {
952
- type: type === "webp" ? "png" : type,
953
- fullPage
954
- };
955
- if (quality !== void 0 && fallbackOptions.type === "jpeg") {
956
- fallbackOptions.quality = quality;
957
- }
958
- if (timeout > 0) {
959
- fallbackOptions.timeout = timeout;
960
- }
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;
961
1036
  try {
962
- const context = page && typeof page.context === "function" ? page.context() : null;
963
- if (!context || typeof context.newCDPSession !== "function") {
964
- throw new Error("CDP session is not available");
965
- }
966
- const session = await context.newCDPSession(page);
967
- try {
968
- const metrics = await session.send("Page.getLayoutMetrics");
969
- const viewport = await resolveCurrentViewportSize(page);
970
- const captureParams = {
971
- format: type,
972
- fromSurface: true,
973
- captureBeyondViewport: fullPage,
974
- optimizeForSpeed: true
975
- };
976
- if (quality !== void 0) {
977
- captureParams.quality = quality;
978
- }
979
- if (fullPage) {
980
- captureParams.clip = buildFullPageClip(metrics, viewport, maxClipHeight);
981
- }
982
- const result = await session.send("Page.captureScreenshot", captureParams);
983
- if (!result || typeof result.data !== "string" || !result.data) {
984
- throw new Error("CDP returned empty screenshot data");
985
- }
986
- return Buffer.from(result.data, "base64");
987
- } finally {
988
- if (typeof session.detach === "function") {
989
- await session.detach().catch(() => {
990
- });
991
- }
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
1044
+ });
1045
+ viewportResized = true;
1046
+ if (state2.settleMs > 0) await delay(state2.settleMs);
992
1047
  }
993
- } catch (error) {
994
- logger.warning(`CDP \u622A\u56FE\u5931\u8D25\uFF0C\u56DE\u9000 page.screenshot: ${error?.message || error}`);
995
- return await page.screenshot(fallbackOptions);
996
- }
997
- };
998
- var captureExpandedFullPageScreenshot = async (page, options = {}) => {
999
- const state2 = await prepareExpandedFullPageScreenshot(page, options);
1000
- try {
1001
1048
  return await capturePageScreenshot(page, {
1049
+ ...options,
1002
1050
  fullPage: true,
1003
- type: options.type || "png",
1004
- quality: options.quality,
1005
- timeout: options.timeout,
1006
- maxClipHeight: state2.targetHeight
1051
+ maxClipHeight: targetHeight
1007
1052
  });
1008
1053
  } finally {
1009
- await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
1010
- logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
1011
- });
1012
1054
  if (options.restore) {
1013
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
+ }
1014
1064
  }
1065
+ await restoreViewportMetrics(page);
1066
+ if (options.restore) {
1067
+ await restoreMainFrameScroll(page, originalMainScroll);
1068
+ }
1069
+ }
1070
+ };
1071
+ var isDarkBlankPixel = ({ r, g, b }) => {
1072
+ const max = Math.max(r, g, b);
1073
+ const min = Math.min(r, g, b);
1074
+ return max <= 34 && max - min <= 10;
1075
+ };
1076
+ var analyzeScreenshotBuffer = async (buffer) => {
1077
+ const image = await Jimp.read(buffer);
1078
+ const { width, height } = image.bitmap;
1079
+ const stepY = Math.max(1, Math.floor(height / 900));
1080
+ const stepX = Math.max(1, Math.floor(width / 420));
1081
+ let darkRows = 0;
1082
+ let loadingLikeRows = 0;
1083
+ let rows = 0;
1084
+ for (let y = 0; y < height; y += stepY) {
1085
+ rows += 1;
1086
+ let dark = 0;
1087
+ let edge = 0;
1088
+ let count = 0;
1089
+ let prev = null;
1090
+ for (let x = 0; x < width; x += stepX) {
1091
+ const rgba = intToRGBA(image.getPixelColor(x, y));
1092
+ count += 1;
1093
+ if (isDarkBlankPixel(rgba)) dark += 1;
1094
+ if (prev) {
1095
+ const diff = Math.abs(rgba.r - prev.r) + Math.abs(rgba.g - prev.g) + Math.abs(rgba.b - prev.b);
1096
+ if (diff > 45) edge += 1;
1097
+ }
1098
+ prev = rgba;
1099
+ }
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);
1106
+ return {
1107
+ width,
1108
+ height,
1109
+ darkBlankRowsRatio: darkRows / sampledRows,
1110
+ loadingLikeRowsRatio: loadingLikeRows / sampledRows
1111
+ };
1112
+ };
1113
+ var resolveScreenshotQualityIssue = (analysis) => {
1114
+ if (!analysis) return null;
1115
+ if (analysis.loadingLikeRowsRatio >= 0.92 || analysis.darkBlankRowsRatio >= 0.72) {
1116
+ return "loading-like-dark-screenshot";
1015
1117
  }
1118
+ return null;
1119
+ };
1120
+ var captureExpandedFullPageScreenshot = async (page, options = {}) => {
1121
+ const attempts = Math.max(1, toPositiveInteger(options.qualityRetryAttempts, DEFAULT_QUALITY_RETRY_ATTEMPTS));
1122
+ const retryDelayMs = Math.max(0, Number(options.qualityRetryDelayMs ?? DEFAULT_QUALITY_RETRY_DELAY_MS) || 0);
1123
+ let lastBuffer = null;
1124
+ let lastAnalysis = null;
1125
+ let lastIssue = null;
1126
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
1127
+ const buffer = await captureExpandedFullPageScreenshotOnce(page, options);
1128
+ const analysis = await analyzeScreenshotBuffer(buffer).catch((error) => {
1129
+ logger.warning(`\u622A\u56FE\u8D28\u91CF\u5206\u6790\u5931\u8D25: ${error?.message || error}`);
1130
+ return null;
1131
+ });
1132
+ const issue = resolveScreenshotQualityIssue(analysis);
1133
+ lastBuffer = buffer;
1134
+ lastAnalysis = analysis;
1135
+ lastIssue = issue;
1136
+ if (!issue) return buffer;
1137
+ if (attempt < attempts && retryDelayMs > 0) {
1138
+ logger.warning(`\u622A\u56FE\u7591\u4F3C\u5F02\u5E38(${issue})\uFF0C\u7B49\u5F85\u540E\u91CD\u8BD5: attempt=${attempt}/${attempts}`);
1139
+ await delay(retryDelayMs);
1140
+ }
1141
+ }
1142
+ if (lastIssue && lastAnalysis) {
1143
+ logger.warning(
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)}`
1145
+ );
1146
+ }
1147
+ return lastBuffer;
1016
1148
  };
1017
1149
 
1018
1150
  // src/errors.js
@@ -1461,6 +1593,9 @@ var ProxyMeterRuntime = {
1461
1593
  getProxyMeterSnapshot
1462
1594
  };
1463
1595
 
1596
+ // src/internals/constants.js
1597
+ var PageRuntimeStateKey = "__playwright_toolkit_runtime_state__";
1598
+
1464
1599
  // src/runtime-env.js
1465
1600
  var BROWSER_PROFILE_SCHEMA_VERSION = 1;
1466
1601
  var SUPPORTED_CLOAK_FINGERPRINT_PLATFORMS = /* @__PURE__ */ new Set(["linux", "macos", "windows"]);
@@ -9040,11 +9175,11 @@ var resolveScreenshotWatermarkifyMeta = async (page, options = {}) => {
9040
9175
  watermark: options.watermark,
9041
9176
  strip: options.strip,
9042
9177
  stripLogoSrc,
9043
- device: resolvePageDevice2(page)
9178
+ device: resolvePageDevice(page)
9044
9179
  };
9045
9180
  };
9046
9181
  var buildFontFamily = () => 'MiSans, "SF Pro Display", "PingFang SC", "Helvetica Neue", Arial, sans-serif';
9047
- var resolvePageDevice2 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
9182
+ var resolvePageDevice = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
9048
9183
  var findStripSegment = (segments, kind) => {
9049
9184
  const found = Array.isArray(segments) ? segments.find((segment) => segment?.kind === kind) : null;
9050
9185
  return found && typeof found === "object" ? found : {};
@@ -9696,7 +9831,7 @@ var watermarkifyScreenshotBuffer = async (buffer, meta, page = null, options = {
9696
9831
  };
9697
9832
 
9698
9833
  // src/internals/compression.js
9699
- import { Jimp, JimpMime, ResizeStrategy } from "jimp";
9834
+ import { Jimp as Jimp2, JimpMime, ResizeStrategy } from "jimp";
9700
9835
  var logger15 = createInternalLogger("Compression");
9701
9836
  var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
9702
9837
  var DEFAULT_SCREENSHOT_OUTPUT_TYPE = "jpeg";
@@ -9778,7 +9913,7 @@ var encodeJpeg = async (sourceImage, compression, scale, quality) => {
9778
9913
  };
9779
9914
  };
9780
9915
  var compressImageBuffer = async (buffer, compression) => {
9781
- const sourceImage = await Jimp.read(buffer);
9916
+ const sourceImage = await Jimp2.read(buffer);
9782
9917
  const maxQuality = toJpegQuality(compression.quality);
9783
9918
  const minQuality = Math.min(maxQuality, toJpegQuality(compression.minQuality));
9784
9919
  let quality = maxQuality;