@@ -6,6 +6,9 @@ const browserAPI = typeof browser !== 'undefined' ? browser : chrome;
66const DEFAULT_SITE_PATTERNS = [ 'https://linux.do/*' , 'https://*.linux.do/*' ] ;
77const LINUX_DO_TAB_PATTERNS = [ '*://linux.do/*' , '*://*.linux.do/*' ] ;
88
9+ // 已注入 content script 的标签页 ID 集合
10+ const injectedTabs = new Set < number > ( ) ;
11+
912type SiteAccessCheckResult =
1013 | {
1114 success : true ;
@@ -41,6 +44,82 @@ function isLinuxDoHostname(hostname: string) {
4144 return hostname === 'linux.do' || hostname . endsWith ( '.linux.do' ) ;
4245}
4346
47+ // 检查 URL 是否匹配已授权的站点
48+ async function isAuthorizedSite ( url : string ) : Promise < boolean > {
49+ try {
50+ const origin = new URL ( url ) . origin ;
51+ const originPattern = `${ origin } /*` ;
52+
53+ // 检查是否是默认站点
54+ if ( DEFAULT_SITE_PATTERNS . includes ( originPattern ) ) {
55+ return true ;
56+ }
57+
58+ // 检查是否是已授权的可选站点
59+ const allowed = await browserAPI . permissions . contains ( { origins : [ originPattern ] } ) ;
60+ return allowed ;
61+ } catch {
62+ return false ;
63+ }
64+ }
65+
66+ // 动态注入 content script 到指定标签页
67+ async function injectContentScript ( tabId : number , url : string ) : Promise < void > {
68+ // 检查是否已注入
69+ if ( injectedTabs . has ( tabId ) ) {
70+ return ;
71+ }
72+
73+ // 检查是否有权限
74+ const authorized = await isAuthorizedSite ( url ) ;
75+ if ( ! authorized ) {
76+ return ;
77+ }
78+
79+ // 排除 raw 页面
80+ try {
81+ const urlObj = new URL ( url ) ;
82+ if ( urlObj . pathname . includes ( '/raw/' ) ) {
83+ return ;
84+ }
85+ } catch {
86+ return ;
87+ }
88+
89+ try {
90+ // 注入 CSS
91+ await browserAPI . scripting . insertCSS ( {
92+ target : { tabId } ,
93+ files : [ 'content-scripts/content.css' ] ,
94+ } ) ;
95+
96+ // 注入 JS
97+ await browserAPI . scripting . executeScript ( {
98+ target : { tabId } ,
99+ files : [ 'content-scripts/content.js' ] ,
100+ } ) ;
101+
102+ injectedTabs . add ( tabId ) ;
103+ console . log ( `[动态注入] 已向标签页 ${ tabId } 注入 content script` ) ;
104+ } catch ( error ) {
105+ console . error ( `[动态注入] 注入失败:` , error ) ;
106+ }
107+ }
108+
109+ // 检查并注入到所有匹配的已打开标签页
110+ async function injectToAllAuthorizedTabs ( ) : Promise < void > {
111+ try {
112+ const tabs = await browserAPI . tabs . query ( { } ) ;
113+ for ( const tab of tabs ) {
114+ if ( tab . id && tab . url ) {
115+ await injectContentScript ( tab . id , tab . url ) ;
116+ }
117+ }
118+ } catch ( error ) {
119+ console . error ( '[动态注入] 扫描标签页失败:' , error ) ;
120+ }
121+ }
122+
44123function normalizeOriginPattern ( input : string ) {
45124 const rawInput = ( input || '' ) . trim ( ) ;
46125 if ( ! rawInput ) {
@@ -203,7 +282,11 @@ browserAPI.runtime.onMessage.addListener((request, sender, sendResponse) => {
203282 if ( request . action === 'permissions_remove_site' ) {
204283 ( async ( ) => {
205284 try {
206- const origin = normalizeOriginPattern ( request . origin || request . url || '' ) ;
285+ const origin = request . origin || request . url || '' ;
286+ if ( ! origin ) {
287+ sendResponse ( { success : false , error : 'URL 不能为空' } ) ;
288+ return ;
289+ }
207290 if ( DEFAULT_SITE_PATTERNS . includes ( origin ) ) {
208291 sendResponse ( { success : false , error : '默认站点权限不可移除' } ) ;
209292 return ;
@@ -521,3 +604,36 @@ browserAPI.runtime.onMessage.addListener((request, sender, sendResponse) => {
521604 return true ;
522605 }
523606} ) ;
607+
608+ // 监听权限变化,当添加新站点权限时注入 content script
609+ // 注意:onAdded 在构建环境的 fake-browser 中未实现,需要 try-catch
610+ try {
611+ if ( browserAPI . permissions ?. onAdded ) {
612+ browserAPI . permissions . onAdded . addListener ( async ( permissions ) => {
613+ if ( permissions . origins && permissions . origins . length > 0 ) {
614+ console . log ( '[动态注入] 检测到新站点权限:' , permissions . origins ) ;
615+ await injectToAllAuthorizedTabs ( ) ;
616+ }
617+ } ) ;
618+ }
619+ } catch {
620+ // 构建环境中 permissions.onAdded 可能未实现,忽略即可
621+ }
622+
623+ // 监听标签页更新,当导航到已授权的站点时注入 content script
624+ browserAPI . tabs . onUpdated . addListener ( async ( tabId , changeInfo , tab ) => {
625+ // 页面开始加载时清理注入状态(刷新或导航到新页面)
626+ if ( changeInfo . status === 'loading' ) {
627+ injectedTabs . delete ( tabId ) ;
628+ }
629+
630+ // 页面加载完成时注入 content script
631+ if ( changeInfo . status === 'complete' && tab . url ) {
632+ await injectContentScript ( tabId , tab . url ) ;
633+ }
634+ } ) ;
635+
636+ // 监听标签页关闭,清理注入状态
637+ browserAPI . tabs . onRemoved . addListener ( ( tabId ) => {
638+ injectedTabs . delete ( tabId ) ;
639+ } ) ;
0 commit comments