基于React的Chrome扩展:chrome.runtime.sendMessage响应undefined及接收端不存在问题
Chrome扩展开发问题:首次加载/重载时background.js响应undefined及连接错误
我用React开发Chrome扩展,开发模式下首次加载或重载时,background.js里出现响应undefined(错误信息:Error retrieving HTML content: undefined),同时报错:Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
项目结构
scraper ├── dist/ ├── public/ │ ├── images/ │ ├── index.html │ └── manifest.json └── src/ ├── extensions/ │ ├── background.js │ └── contentScript.js ├── App.tsx └── utils/ └── chrome-api.js
相关代码文件
App.tsx
import React, { useEffect } from 'react'; import './App.css'; import { useRecoilState } from 'recoil'; import messagesState from './states/messages'; import { isValidChromeRuntime } from './utils/chrome-api'; function App() { const [htmlContent, setHtmlContent] = useRecoilState(messagesState); const scrapeTab = () => { if (chrome && chrome.runtime) { chrome.runtime.sendMessage({ action: 'getHTML' }, function (response) { console.log(response); if (response) { setHtmlContent(response); } else { console.error('Error retrieving HTML content:', response); } }); } else { console.error( 'Chrome API is not available. Make sure this component is running in a Chrome extension context.', chrome ); } }; useEffect(() => { if (isValidChromeRuntime()) { scrapeTab(); } else { console.log('here'); } }, []); return ( <div className="App"> <h1>Slack scraper</h1> {JSON.stringify(htmlContent)} </div> ); } export default App;
background.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { if (request.action === 'getHTML') { chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { if (tabs.length) { chrome.tabs.sendMessage( tabs[0].id, { action: 'getHTML' }, function (response) { if (response) { sendResponse(response); } else { console.error('Error retrieving HTML content:', response); } } ); } }); return true; } });
contentScript.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { if (request.action === 'getHTML') { const messages = scrapeTheDoc(); sendResponse(messages); } }); const scrapeTheDoc = () => { const container = document.querySelector('.p-workspace__primary_view_body'); return container.toString(); }; document.addEventListener('scroll', function () { chrome.runtime.sendMessage({ action: 'scrollEvent' }); });
manifest.json
{ "manifest_version": 3, "name": "scraper", "version": "1.0", "description": "Scraper", "host_permissions": ["<all_urls>"], "icons": { "16": "images/logo.png", "32": "images/logo.png", "180": "images/logo.png", "192": "images/logo.png", "512": "images/logo.png" }, "offline_enabled": true, "permissions": ["storage", "activeTab", "contextMenus", "tabs"], "background": { "service_worker": "background.js" }, "content_scripts": [ { "matches": ["<all_urls>"], "js": ["contentScript.js"] } ], "action": { "default_popup": "index.html" } }
chrome-api.js
export const isValidChromeRuntime = () => { return chrome.runtime && !!chrome.runtime.getManifest(); };
webpack.config.js
const HTMLPlugin = require('html-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const path = require('path'); module.exports = { entry: { background: './src/extensions/background.js', contentScript: './src/extensions/contentScript.js', popup: './src/index.tsx', }, mode: 'production', module: { rules: [ { test: /\.(ts|tsx)$/, use: 'babel-loader', exclude: /node_modules/, }, { test: /\.css$/, use: ['style-loader', 'css-loader'], exclude: /node_modules/, }, ], }, resolve: { extensions: ['.tsx', '.ts', '.js', '.jsx'], }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js', }, plugins: [ new HTMLPlugin({ template: './public/index.html', chunks: ['popup'], filename: 'index.html', }), new CopyWebpackPlugin({ patterns: [ { from: './public', to: '', globOptions: { ignore: ['**/index.html'], }, }, ], }), ], };
问题原因及解决方法
核心原因
- 内容脚本未加载完成:首次加载/重载扩展时,popup立即发送消息,但当前标签页的content script还没注入完成,导致background向tab发送消息时没有接收端,触发连接错误。
- 未处理错误回调:
chrome.tabs.sendMessage失败时未捕获runtime.lastError,导致未处理的错误警告。 - DOM元素判断缺失:
scrapeTheDoc中直接调用container.toString(),若元素不存在会报错,进而导致响应异常。
具体修复步骤
1. 增加消息发送重试机制
修改App.tsx中的scrapeTab函数,当首次发送失败时重试,等待content script加载完成:
const scrapeTab = () => { if (chrome && chrome.runtime) { const sendMessageWithRetry = (attempts = 3) => { chrome.runtime.sendMessage({ action: 'getHTML' }, function (response) { if (chrome.runtime.lastError) { if (attempts > 0) { setTimeout(() => sendMessageWithRetry(attempts - 1), 500); } else { console.error('多次重试后仍无法获取HTML:', chrome.runtime.lastError); } return; } console.log(response); setHtmlContent(response || '无返回内容'); }); }; sendMessageWithRetry(); } else { console.error('Chrome API不可用,请确认运行环境为Chrome扩展'); } };
2. 完善background.js的错误处理
在chrome.tabs.sendMessage回调中捕获错误,避免未处理的runtime.lastError:
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { if (request.action === 'getHTML') { chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { if (tabs.length) { chrome.tabs.sendMessage( tabs[0].id, { action: 'getHTML' }, function (response) { if (chrome.runtime.lastError) { console.error('向content script发送消息失败:', chrome.runtime.lastError); sendResponse(null); return; } sendResponse(response || null); } ); } else { sendResponse(null); } }); return true; } });
3. 修复contentScript.js的DOM元素处理
避免元素不存在时调用toString()导致报错:
const scrapeTheDoc = () => { const container = document.querySelector('.p-workspace__primary_view_body'); return container ? container.outerHTML : '目标元素未找到'; };
4. 可选:扩展重载后强制注入content script
在background.js中添加扩展安装/更新监听,重新注入content script到当前标签页,确保重载后立即生效:
chrome.runtime.onInstalled.addListener(() => { chrome.tabs.query({ url: '<all_urls>' }, (tabs) => { tabs.forEach(tab => { if (tab.id) { chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['contentScript.js'] }); } }); }); });
同时需要在manifest.json的permissions中添加scripting权限:
"permissions": ["storage", "activeTab", "contextMenus", "tabs", "scripting"],
内容的提问来源于stack exchange,提问作者user24455281
相关产品推荐
相关产品推荐

