Chrome 扩展开发实录:从抓狂到曙光,我的 URL 收集器 Debug 血泪史

最近看公司的新项目项目,代码看得头晕眼花。为了快速理清脉络,我决定从网络请求入手,毕竟 F12 的 Network 面板是我的老朋友了。但问题来了,随便一个页面就几十上百个请求,看得眼花缭乱,很多还不是我关心的接口请求(Fetch/XHR)。

于是,一个想法诞生了:做一个 Chrome 扩展,专门捕获 Fetch 和 XHR 请求,并能方便地复制或导出数据,最好能直接喂给 AI 进行分析!

听起来很简单,对吧?呵呵,事实证明,我还是太年轻了...

第一回合:初生牛犊不怕虎 —— 简单脚本的尝试

最开始的想法很简单:写个 JavaScript 脚本,重写 window.fetch​ 和 XMLHttpRequest.prototype.send​,然后把捕获到的数据存起来,再提供个全局函数导出。

// (概念代码)
const _originalFetch = window.fetch;
const requests = [];
window.fetch = async function(...args) {
    // ... 捕获信息 ...
    requests.push(requestInfo);
    // ... 调用原始 fetch ...
    // ... 处理响应 ...
    // ... 更新 requestInfo ...
}
// 类似地处理 XHR...
window.getRequests = () => JSON.stringify(requests);

结果: 能用,但很麻烦。每次都要手动复制代码到控制台,或者做成 Bookmarklet。而且很容易被页面本身的脚本干扰或覆盖。Pass!

第二回合:走向正规军 —— 标准内容脚本注入

是时候上 Chrome 扩展了!标准的做法:

  1. ​manifest.json​:声明权限 (scripting​, activeTab​ 等),设置 background script,并用 content_scripts​ 在 document_start​ 时注入 content_script.js​ 到所有 frame。
  2. ​content_script.js​:包含核心的 fetch​/XHR​ 覆盖逻辑,捕获数据后通过 chrome.runtime.sendMessage​ 发给 background。
  3. ​background.js​:监听消息,存储数据(一开始用简单数组),并处理 popup 的请求。
  4. ​popup.html​/popup.js​:提供 UI,显示计数,提供复制、导出、清空按钮。

信心满满地加载扩展,刷新目标页面... 咦?扩展图标上的徽章数字始终是 0!

打开 F12 控制台和扩展后台控制台:

  • 目标页面控制台显示 Content Script Injected​,确认脚本注入了。
  • 扩展后台控制台一片寂静。

猜想与 Debug 1: 难道是消息没发出去?在 content_script.js​ 的 sendMessage​ 前后加日志。

结果: 页面加载后,根本没有触发 sendMessage​ 前的日志!这说明我们覆盖的 fetch​/XHR​ 函数从未被调用!

第三回合:时序之谜与隔离之困

为什么覆盖了却不被调用?

猜想与 Debug 2: 是不是页面脚本运行太早,在我们覆盖之前就把原始函数引用存起来了?或者覆盖被页面后续脚本干掉了?

  • 尝试延迟执行: 在 content_script.js​ 里用 setTimeout​ 延迟 100ms 再执行覆盖逻辑。

    • 结果: 依然没有触发日志。
  • 尝试检查覆盖状态: 在覆盖前检查 window.fetch === originalFetch​。

    • 结果: 日志显示覆盖成功了,但依然没有触发。
  • 猜想 Frame 问题: 知乎这种复杂页面有很多 iframe。是不是脚本只在顶层生效?确认 manifest.json​ 里有 "all_frames": true​。尝试在 content_script.js​ 里移除防止重复初始化的标志,并加入日志区分顶层和 iframe。

    • 结果: 日志显示顶层和 iframe 都尝试了覆盖,但请求依然没有触发我们的拦截器。

“啊哈!”时刻(并不): 此时,一个幽灵般的概念浮现脑海 —— Chrome 扩展的隔离环境 (Isolated Worlds)。内容脚本的 JavaScript 环境和页面的主环境是隔离的。我们修改 window.fetch​ 可能只是修改了“隔离世界”里的 window​ 对象,页面实际调用的可能是主世界里那个“纯洁”的 fetch​!

第四回合:深入敌后 —— 脚本注入主世界

既然隔离环境不行,那就直接把代码注入到主世界!

策略:

  1. ​content_script.js​ 不再直接覆盖函数。
  2. 它定义一个包含所有拦截逻辑的字符串 interceptorCode​。
  3. 这个 interceptorCode​ 里的逻辑在捕获到数据后,使用 window.postMessage​ 将数据发送出去(因为它不能直接调用 chrome.runtime.sendMessage​)。
  4. ​content_script.js​ 创建一个

你可能感兴趣的:(chrome,前端,javascript)