@@ -812,8 +812,17 @@ export default class DataBrowser extends React.Component {
812812 }
813813 }
814814
815+ isSafeHttpUrl ( url ) {
816+ try {
817+ const parsed = new URL ( url ) ;
818+ return parsed . protocol === 'http:' || parsed . protocol === 'https:' ;
819+ } catch {
820+ return false ;
821+ }
822+ }
823+
815824 extractMediaUrls ( data ) {
816- const urls = { images : [ ] , videos : [ ] , audios : [ ] } ;
825+ const urls = { images : new Set ( ) , videos : new Set ( ) , audios : new Set ( ) } ;
817826
818827 if ( ! data ?. panel ?. segments ) {
819828 return urls ;
@@ -822,12 +831,12 @@ export default class DataBrowser extends React.Component {
822831 data . panel . segments . forEach ( segment => {
823832 if ( segment . items ) {
824833 segment . items . forEach ( item => {
825- if ( item . type === 'image' && item . url ) {
826- urls . images . push ( item . url ) ;
827- } else if ( item . type === 'video' && item . url ) {
828- urls . videos . push ( item . url ) ;
829- } else if ( item . type === 'audio' && item . url ) {
830- urls . audios . push ( item . url ) ;
834+ if ( item . type === 'image' && item . url && this . isSafeHttpUrl ( item . url ) ) {
835+ urls . images . add ( item . url ) ;
836+ } else if ( item . type === 'video' && item . url && this . isSafeHttpUrl ( item . url ) ) {
837+ urls . videos . add ( item . url ) ;
838+ } else if ( item . type === 'audio' && item . url && this . isSafeHttpUrl ( item . url ) ) {
839+ urls . audios . add ( item . url ) ;
831840 }
832841 } ) ;
833842 }
@@ -837,23 +846,29 @@ export default class DataBrowser extends React.Component {
837846 }
838847
839848 prefetchMedia ( urls , mediaType ) {
840- if ( ! urls || urls . length === 0 ) {
849+ if ( ! urls || urls . size === 0 ) {
841850 return ;
842851 }
843852
844853 urls . forEach ( url => {
845- if ( mediaType === 'image' ) {
846- const img = new Image ( ) ;
847- img . onerror = ( ) => {
848- console . error ( `Failed to prefetch image: ${ url } ` ) ;
849- } ;
850- img . src = url ;
851- } else if ( mediaType === 'video' || mediaType === 'audio' ) {
852- // For video and audio, we can use fetch to cache the content
853- fetch ( url , { mode : 'no-cors' } ) . catch ( error => {
854- console . error ( `Failed to prefetch ${ mediaType } : ${ url } ` , error ) ;
855- } ) ;
856- }
854+ // Use link-based prefetching for better browser optimization and caching
855+ const link = document . createElement ( 'link' ) ;
856+ link . rel = mediaType === 'image' ? 'preload' : 'prefetch' ;
857+ link . as = mediaType ;
858+ link . href = url ;
859+
860+ link . onerror = ( ) => {
861+ console . error ( `Failed to prefetch ${ mediaType } : ${ url } ` ) ;
862+ } ;
863+
864+ document . head . appendChild ( link ) ;
865+
866+ // Clean up the link element after a delay to prevent memory leaks
867+ setTimeout ( ( ) => {
868+ if ( link . parentNode ) {
869+ link . parentNode . removeChild ( link ) ;
870+ }
871+ } , 30000 ) ; // Remove after 30 seconds
857872 } ) ;
858873 }
859874
@@ -884,13 +899,13 @@ export default class DataBrowser extends React.Component {
884899 const { prefetchImage, prefetchVideo, prefetchAudio } = this . getPrefetchSettings ( ) ;
885900 const mediaUrls = this . extractMediaUrls ( result ) ;
886901
887- if ( prefetchImage && mediaUrls . images . length > 0 ) {
902+ if ( prefetchImage && mediaUrls . images . size > 0 ) {
888903 this . prefetchMedia ( mediaUrls . images , 'image' ) ;
889904 }
890- if ( prefetchVideo && mediaUrls . videos . length > 0 ) {
905+ if ( prefetchVideo && mediaUrls . videos . size > 0 ) {
891906 this . prefetchMedia ( mediaUrls . videos , 'video' ) ;
892907 }
893- if ( prefetchAudio && mediaUrls . audios . length > 0 ) {
908+ if ( prefetchAudio && mediaUrls . audios . size > 0 ) {
894909 this . prefetchMedia ( mediaUrls . audios , 'audio' ) ;
895910 }
896911 } ) . catch ( error => {
0 commit comments