Chrome扩展注入JS报错:无法读取undefined的'sendMessage'属性
问题:DOM注入脚本无法向Service Worker发送消息
在DOM中注入脚本后,尝试调用chrome.runtime.sendMessage向后台Service Worker发消息时直接报错,相关代码如下:
background.js(后台脚本)
chrome.runtime.onMessage.addListener((req, sender, sendRes) => { console.log('this has worked', req, sender, sendRes('this worked')); return true; });
inject.js(用于注入injected.js的内容脚本)
const s = document.createElement('script'); s.src = chrome.runtime.getURL('injected.js'); s.onload = function () { this.remove(); }; (document.head || document.documentElement).appendChild(s);
injected.js(注入到页面的脚本)
console.log('fetch interceptor started'); const { fetch: origFetch } = window; // 此处直接调用时立即报错 chrome.runtime.sendMessage({ works: 'This worked at least' }, (res) => console.log(res)); window.fetch = async (...args) => { const response = await origFetch(...args); const url = new URL(args[0].url); response .clone() .json() .then(async (body) => await sendMessageToBackground({ pathname, body })) .catch((err) => console.error(err)); return response; }; async function sendMessageToBackground(msg) { const res = await chrome.runtime.sendMessage(msg); console.log('result message', res); console.log('msg', msg); }
manifest.json相关配置
{ ... "permissions": ["storage", "activeTab", "scripting", "webRequest"], "host_permissions": ["https://REDACTED.com/*"], "manifest_version": 3, "background": { "service_worker": "background.js" }, "content_scripts": [ { "matches": ["https://REDACTED.com/*"], "run_at": "document_start", "js": ["inject.js"] } ], "web_accessible_resources": [{ "resources": ["injected.js"], "matches": ["https://REDACTED.com/*"] }], ... }
原因分析
注入到页面DOM的脚本属于页面上下文,和扩展的内容脚本、后台脚本处于不同的执行环境。页面上下文没有权限直接调用Chrome扩展的API(比如chrome.runtime.sendMessage),只有扩展自身的内容脚本、背景脚本等才能访问这些API,所以直接调用会报错。
解决方案
通过内容脚本作为中间代理,建立页面上下文和扩展后台的通信:页面脚本发送自定义事件给内容脚本,内容脚本调用扩展API和后台通信,再把结果返回给页面脚本。
修改后的inject.js(内容脚本)
// 注入页面脚本 const s = document.createElement('script'); s.src = chrome.runtime.getURL('injected.js'); s.onload = function () { this.remove(); }; (document.head || document.documentElement).appendChild(s); // 监听页面脚本发来的自定义事件 window.addEventListener('send-to-background', async (e) => { const { message } = e.detail; // 调用扩展API发送消息给后台 const response = await chrome.runtime.sendMessage(message); // 把结果发回页面脚本 window.dispatchEvent(new CustomEvent('background-response', { detail: { response } })); });
修改后的injected.js(页面注入脚本)
console.log('fetch interceptor started'); const { fetch: origFetch } = window; // 替换原有的直接调用,改为通过自定义事件通信 sendMessageToBackground({ works: 'This worked at least' }) .then(res => console.log(res)); window.fetch = async (...args) => { const response = await origFetch(...args); const url = new URL(args[0].url); response .clone() .json() .then(async (body) => await sendMessageToBackground({ pathname: url.pathname, body })) .catch((err) => console.error(err)); return response; }; // 封装消息发送函数,通过自定义事件和内容脚本通信 async function sendMessageToBackground(msg) { return new Promise((resolve) => { // 监听后台返回的结果 const handleResponse = (e) => { window.removeEventListener('background-response', handleResponse); resolve(e.detail.response); }; window.addEventListener('background-response', handleResponse); // 发送自定义事件给内容脚本 window.dispatchEvent(new CustomEvent('send-to-background', { detail: { message: msg } })); }); }
内容的提问来源于stack exchange,提问作者Spurious
相关产品推荐
相关产品推荐

