11import { URL_MATCH } from "@/utils/constant" ;
22import { cross } from "@/utils/global" ;
33import { logger } from "@/utils/logger" ;
4+ import { CODE_PREFIX , CODE_SUFFIX } from "../utils/constant" ;
45
5- export const implantScript = ( ) => {
6- /** RUN INJECT SCRIPT IN DOCUMENT START **/
6+ export const importScript = ( ) => {
77 // #IFDEF CHROMIUM
88 // https://bugs.chromium.org/p/chromium/issues/detail?id=634381
99 // https://stackoverflow.com/questions/75495191/chrome-extension-manifest-v3-how-to-use-window-addeventlistener
@@ -35,6 +35,7 @@ export const implantScript = () => {
3535 return void 0 ;
3636 }
3737 if ( tabId && cross . scripting ) {
38+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript
3839 cross . scripting . executeScript ( {
3940 target : { tabId : tabId , allFrames : true } ,
4041 files : [ process . env . INJECT_FILE + ".js" ] ,
@@ -49,8 +50,8 @@ export const implantScript = () => {
4950 // #IFDEF GECKO
5051 logger . info ( "Register Inject Scripts By Content Script Inline Code" ) ;
5152 // 使用 cross.tabs.executeScript 得到的 window 是 content window
52- // 此时就必须要使用 Inject Script 的方式才能正常注入脚本
53- // 然而这种方式就会受到 CSP 的限制, 因此在这里处理 CSP 问题
53+ // 此时就必须要使用 inject script 的方式才能正常注入脚本
54+ // 然而这种方式就会受到 content security policy 策略的限制
5455 // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/onHeadersReceived
5556 chrome . webRequest . onHeadersReceived . addListener (
5657 res => {
@@ -66,7 +67,7 @@ export const implantScript = () => {
6667 const types = value . split ( ";" ) . map ( it => it . trim ( ) ) ;
6768 const target : string [ ] = [ ] ;
6869 // CSP 不支持多个 nonce, 但可以配置多个 hash
69- // 这里的 HASH 会在 WrapperCodePlugin 中计算并替换资源
70+ // 这里的 hash 会在编译时计算并替换资源
7071 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
7172 const hashed = "'sha256-${CSP-HASH}'" ;
7273 for ( const item of types ) {
@@ -77,40 +78,51 @@ export const implantScript = () => {
7778 }
7879 target . push ( item ) ;
7980 }
80- // 覆盖原有的响应头, 但扩展的 CSP 总是更倾向于更加严格的模式
81+ // 覆盖原有的响应头, 扩展的 CSP 总是更倾向于更加严格的模式
8182 // 实际测试中仅有完全抹除标头时, 才可以解决冲突的问题
8283 res . responseHeaders [ i ] . value = target . join ( ";" ) ;
83- // 如果存在 nonce 的配置, 则尝试注入 Inject Script
84+ // 存在 CSP 时尝试直接在 content script 中执行
85+ let code = [
86+ `if (window["${ process . env . INJECT_FILE } "] && document instanceof XMLDocument === false) {` ,
87+ ` window["${ process . env . INJECT_FILE } "]();` ,
88+ `};` ,
89+ ] . join ( "\n" ) ;
90+ // 如果存在 blob: 的配置 尝试以 blob src 的形式注入
91+ if ( / s c r i p t - s r c [ - \w ' ] + b l o b : / . test ( value ) ) {
92+ code = [
93+ CODE_PREFIX ,
94+ `script.src = URL.createObjectURL(new Blob([code]));` ,
95+ CODE_SUFFIX ,
96+ ] . join ( "\n" ) ;
97+ }
98+ // 如果存在 nonce 的配置 则会尝试以 nonce 的形式注入
8499 const nonce = / ' n o n c e - ( [ - + / = \w ] + ) ' / . exec ( value ) ;
85100 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 } ) ;
101+ code = [
102+ CODE_PREFIX ,
103+ `script.nonce = "${ nonce [ 1 ] } ";` ,
104+ `script.innerText = code` ,
105+ CODE_SUFFIX ,
106+ ] . join ( "\n" ) ;
113107 }
108+ const onUpdate = ( _ : number , changeInfo : chrome . tabs . TabChangeInfo ) => {
109+ if ( changeInfo . status !== "loading" ) return void 0 ;
110+ // https://developer.mozilla.org/zh-CN/docs/Mozilla/Add-ons/WebExtensions/API/tabs/executeScript
111+ cross . tabs
112+ . executeScript ( res . tabId , {
113+ allFrames : false ,
114+ code : code ,
115+ runAt : "document_start" ,
116+ frameId : res . frameId ,
117+ } )
118+ . catch ( err => {
119+ if ( __DEV__ ) logger . warning ( "Inject Script" , err ) ;
120+ } ) ;
121+ cross . tabs . onUpdated . removeListener ( onUpdate ) ;
122+ } ;
123+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/onUpdated
124+ // @ts -expect-error filter params
125+ cross . tabs . onUpdated . addListener ( onUpdate , { tabId : res . tabId } ) ;
114126 }
115127 // 返回修改后的响应头配置
116128 return {
0 commit comments