You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何让Chrome扩展的内容脚本在页面读取变量前修改该变量

如何让Chrome扩展的内容脚本在页面读取变量前修改该变量

嘿,这个需求我太熟悉了!要在页面读取window.gameData.today.answers之前修改它,核心就是抢在页面脚本执行/读取变量的时机前动手,结合你用的"world": "MAIN"配置,咱们可以这么来:

一、优先方案:用document_start + 属性拦截器(最稳妥)

因为你用了MAIN世界的内容脚本,意味着你的脚本能直接访问页面的window对象,那咱们可以利用属性的getter/setter来“劫持”变量的赋值过程,具体步骤:

  1. 调整manifest里的内容脚本配置
    确保你的内容脚本设置了"run_at": "document_start",这样它会在页面任何资源(包括内联脚本)加载执行前就跑起来:

    // manifest.json片段
    "content_scripts": [
      {
        "matches": ["*://你的目标域名/*"],
        "js": ["your-content-script.js"],
        "world": "MAIN",
        "run_at": "document_start"
      }
    ]
    
  2. 在内容脚本里设置拦截逻辑
    咱们给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加载时修改里面的内联脚本:

  1. 在manifest里添加权限
    需要声明webRequestwebRequestBlocking权限,以及目标域名的主机权限:

    // manifest.json片段
    "permissions": [
      "webRequest",
      "webRequestBlocking",
      "*://你的目标域名/*"
    ]
    
  2. 在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

火山引擎 最新活动