Chrome扩展拦截当前标签POST请求失败问题及方案咨询
问题描述
我们希望通过Chrome扩展读取/保存POST请求的请求体(payload):
- 场景:目标网页会偶尔向服务器提交POST请求。
- 需求:Chrome扩展的popup.html中有一个id为
startIntercept的按钮,点击后需将当前标签页下一次触发的POST请求的所有详情(请求头、请求体)保存到会话变量中,无需关注响应内容。 - 尝试方案:通过覆写
window.XMLHttpRequest.prototype.open方法实现,保存原open方法后创建新方法拦截请求,但注入startWork.js脚本后仍无法拦截请求,现咨询失败原因及更优实现方案。
现有代码
popup.js
let startIntercept = document.getElementById("startIntercept"); startIntercept.addEventListener("click", async () => { let [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ["startWork.js"], }); });
startWork.js
let oldXHROpen = window.XMLHttpRequest.prototype.open; console.log('script injected'); window.XMLHttpRequest.prototype.open = function() { this.addEventListener("load", function() { const responseBody = this.responseText; sessionStorage.setItem('payload', responseBody ); }); return oldXHROpen.apply(this, arguments); };
manifest.json
{ "name": "extTracker", "description": "want to save post request details", "version": "1.0", "manifest_version": 3, "background": { "service_worker": "background.js" }, "host_permissions": ["<all_urls>"], "permissions": ["storage", "activeTab", "scripting"], "web_accessible_resources": [{ "resources": ["/assets/media/blinkSd.mp3"], "matches": ["<all_urls>"], "use_dynamic_url": true }], "action": { "default_popup": "popup.html" } }
失败原因分析
- 脚本注入时机滞后:点击按钮注入脚本时,页面中可能已经初始化了
XMLHttpRequest实例,覆写只会影响之后新建的实例,无法拦截早于脚本注入的请求对象。 - 未捕获请求体且逻辑错位:当前代码监听
load事件只能获取响应内容,完全没处理请求体(payload),也没判断请求方法是否为POST,根本没命中需求核心。 - 遗漏fetch API拦截:现代网页大量使用
fetch而非XMLHttpRequest,你的代码只覆盖了XHR,漏掉了大部分可能的请求。 - 会话存储作用域限制:页面脚本和内容脚本虽共享
sessionStorage,但如果页面有iframe或跨源子页面,可能出现存储/读取失败的情况。
更优实现方案
方案一:改进内容脚本,同时拦截XHR和fetch
直接在页面主上下文拦截所有请求,确保捕获下一次POST请求体:
修改后的startWork.js
// 标记是否需要拦截下一次POST请求 let shouldInterceptNextPost = true; // 拦截XMLHttpRequest const oldXHROpen = window.XMLHttpRequest.prototype.open; const oldXHRSend = window.XMLHttpRequest.prototype.send; window.XMLHttpRequest.prototype.open = function(method, url) { this._method = method; this._url = url; // 记录请求头(需覆写setRequestHeader) this._requestHeaders = {}; const originalSetHeader = this.setRequestHeader; this.setRequestHeader = function(key, value) { this._requestHeaders[key] = value; originalSetHeader.apply(this, arguments); }; return oldXHROpen.apply(this, arguments); }; window.XMLHttpRequest.prototype.send = function(body) { if (shouldInterceptNextPost && this._method.toUpperCase() === 'POST') { const requestDetails = { method: this._method, url: this._url, headers: this._requestHeaders, requestBody: body }; sessionStorage.setItem('nextPostRequest', JSON.stringify(requestDetails)); shouldInterceptNextPost = false; } return oldXHRSend.apply(this, arguments); }; // 拦截fetch API const oldFetch = window.fetch; window.fetch = async function(resource, options = {}) { const method = (options.method || 'GET').toUpperCase(); if (shouldInterceptNextPost && method === 'POST') { const url = typeof resource === 'string' ? resource : resource.url; const headers = options.headers ? Object.fromEntries(options.headers.entries()) : {}; let requestBody = null; if (options.body) { requestBody = await (new Response(options.body)).text(); } const requestDetails = { method, url, headers, requestBody }; sessionStorage.setItem('nextPostRequest', JSON.stringify(requestDetails)); shouldInterceptNextPost = false; } return oldFetch.apply(this, arguments); }; console.log('拦截脚本已注入,等待下一次POST请求');
调整popup.js注入时机
确保脚本在页面初始化前注入,避免遗漏请求:
chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ["startWork.js"], world: 'MAIN', // 注入到页面主上下文,确保覆盖原生API runAt: 'document_start' // 在页面开始加载时注入 });
方案二:使用Chrome webRequest API(Manifest V3推荐)
无需注入内容脚本,通过后台服务工作者拦截请求,稳定性更强:
步骤1:更新manifest.json
添加必要权限:
{ "name": "extTracker", "description": "保存POST请求详情", "version": "1.0", "manifest_version": 3, "background": { "service_worker": "background.js" }, "host_permissions": ["<all_urls>"], "permissions": ["storage", "activeTab", "scripting", "webRequest", "webRequestBlocking"], "action": { "default_popup": "popup.html" } }
步骤2:编写background.js
let interceptActive = false; let targetTabId = null; // 拦截请求发送事件 chrome.webRequest.onBeforeRequest.addListener( (details) => { if (interceptActive && details.tabId === targetTabId && details.method === 'POST') { let requestBody = ''; // 解析请求体 if (details.requestBody?.raw) { requestBody = new TextDecoder().decode(details.requestBody.raw[0].bytes); } else if (details.requestBody?.formData) { requestBody = JSON.stringify(details.requestBody.formData); } // 保存到扩展会话存储 chrome.storage.session.set({ nextPostRequest: { method: details.method, url: details.url, headers: details.requestHeaders, requestBody: requestBody } }).then(() => { interceptActive = false; console.log('已捕获并保存POST请求详情'); }); } }, { urls: ["<all_urls>"], types: ["xmlhttprequest", "fetch"] }, ["requestBody", "blocking"] ); // 接收popup的启动指令 chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.action === 'startIntercept') { interceptActive = true; targetTabId = message.tabId; sendResponse({ status: 'intercept started' }); } });
步骤3:修改popup.js
let startIntercept = document.getElementById("startIntercept"); startIntercept.addEventListener("click", async () => { let [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); chrome.runtime.sendMessage({ action: 'startIntercept', tabId: tab.id }, (response) => { if (response.status === 'intercept started') { alert('已启动下一次POST请求拦截'); } }); });
方案优势
- 无需注入内容脚本,避开页面上下文隔离问题。
- 同时支持XHR和fetch请求,覆盖所有常见请求方式。
- 使用
chrome.storage.session存储数据,扩展内各组件可统一读取。
内容的提问来源于stack exchange,提问作者user2817235
相关产品推荐
相关产品推荐

