@@ -16,22 +16,70 @@ function validSignature(bytes, format) {
1616 && bytes . subarray ( 8 , 12 ) . toString ( "ascii" ) === "WEBP" ;
1717}
1818
19- function boundedDimension ( value , label ) {
19+ function reportedNumber ( value ) {
2020 const number = Number ( value ) ;
21- if ( ! Number . isFinite ( number ) || number <= 0 || number > BROWSER_SCREENSHOT_MAX_CSS_DIMENSION ) {
22- throw new BrowserControlError ( "BROWSER_AUTOMATION_SCREENSHOT_BOUNDS" ,
23- `browser screenshot ${ label } is outside the supported CSS bounds` , { outcome : "notSent" } ) ;
24- }
25- return number ;
21+ return Number . isFinite ( number ) ? number : null ;
2622}
2723
28- function boundedScale ( value ) {
29- const number = Number ( value ) ;
30- if ( ! Number . isFinite ( number ) || number < 0.1 || number > 3 ) {
31- throw new BrowserControlError ( "BROWSER_AUTOMATION_SCREENSHOT_BOUNDS" ,
32- "browser screenshot clip scale is outside the supported bounds" , { outcome : "notSent" } ) ;
24+ function boundsError ( source , reason , measured ) {
25+ const details = Object . freeze ( {
26+ reason,
27+ source,
28+ measured : Object . freeze ( {
29+ x : reportedNumber ( measured . x ) ,
30+ y : reportedNumber ( measured . y ) ,
31+ cssWidth : reportedNumber ( measured . width ) ,
32+ cssHeight : reportedNumber ( measured . height ) ,
33+ scale : reportedNumber ( measured . scale ) ,
34+ scaledCssPixels : reportedNumber ( measured . scaledCssPixels ) ,
35+ } ) ,
36+ limits : Object . freeze ( {
37+ maxCssDimension : BROWSER_SCREENSHOT_MAX_CSS_DIMENSION ,
38+ maxScaledCssPixels : BROWSER_SCREENSHOT_MAX_CSS_PIXELS ,
39+ minScale : 0.1 ,
40+ maxScale : 3 ,
41+ } ) ,
42+ recovery : Object . freeze ( { automatic : false , viewportScrollMayTriggerEffects : true } ) ,
43+ } ) ;
44+ return new BrowserControlError ( "BROWSER_AUTOMATION_SCREENSHOT_BOUNDS" ,
45+ `browser screenshot ${ source } exceeds the supported ${ reason } bounds` , {
46+ outcome : "notSent" ,
47+ retryable : false ,
48+ details,
49+ } ) ;
50+ }
51+
52+ export function validateBrowserScreenshotBounds ( { source, x = 0 , y = 0 , width, height, scale = 1 } ) {
53+ const measured = {
54+ x : Number ( x ) ,
55+ y : Number ( y ) ,
56+ width : Number ( width ) ,
57+ height : Number ( height ) ,
58+ scale : Number ( scale ) ,
59+ } ;
60+ measured . scaledCssPixels = measured . width * measured . height * measured . scale * measured . scale ;
61+ if ( ! Number . isFinite ( measured . scale ) || measured . scale < 0.1 || measured . scale > 3 ) {
62+ throw boundsError ( source , "scale" , measured ) ;
63+ }
64+ if ( ! Number . isFinite ( measured . x ) || ! Number . isFinite ( measured . y ) || measured . x < 0 || measured . y < 0 ) {
65+ throw boundsError ( source , "origin" , measured ) ;
3366 }
34- return number ;
67+ if ( ! Number . isFinite ( measured . width ) || ! Number . isFinite ( measured . height )
68+ || measured . width <= 0 || measured . height <= 0
69+ || measured . width > BROWSER_SCREENSHOT_MAX_CSS_DIMENSION
70+ || measured . height > BROWSER_SCREENSHOT_MAX_CSS_DIMENSION ) {
71+ throw boundsError ( source , "dimension" , measured ) ;
72+ }
73+ if ( measured . x > BROWSER_SCREENSHOT_MAX_CSS_DIMENSION
74+ || measured . y > BROWSER_SCREENSHOT_MAX_CSS_DIMENSION
75+ || measured . x + measured . width > BROWSER_SCREENSHOT_MAX_CSS_DIMENSION
76+ || measured . y + measured . height > BROWSER_SCREENSHOT_MAX_CSS_DIMENSION ) {
77+ throw boundsError ( source , "extent" , measured ) ;
78+ }
79+ if ( measured . scaledCssPixels > BROWSER_SCREENSHOT_MAX_CSS_PIXELS ) {
80+ throw boundsError ( source , "area" , measured ) ;
81+ }
82+ return Object . freeze ( measured ) ;
3583}
3684
3785export class BrowserScreenshot {
@@ -49,26 +97,37 @@ export class BrowserScreenshot {
4997 const layout = await this . _command ( sessionRef , "Page.getLayoutMetrics" , { } , commandResults , signal ) ;
5098 const metrics = layout . result || { } ;
5199 const viewport = metrics . cssVisualViewport || metrics . visualViewport || { } ;
52- const content = metrics . contentSize || { } ;
100+ // 최신 CDP의 CSS pixel 필드를 우선한다. deprecated contentSize는 device pixel일 수 있다.
101+ const content = metrics . cssContentSize || metrics . contentSize || { } ;
53102 let clip = null ;
54103 let cssWidth ;
55104 let cssHeight ;
56105 if ( options . clip ) {
57- clip = { ...options . clip , scale : boundedScale ( options . clip . scale ?? 1 ) } ;
58- cssWidth = boundedDimension ( clip . width , "clip width" ) ;
59- cssHeight = boundedDimension ( clip . height , "clip height" ) ;
106+ const bounds = validateBrowserScreenshotBounds ( {
107+ source : "clip" ,
108+ ...options . clip ,
109+ scale : options . clip . scale ?? 1 ,
110+ } ) ;
111+ clip = { x : bounds . x , y : bounds . y , width : bounds . width , height : bounds . height , scale : bounds . scale } ;
112+ cssWidth = bounds . width ;
113+ cssHeight = bounds . height ;
60114 } else if ( options . fullPage === true ) {
61- cssWidth = boundedDimension ( content . width , "content width" ) ;
62- cssHeight = boundedDimension ( content . height , "content height" ) ;
63- clip = { x : 0 , y : 0 , width : cssWidth , height : cssHeight , scale : 1 } ;
115+ const bounds = validateBrowserScreenshotBounds ( {
116+ source : "content" ,
117+ width : content . width ,
118+ height : content . height ,
119+ } ) ;
120+ cssWidth = bounds . width ;
121+ cssHeight = bounds . height ;
122+ clip = { x : 0 , y : 0 , width : bounds . width , height : bounds . height , scale : 1 } ;
64123 } else {
65- cssWidth = boundedDimension ( viewport . clientWidth , "viewport width" ) ;
66- cssHeight = boundedDimension ( viewport . clientHeight , "viewport height" ) ;
67- }
68- const scale = clip ?. scale ?? 1 ;
69- if ( cssWidth * cssHeight * scale * scale > BROWSER_SCREENSHOT_MAX_CSS_PIXELS ) {
70- throw new BrowserControlError ( "BROWSER_AUTOMATION_SCREENSHOT_BOUNDS" ,
71- "browser screenshot exceeds the CSS pixel area limit" , { outcome : "notSent" } ) ;
124+ const bounds = validateBrowserScreenshotBounds ( {
125+ source : "viewport" ,
126+ width : viewport . clientWidth ,
127+ height : viewport . clientHeight ,
128+ } ) ;
129+ cssWidth = bounds . width ;
130+ cssHeight = bounds . height ;
72131 }
73132 const captured = await this . _command ( sessionRef , "Page.captureScreenshot" , {
74133 format,
0 commit comments