如何让Chrome扩展的内容脚本在页面读取变量前修改该变量
嘿,这个需求我太熟悉了!要在页面读取window.gameData.today.answers之前修改它,核心就是抢在页面脚本执行/读取变量的时机前动手,结合你用的"world": "MAIN"配置,咱们可以这么来:
一、优先方案:用document_start + 属性拦截器(最稳妥)
因为你用了MAIN世界的内容脚本,意味着你的脚本能直接访问页面的window对象,那咱们可以利用属性的getter/setter来“劫持”变量的赋值过程,具体步骤:
调整manifest里的内容脚本配置
确保你的内容脚本设置了"run_at": "document_start",这样它会在页面任何资源(包括内联脚本)加载执行前就跑起来:// manifest.json片段 "content_scripts": [ { "matches": ["*://你的目标域名/*"], "js": ["your-content-script.js"], "world": "MAIN", "run_at": "document_start" } ]在内容脚本里设置拦截逻辑
咱们给window.gameData定义一个setter,当页面执行window.gameData = {...}赋值时,先修改today.answers再把值存起来,后续页面读取到的就是修改后的内容了:// your-content-script.js (function() { // 用一个内部变量存真正的gameData值 let _actualGameData; // 给window.gameData设置拦截器 Object.defineProperty(window, 'gameData', { get() { // 页面读取时返回修改后的值 return _actualGameData; }, set(newValue) { // 页面赋值时,先修改我们要改的部分 if (newValue.today && Array.isArray(newValue.today.answers)) { // 替换成你想要的数组,比如这里改成自定义答案 newValue.today.answers = ["my-custom-word-1", "my-custom-word-2"]; } // 把修改后的值存到内部变量 _actualGameData = newValue; }, // 允许后续页面可能重新定义这个属性(避免报错) configurable: true, enumerable: true }); })();这个方法的妙处在于:不管页面什么时候给
window.gameData赋值,咱们都能第一时间拦截并修改,完美抢在页面读取之前。
二、备用方案:拦截并修改页面HTML(极端情况用)
如果上面的方案因为页面的特殊逻辑失效(比如页面用了非常奇怪的变量赋值方式),咱们可以直接在页面HTML加载时修改里面的内联脚本:
在manifest里添加权限
需要声明webRequest和webRequestBlocking权限,以及目标域名的主机权限:// manifest.json片段 "permissions": [ "webRequest", "webRequestBlocking", "*://你的目标域名/*" ]在background脚本里拦截并修改HTML
咱们监听页面的主请求,把返回的HTML里的window.gameData定义直接修改:// background.js chrome.webRequest.onBeforeResponse.addListener( async (details) => { // 只处理主页面请求 if (details.type !== 'main_frame') return; // 把响应体转换成文本 const responseText = await new Response(details.responseBody).text(); // 正则匹配内联脚本里的gameData定义,替换其中的answers数组 const modifiedHtml = responseText.replace( /window.gameData = ({.*?"today":{.*?"answers":\[.*?\],.*?}})/s, (match, gameDataStr) => { // 解析JSON并修改 const gameData = JSON.parse(gameDataStr); gameData.today.answers = ["custom-answer-1", "custom-answer-2"]; return `window.gameData = ${JSON.stringify(gameData)}`; } ); // 返回修改后的HTML,同时更新Content-Length头 return { responseHeaders: details.responseHeaders.map(header => { if (header.name.toLowerCase() === 'content-length') { header.value = String(modifiedHtml.length); } return header; }), responseBody: btoa(unescape(encodeURIComponent(modifiedHtml))) }; }, { urls: ["*://你的目标域名/*"] }, ["blocking", "responseBody"] );注意:这个方法比较“重”,而且依赖页面HTML的结构,如果页面更新了内联脚本的写法,正则可能失效,所以优先用第一个方案。
三、验证执行顺序
你可以在内容脚本里加console.log('我的扩展脚本执行了!'),在页面的内联脚本里加console.log('页面脚本执行了!'),打开Chrome控制台看输出顺序——只要你的脚本先打印,就说明拦截时机是对的。
备注:内容来源于stack exchange,提问作者Allen Cypher




