@@ -45,13 +45,12 @@ export const implantScript = () => {
4545 } ) ;
4646 }
4747 // #ENDIF
48+
4849 // #IFDEF GECKO
4950 logger . info ( "Register Inject Scripts By Content Script Inline Code" ) ;
5051 // 使用 cross.tabs.executeScript 得到的 window 是 content window
5152 // 此时就必须要使用 Inject Script 的方式才能正常注入脚本
5253 // 然而这种方式就会受到 CSP 的限制, 因此在这里处理 CSP 问题
53- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
54- // https://developer.mozilla.org/zh-CN/docs/Mozilla/Add-ons/WebExtensions/API/tabs/executeScript
5554 // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/onHeadersReceived
5655 chrome . webRequest . onHeadersReceived . addListener (
5756 res => {
@@ -66,7 +65,9 @@ export const implantScript = () => {
6665 const value = res . responseHeaders [ i ] . value || "" ;
6766 const types = value . split ( ";" ) . map ( it => it . trim ( ) ) ;
6867 const target : string [ ] = [ ] ;
69- // 这里的 HASH 在 WrapperCodePlugin 中计算并替换资源
68+ // CSP 不支持多个 nonce, 但可以配置多个 hash
69+ // 这里的 HASH 会在 WrapperCodePlugin 中计算并替换资源
70+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
7071 const hashed = "'sha256-${CSP-HASH}'" ;
7172 for ( const item of types ) {
7273 const [ type , ...rest ] = item . split ( " " ) ;
@@ -76,10 +77,42 @@ export const implantScript = () => {
7677 }
7778 target . push ( item ) ;
7879 }
79- // 这里存在一个问题, 扩展的 CSP 总是更倾向于更加严格的模式
80+ // 覆盖原有的响应头, 但扩展的 CSP 总是更倾向于更加严格的模式
8081 // 实际测试中仅有完全抹除标头时, 才可以解决冲突的问题
8182 res . responseHeaders [ i ] . value = target . join ( ";" ) ;
83+ // 如果存在 nonce 的配置, 则尝试注入 Inject Script
84+ const nonce = / ' n o n c e - ( [ - + / = \w ] + ) ' / . exec ( value ) ;
85+ if ( nonce && nonce [ 1 ] ) {
86+ const nonceId = nonce [ 1 ] ;
87+ const code = `
88+ if (window["${ process . env . INJECT_FILE } "] && document instanceof XMLDocument === false) {
89+ const script = document.createElementNS("http://www.w3.org/1999/xhtml", "script");
90+ script.setAttribute("type", "text/javascript");
91+ script.innerText = \`;(\${window["${ process . env . INJECT_FILE } "].toString()})();\`;
92+ script.nonce = "${ nonceId } ";
93+ document.documentElement.appendChild(script);
94+ script.onload = () => script.remove();
95+ };` ;
96+ const onUpdate = ( _ : number , changeInfo : chrome . tabs . TabChangeInfo ) => {
97+ if ( changeInfo . status !== "loading" ) return void 0 ;
98+ // https://developer.mozilla.org/zh-CN/docs/Mozilla/Add-ons/WebExtensions/API/tabs/executeScript
99+ cross . tabs
100+ . executeScript ( res . tabId , {
101+ allFrames : true ,
102+ code : code ,
103+ runAt : "document_start" ,
104+ } )
105+ . catch ( err => {
106+ if ( __DEV__ ) logger . warning ( "Inject Script" , err ) ;
107+ } ) ;
108+ cross . tabs . onUpdated . removeListener ( onUpdate ) ;
109+ } ;
110+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/onUpdated
111+ // @ts -expect-error filter params
112+ cross . tabs . onUpdated . addListener ( onUpdate , { tabId : res . tabId } ) ;
113+ }
82114 }
115+ // 返回修改后的响应头配置
83116 return {
84117 responseHeaders : res . responseHeaders ,
85118 } ;
0 commit comments