@@ -88,11 +88,27 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
8888 currentStateMessages . push ( message )
8989 const resultData = JSON . parse ( message . text || "{}" ) as BrowserActionResult
9090
91+ // Validate screenshot data to ensure it's a proper data URL
92+ let validatedScreenshot = resultData . screenshot
93+ if ( validatedScreenshot && typeof validatedScreenshot === "string" ) {
94+ // Ensure the screenshot is a valid data URL
95+ if ( ! validatedScreenshot . startsWith ( "data:image/" ) ) {
96+ // If it's just base64 without the data URL prefix, add it
97+ if ( validatedScreenshot . match ( / ^ [ A - Z a - z 0 - 9 + / ] + = * $ / ) ) {
98+ validatedScreenshot = `data:image/webp;base64,${ validatedScreenshot } `
99+ } else {
100+ // Invalid screenshot data, set to undefined
101+ console . warn ( "Invalid screenshot data detected, skipping screenshot" )
102+ validatedScreenshot = undefined
103+ }
104+ }
105+ }
106+
91107 // Add page with current state and previous next actions
92108 result . push ( {
93109 currentState : {
94110 url : resultData . currentUrl ,
95- screenshot : resultData . screenshot ,
111+ screenshot : validatedScreenshot ,
96112 mousePosition : resultData . currentMousePosition ,
97113 consoleLogs : resultData . logs ,
98114 messages : [ ...currentStateMessages ] ,
@@ -296,18 +312,9 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
296312 backgroundColor : "var(--vscode-input-background)" ,
297313 } } >
298314 { displayState . screenshot ? (
299- < img
300- src = { displayState . screenshot }
315+ < ScreenshotImage
316+ screenshot = { displayState . screenshot }
301317 alt = { t ( "chat:browser.screenshot" ) }
302- style = { {
303- position : "absolute" ,
304- top : 0 ,
305- left : 0 ,
306- width : "100%" ,
307- height : "100%" ,
308- objectFit : "contain" ,
309- cursor : "pointer" ,
310- } }
311318 onClick = { ( ) =>
312319 vscode . postMessage ( {
313320 type : "openImage" ,
@@ -576,4 +583,86 @@ const BrowserCursor: React.FC<{ style?: React.CSSProperties }> = ({ style }) =>
576583 )
577584}
578585
586+ // Separate component to handle screenshot rendering with error boundaries
587+ const ScreenshotImage : React . FC < {
588+ screenshot : string
589+ alt : string
590+ onClick : ( ) => void
591+ } > = React . memo ( ( { screenshot, alt, onClick } ) => {
592+ const [ imageError , setImageError ] = useState ( false )
593+ const [ validatedSrc , setValidatedSrc ] = useState < string > ( "" )
594+
595+ useEffect ( ( ) => {
596+ // Validate and sanitize the screenshot data
597+ if ( screenshot && typeof screenshot === "string" ) {
598+ // Check if it's a valid data URL
599+ if ( screenshot . startsWith ( "data:image/" ) ) {
600+ // Extract the base64 part and validate it
601+ const base64Match = screenshot . match ( / ^ d a t a : i m a g e \/ [ ^ ; ] + ; b a s e 6 4 , ( .+ ) $ / )
602+ if ( base64Match && base64Match [ 1 ] ) {
603+ try {
604+ // Validate base64 by attempting to decode a small portion
605+ // This will throw if the base64 is invalid
606+ atob ( base64Match [ 1 ] . substring ( 0 , 100 ) )
607+ // If successful, use the original screenshot
608+ setValidatedSrc ( screenshot )
609+ setImageError ( false )
610+ } catch ( _e ) {
611+ console . error ( "Invalid base64 in screenshot data" )
612+ setImageError ( true )
613+ }
614+ } else {
615+ setImageError ( true )
616+ }
617+ } else {
618+ setImageError ( true )
619+ }
620+ } else {
621+ setImageError ( true )
622+ }
623+ } , [ screenshot ] )
624+
625+ if ( imageError || ! validatedSrc ) {
626+ // Fallback UI when screenshot is invalid
627+ return (
628+ < div
629+ style = { {
630+ position : "absolute" ,
631+ top : 0 ,
632+ left : 0 ,
633+ width : "100%" ,
634+ height : "100%" ,
635+ display : "flex" ,
636+ alignItems : "center" ,
637+ justifyContent : "center" ,
638+ backgroundColor : "var(--vscode-editor-background)" ,
639+ color : "var(--vscode-descriptionForeground)" ,
640+ fontSize : "12px" ,
641+ textAlign : "center" ,
642+ padding : "20px" ,
643+ } } >
644+ Screenshot unavailable
645+ </ div >
646+ )
647+ }
648+
649+ return (
650+ < img
651+ src = { validatedSrc }
652+ alt = { alt }
653+ style = { {
654+ position : "absolute" ,
655+ top : 0 ,
656+ left : 0 ,
657+ width : "100%" ,
658+ height : "100%" ,
659+ objectFit : "contain" ,
660+ cursor : "pointer" ,
661+ } }
662+ onClick = { onClick }
663+ onError = { ( ) => setImageError ( true ) }
664+ />
665+ )
666+ } )
667+
579668export default BrowserSessionRow
0 commit comments