
本文介绍一种可靠方法:通过 content script 动态注入脚本到页面主执行环境(main world),从而真正拦截网页自身发起的 fetch 请求响应,绕过隔离沙箱限制。适用于 chrome、firefox 等主流浏览器。
本文介绍一种可靠方法:通过 content script 动态注入脚本到页面主执行环境(main world),从而真正拦截网页自身发起的 fetch 请求响应,绕过隔离沙箱限制。适用于 chrome、firefox 等主流浏览器。
在开发浏览器扩展时,一个常见但棘手的需求是:监听并修改网页自身(而非扩展代码)发出的 fetch 请求的响应数据。由于浏览器安全模型(如 Chrome 的“isolated world”机制),content script 运行在独立上下文中,无法直接覆盖或监听页面主线程中的 window.fetch —— 即使你重写了 content script 内的 fetch,也仅影响该脚本自身调用,对页面 JS 发起的请求完全无效。
✅ 正确解法是:将 patch 逻辑注入到页面的主 JavaScript 环境(main world)中,使其与网页原始代码共享同一全局作用域。这才能真正劫持所有由页面发起的 fetch 调用。
✅ 实现步骤(完整可运行方案)
1. 在 content_scripts 中动态注入脚本
在你的 content.js(或 manifest 中声明的 content script)中添加以下代码:
// 注入 attach.js 到页面主环境(main world)
const injectScript = () => {
const script = document.createElement('script');
script.src = chrome.runtime.getURL('scripts/attach.js');
script.onload = () => script.remove();
(document.head || document.documentElement).appendChild(script);
};
// 确保 DOM 已就绪再注入(避免 document.head 不存在)
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', injectScript);
} else {
injectScript();
}⚠️ 注意:
chrome.runtime.getURL()仅在 Chrome/Edge 等 Chromium 浏览器中可用;Firefox 需使用browser.runtime.getURL()(建议统一用chrome?.runtime?.getURL || browser?.runtime?.getURL兼容处理)。
2. 创建 scripts/attach.js(主世界执行)
将该文件放入扩展包根目录下的 scripts/ 文件夹,并在 manifest.json 中声明其可被访问(确保 "web_accessible_resources" 包含该路径):
{
"web_accessible_resources": [{
"resources": ["scripts/attach.js"],
"matches": ["<all_urls>"]
}]
}scripts/attach.js 内容如下(已增强健壮性与兼容性):
// attach.js —— 运行于页面主世界(main world)
(function () {
if (typeof window.fetch === 'undefined') return;
const originalFetch = window.fetch;
window.fetch = async function (...args) {
let response;
try {
response = await originalFetch(...args);
} catch (err) {
// 保留原始错误行为(如网络失败)
throw err;
}
// ✅ 关键:仅对 JSON 响应做克隆解析;跳过二进制/流式响应(如图片、PDF、stream)
const contentType = response.headers.get('content-type') || '';
if (!contentType.includes('application/json') && !contentType.includes('text/')) {
return response; // 不处理非文本响应,避免 clone() 失败
}
try {
const cloned = response.clone();
const data = await cloned.json();
// ? 在此处修改响应数据(示例:添加调试标记)
const modifiedData = {
...data,
_intercepted_by_extension: true,
_timestamp: Date.now()
};
// 返回伪造响应(保持原始 headers)
const headers = new Headers(response.headers);
// 若需修改 headers,请注意 CORS 限制(仅限同源或预检通过)
return new Response(JSON.stringify(modifiedData), {
status: response.status,
statusText: response.statusText,
headers
});
} catch (parseErr) {
// JSON 解析失败 → 原样返回(如 HTML、空响应等)
return response;
}
};
})();? 重要注意事项
-
CORS 限制依然存在:你无法读取或修改跨域响应体(除非目标服务明确允许
Access-Control-Expose-Headers并开启 CORS)。本方案仅对同源或 CORS 预检通过的请求有效。 -
避免无限递归:确保
attach.js中不触发新的fetch(或确保其不受 patch 影响),否则可能造成死循环。 -
性能考量:
response.clone().json()会消耗内存与时间,生产环境建议加响应大小限制(如检查Content-Lengthheader 或使用response.body流式判断)。 -
Firefox 兼容性:Firefox 默认允许 content script 注入到 main world(无需额外配置),但需确保
manifest.json中"run_at": "document_idle"或"document_start"合理设置;若遇问题,可尝试run_at: "document_start"+setTimeout(..., 0)延迟注入。 -
调试技巧:在
attach.js中使用console.log会显示在页面控制台(而非扩展后台),便于验证是否成功注入。
✅ 总结
要真正拦截网页自身的 fetch 响应,核心在于突破 content script 的执行隔离。通过动态 <script></script> 注入方式将 monkey patch 代码运行在页面主世界,是目前最稳定、兼容性最佳的实践方案。它不依赖 browser.tabs.executeScript({ runAt: 'document_start' })(该 API 在 content script 中不可用),也不受 window.fetch 覆盖范围限制,可精准作用于所有由页面 JS 发起的请求。
掌握这一模式后,你还能轻松扩展功能:记录请求链路、注入 mock 数据、实现离线缓存、甚至构建轻量级 API 调试面板。


















