@@ -559,25 +559,29 @@ export class Sniffer {
559559 } else {
560560 const results : ImageItem [ ] = response . results || [ ] ;
561561 const updatedResults = [ ...results ] ;
562-
562+
563563 // 在侧边栏环境重新获取防盗链域名的真实宽高(DNR 规则已生效,可正常加载)
564- await runConcurrent ( updatedResults , METADATA_CONCURRENCY , async ( item , index ) => {
565- const needUpdate =
566- item . url . includes ( "sinaimg.cn" ) ||
567- item . url . includes ( "weibo.com" ) ;
568- if ( needUpdate ) {
569- const meta = await this . getImageMetadata ( item . url ) ;
570- if ( meta ) {
571- updatedResults [ index ] = {
572- ...item ,
573- width : meta . width ,
574- height : meta . height ,
575- sizeKB : meta . sizeKB > 0 ? meta . sizeKB : item . sizeKB ,
576- } ;
564+ await runConcurrent (
565+ updatedResults ,
566+ METADATA_CONCURRENCY ,
567+ async ( item , index ) => {
568+ const needUpdate =
569+ item . url . includes ( "sinaimg.cn" ) ||
570+ item . url . includes ( "weibo.com" ) ;
571+ if ( needUpdate ) {
572+ const meta = await this . getImageMetadata ( item . url ) ;
573+ if ( meta ) {
574+ updatedResults [ index ] = {
575+ ...item ,
576+ width : meta . width ,
577+ height : meta . height ,
578+ sizeKB : meta . sizeKB > 0 ? meta . sizeKB : item . sizeKB ,
579+ } ;
580+ }
577581 }
578- }
579- } ) ;
580-
582+ } ,
583+ ) ;
584+
581585 resolve ( updatedResults ) ;
582586 }
583587 } ,
@@ -612,7 +616,8 @@ export class Sniffer {
612616 settings ?. interfaceBehavior ?. identifyBackgroundImages ?? true ;
613617 const isTelegramHost = window . location . host . includes ( "telegram" ) ;
614618 const identifyBlob =
615- ( settings ?. interfaceBehavior ?. identifyBlobImages ?? false ) || isTelegramHost ;
619+ ( settings ?. interfaceBehavior ?. identifyBlobImages ?? false ) ||
620+ isTelegramHost ;
616621
617622 const isPixiv = window . location . href . includes ( "pixiv.net" ) ;
618623 const [ treeUrls , perfUrls , svgUrls ] = await Promise . all ( [
@@ -629,11 +634,18 @@ export class Sniffer {
629634 ) {
630635 const resolved = UrlResolver . transformSiteSpecificUrl ( url ) ;
631636 // 过滤掉 weibo.com 的网页链接(如 /u/false 或 /status/ 等非真实图片)
632- if ( resolved . includes ( "weibo.com" ) && ! resolved . match ( / \. ( j p g | j p e g | p n g | g i f | w e b p | s v g ) / i) ) {
637+ if (
638+ resolved . includes ( "weibo.com" ) &&
639+ ! resolved . match ( / \. ( j p g | j p e g | p n g | g i f | w e b p | s v g ) / i)
640+ ) {
633641 return ;
634642 }
635643 // 如果在 Pixiv 网站,过滤掉所有的 SVG 资源,防止 UI 背景渐变/图标等乱入
636- if ( isPixiv && ( resolved . includes ( "image/svg+xml" ) || resolved . toLowerCase ( ) . includes ( ".svg" ) ) ) {
644+ if (
645+ isPixiv &&
646+ ( resolved . includes ( "image/svg+xml" ) ||
647+ resolved . toLowerCase ( ) . includes ( ".svg" ) )
648+ ) {
637649 return ;
638650 }
639651 urls . add ( resolved ) ;
@@ -669,20 +681,6 @@ export class Sniffer {
669681 pageTitle : document . title ,
670682 pageUrl : window . location . href ,
671683 } ) ;
672- } else {
673- // 兜底保留:如果测量超时或失败,不直接丢弃图片,而是生成兜底的 ImageItem!
674- items . push ( {
675- url,
676- width : 0 ,
677- height : 0 ,
678- sizeKB : 0 ,
679- format : ImageTypeDetector . getFormatFromUrl ( url ) ,
680- filename : url . split ( "/" ) . pop ( ) ?. split ( / [ ? # ] / ) [ 0 ] || "image" ,
681- id,
682- isSelected : false ,
683- pageTitle : document . title ,
684- pageUrl : window . location . href ,
685- } ) ;
686684 }
687685 } ) ;
688686
@@ -717,82 +715,91 @@ export class Sniffer {
717715 filename : url . split ( "/" ) . pop ( ) ?. split ( / [ ? # ] / ) [ 0 ] || "image" ,
718716 } ;
719717 }
720-
721718 try {
722- const dimensions = await new Promise < { width : number ; height : number } > (
723- ( resolve , reject ) => {
724- const img = new Image ( ) ;
725- const timeoutId = window . setTimeout ( ( ) => {
726- cleanup ( ) ;
727- reject (
728- new Error (
719+ let dimensions : { width : number ; height : number } ;
720+ try {
721+ dimensions = await new Promise < { width : number ; height : number } > (
722+ ( resolve , reject ) => {
723+ const img = new Image ( ) ;
724+ const timeoutId = window . setTimeout ( ( ) => {
725+ cleanup ( ) ;
726+ const err = new Error (
729727 `Timed out loading image metadata: ${ String ( url ) . slice ( 0 , 100 ) } ` ,
730- ) ,
731- ) ;
732- } , IMAGE_METADATA_TIMEOUT_MS ) ;
733- const cleanup = ( ) => {
734- window . clearTimeout ( timeoutId ) ;
735- img . onload = null ;
736- img . onerror = null ;
737- try {
738- img . src = "" ;
739- } catch {
740- // 忽略清理失败
741- }
742- } ;
728+ ) ;
729+ ( err as Error & { isTimeout ?: boolean } ) . isTimeout = true ;
730+ reject ( err ) ;
731+ } , IMAGE_METADATA_TIMEOUT_MS ) ;
732+ const cleanup = ( ) => {
733+ window . clearTimeout ( timeoutId ) ;
734+ img . onload = null ;
735+ img . onerror = null ;
736+ try {
737+ img . src = "" ;
738+ } catch {
739+ // 忽略清理失败
740+ }
741+ } ;
743742
744- img . onload = ( ) => {
745- const size = {
746- width : img . naturalWidth ,
747- height : img . naturalHeight ,
743+ img . onload = ( ) => {
744+ const size = {
745+ width : img . naturalWidth ,
746+ height : img . naturalHeight ,
747+ } ;
748+ cleanup ( ) ;
749+ resolve ( size ) ;
748750 } ;
749- cleanup ( ) ;
750- resolve ( size ) ;
751- } ;
752- img . onerror = ( ) => {
753- cleanup ( ) ;
754- reject (
755- new Error ( `Failed to load image: ${ String ( url ) . slice ( 0 , 100 ) } ` ) ,
756- ) ;
757- } ;
758- // 仅侧边栏页面(chrome-extension:// 协议)才需要走后台 Blob 代理。
759- // Content script 运行在目标页上下文,可直接加载且受 DNR 规则保护,无需代理。
760- // 对于本地内存中的 blob: 图片,后台 Service Worker 无法跨越沙盒拉取,直接不走代理。
761- const isSidePanelPage =
762- typeof window !== "undefined" &&
763- window . location . protocol === "chrome-extension:" &&
764- typeof chrome !== "undefined" &&
765- ! ! chrome . runtime ?. sendMessage ;
766-
767- const isBlobUrl = url . startsWith ( "blob:" ) ;
768-
769- if ( isSidePanelPage && ! isBlobUrl ) {
770- // 根据域名传入对应的防盗链 Referer,Service Worker fetch 不受 DNR 规则覆盖
771- let referer : string | undefined ;
772- if ( url . includes ( "sinaimg.cn" ) || url . includes ( "weibo.com" ) ) {
773- referer = "https://weibo.com/" ;
774- } else if ( url . includes ( "pximg.net" ) ) {
775- referer = "https://www.pixiv.net/" ;
751+ img . onerror = ( ) => {
752+ cleanup ( ) ;
753+ reject (
754+ new Error ( `Failed to load image: ${ String ( url ) . slice ( 0 , 100 ) } ` ) ,
755+ ) ;
756+ } ;
757+ // 仅侧边栏页面(chrome-extension:// 协议)才需要走后台 Blob 代理。
758+ // Content script 运行在目标页上下文,可直接加载且受 DNR 规则保护,无需代理。
759+ // 对于本地内存中的 blob: 图片,后台 Service Worker 无法跨越沙盒拉取,直接不走代理。
760+ const isSidePanelPage =
761+ typeof window !== "undefined" &&
762+ window . location . protocol === "chrome-extension:" &&
763+ typeof chrome !== "undefined" &&
764+ ! ! chrome . runtime ?. sendMessage ;
765+
766+ const isBlobUrl = url . startsWith ( "blob:" ) ;
767+
768+ if ( isSidePanelPage && ! isBlobUrl ) {
769+ // 根据域名传入对应的防盗链 Referer,Service Worker fetch 不受 DNR 规则覆盖
770+ let referer : string | undefined ;
771+ if ( url . includes ( "sinaimg.cn" ) || url . includes ( "weibo.com" ) ) {
772+ referer = "https://weibo.com/" ;
773+ } else if ( url . includes ( "pximg.net" ) ) {
774+ referer = "https://www.pixiv.net/" ;
775+ }
776+ chrome . runtime . sendMessage (
777+ {
778+ type : "FETCH_BLOB" ,
779+ payload : { url, referer } ,
780+ } ,
781+ ( response ) => {
782+ if ( response ?. success && response . arrayBuffer ) {
783+ const mimeType = response . mimeType || "image/jpeg" ;
784+ img . src = `data:${ mimeType } ;base64,${ response . arrayBuffer } ` ;
785+ } else {
786+ img . src = url ;
787+ }
788+ } ,
789+ ) ;
790+ } else {
791+ img . src = url ;
776792 }
777- chrome . runtime . sendMessage (
778- {
779- type : "FETCH_BLOB" ,
780- payload : { url, referer } ,
781- } ,
782- ( response ) => {
783- if ( response ?. success && response . arrayBuffer ) {
784- const mimeType = response . mimeType || "image/jpeg" ;
785- img . src = `data:${ mimeType } ;base64,${ response . arrayBuffer } ` ;
786- } else {
787- img . src = url ;
788- }
789- } ,
790- ) ;
791- } else {
792- img . src = url ;
793- }
794- } ,
795- ) ;
793+ } ,
794+ ) ;
795+ } catch ( err ) {
796+ const error = err as Error & { isTimeout ?: boolean } ;
797+ if ( error ?. isTimeout ) {
798+ throw error ; // 扔给最外层 try...catch,直接返回 null 丢弃以符合测试对 stalled 图片的处理
799+ }
800+ // 网络加载失败(如防盗链拦截),设宽高为 0 以保留该项,便于后续代理下载
801+ dimensions = { width : 0 , height : 0 } ;
802+ }
796803
797804 let sizeKB = 0 ;
798805 let format : ImageFormat = "UNKNOWN" ;
0 commit comments