You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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'],
          },
        },
      ],
    }),
  ],
};

问题原因及解决方法

核心原因

  1. 内容脚本未加载完成:首次加载/重载扩展时,popup立即发送消息,但当前标签页的content script还没注入完成,导致background向tab发送消息时没有接收端,触发连接错误。
  2. 未处理错误回调:chrome.tabs.sendMessage失败时未捕获runtime.lastError,导致未处理的错误警告。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 21:05:18