Chrome扩展开发报错:无法建立连接,接收端不存在问题求助
Chrome扩展问题:页面抓取与点击功能报错解决
问题场景
开发了一款Chrome扩展,用于抓取example.com网站并在内部页面执行点击功能。新标签页能成功创建,但持续出现两个错误:
- Port disconnected.Content script may not be injected.
- Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
扩展文件代码
1. popup.html
<link href="popup.css" rel="stylesheet" type="text/css" /> <script type="module" src="popup.js"></script> <div class="container"> <button class="btnStart" id="btnStart">Start</button> </div>
2. popup.js
import { getActiveTabURL } from "./utils.js"; document.addEventListener("DOMContentLoaded", async () => { const activeTab = await getActiveTabURL(); if (!activeTab.url.includes("example.com")) { const container = document.querySelector(".container"); container.innerHTML = '<div class="title">This is not a example website page.</div>'; return; } else { document.getElementById('btnStart').addEventListener('click', function () { chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { const tabId = tabs[0].id; chrome.tabs.sendMessage(tabId, { action: "BEGIN", message: "BEGIN", urlPath: activeTab.url, tabId: tabId }, function (response) { console.log("0) outer response in popup.js main script file") console.log(response); chrome.runtime.sendMessage({ tabId: response.tabId, message: "START", urlPath: response.urlPath }, function (inner) { console.log("1) inner response in popup.js main script file") console.log(inner); }); }); }); }); } });
3. contentScript.js
const tryConnect = () => { chrome.runtime.onMessage.addListener(async (msg, sender, sendResponse) => { if (msg.action === "scrapeAndClick") { console.log("Scraping and Clicking..."); const messageButtonParent = document.querySelector(".profile-action-compose-option"); const messageButton = messageButtonParent ? messageButtonParent.querySelector("button") : null; if (messageButton) { console.log(messageButton.innerText); } else { console.error("Button not found"); } sendResponse({ complete: true, result: "Scrape and click completed" }); } if (msg.action === "BEGIN") { const urlPath = msg.urlPath; const tabId = msg.tabId; const activeTabPageResultsContainer = document.getElementsByClassName("reusable-search__result-container"); let individualResultContainer; let individualProfileContainer; const profileLinks = []; for (var i = 0; i < activeTabPageResultsContainer.length; i++) { individualResultContainer = activeTabPageResultsContainer[i]; individualProfileContainer = individualResultContainer.querySelector('.app-aware-link'); profileLinks.push(individualProfileContainer.getAttribute("href")); } const item = profileLinks[0]; console.log("Profile links collected:", { complete: true, result: "Profile links collected", tabId: tabId, message: "START", urlPath: item }); sendResponse({ complete: true, result: "Profile links collected", tabId: tabId, message: "START", urlPath: item }); } }); }; setTimeout(tryConnect, 1000);
4. background.js
chrome.runtime.onMessage.addListener((obj, sender, sendResponse) => { const message = obj.message; const urlPath = obj.urlPath; if (message === "START") { chrome.tabs.create({ url: urlPath, active: false }, function (tab) { console.log(`new tab created dynamically with id: ${tab.id}`); return new Promise((resolve, reject) => { chrome.tabs.sendMessage(tab.id, { action: "scrapeAndClick", message: "scrapeAndClick", urlPath: urlPath, tabId: tab.id }, function (response) { if (response && response.complete) { console.log(response); resolve(response); } else { console.error('Error in scraping and clicking or response is undefined:', response); reject(response); } }); }); }); return true; } return Promise.resolve(); });
5. manifest.json
{ "name": "Example page scraping", "version": "0.0.1", "description": "Scraping and submitting forms", "permissions": [ "storage", "tabs" ], "host_permissions": [ "https://*.example.com/*" ], "externally_connectable": { "matches": [ "*://*.example.com/*" ] }, "background": { "service_worker": "background.js" }, "content_scripts": [ { "matches": [ "*://*.example.com/*" ], "js": [ "contentScript.js" ] } ], "web_accessible_resources": [ { "resources": [ "assets/icon-16_x_16.png", "assets/icon-24_x_24.png", "assets/icon-32_x_32.png" ], "matches": [ "https://*.example.com/*" ] } ], "action": { "default_icon": { "16": "assets/icon-16_x_16.png", "24": "assets/icon-24_x_24.png", "32": "assets/icon-32_x_32.png" }, "default_title": "Example Scaping and Submission", "default_popup": "popup.html" }, "manifest_version": 3 }
错误原因与解决方案
核心问题分析
两个错误本质是新标签页创建后立即发送消息,但内容脚本还未完成注入/页面未加载完成,导致接收端不存在。另外还有几个细节问题:
- background.js中创建标签后立即调用
sendMessage,此时新标签页的DOM和内容脚本都未加载完毕 - contentScript.js用
setTimeout延迟注册消息监听,可能导致早期消息丢失 - 异步响应处理不符合Chrome扩展的规范要求
具体修复步骤
1. 修复background.js:等待标签加载完成再发送消息
使用chrome.tabs.onUpdated监听标签加载状态,直到页面加载完成(status为complete)再发送消息:
chrome.runtime.onMessage.addListener((obj, sender, sendResponse) => { const message = obj.message; const urlPath = obj.urlPath; if (message === "START") { chrome.tabs.create({ url: urlPath, active: false }, (tab) => { console.log(`new tab created dynamically with id: ${tab.id}`); // 监听标签加载完成 const listener = (tabId, changeInfo) => { if (tabId === tab.id && changeInfo.status === 'complete') { chrome.tabs.onUpdated.removeListener(listener); // 此时发送消息 chrome.tabs.sendMessage(tab.id, { action: "scrapeAndClick" }, (response) => { if (response?.complete) { console.log(response); sendResponse(response); } else { console.error('Scraping failed:', response); sendResponse({ complete: false, error: 'Failed to scrape' }); } }); } }; chrome.tabs.onUpdated.addListener(listener); }); // 标记异步响应 return true; } return Promise.resolve(); });
2. 修复contentScript.js:移除不必要延迟,规范消息监听
直接注册消息监听,不需要延迟;同时处理异步场景时返回true:
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { if (msg.action === "scrapeAndClick") { // 异步执行操作,确保元素加载完成 const performAction = async () => { console.log("Scraping and Clicking..."); // 短延迟确保动态元素加载,也可替换为MutationObserver监听元素 await new Promise(resolve => setTimeout(resolve, 500)); const messageButtonParent = document.querySelector(".profile-action-compose-option"); const messageButton = messageButtonParent?.querySelector("button"); if (messageButton) { console.log(messageButton.innerText); // 执行点击操作 messageButton.click(); sendResponse({ complete: true, result: "Scrape and click completed" }); } else { console.error("Button not found"); sendResponse({ complete: false, error: "Button not found" }); } }; performAction(); // 标记异步响应 return true; } if (msg.action === "BEGIN") { const tabId = msg.tabId; const activeTabPageResultsContainer = document.getElementsByClassName("reusable-search__result-container"); const profileLinks = []; for (const container of activeTabPageResultsContainer) { const profileLink = container.querySelector('.app-aware-link'); if (profileLink) profileLinks.push(profileLink.getAttribute("href")); } const item = profileLinks[0]; console.log("Profile links collected:", item); sendResponse({ complete: true, result: "Profile links collected", tabId, urlPath: item }); } });
3. 优化popup.js:添加错误处理逻辑
避免因响应异常导致后续逻辑崩溃:
import { getActiveTabURL } from "./utils.js"; document.addEventListener("DOMContentLoaded", async () => { const activeTab = await getActiveTabURL(); if (!activeTab.url.includes("example.com")) { const container = document.querySelector(".container"); container.innerHTML = '<div class="title">This is not an example website page.</div>'; return; } document.getElementById('btnStart').addEventListener('click', () => { chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { const tabId = tabs[0].id; chrome.tabs.sendMessage(tabId, { action: "BEGIN" }, (response) => { if (chrome.runtime.lastError) { console.error("Failed to send BEGIN message:", chrome.runtime.lastError); return; } console.log("0) outer response in popup.js:", response); if (response?.complete && response.urlPath) { chrome.runtime.sendMessage({ message: "START", urlPath: response.urlPath }, (inner) => { if (chrome.runtime.lastError) { console.error("Failed to send START message:", chrome.runtime.lastError); return; } console.log("1) inner response in popup.js:", inner); }); } else { console.error("Invalid response from content script:", response); } }); }); }); });
额外注意事项
- 确保
host_permissions覆盖所有需要访问的example.com子域名和路径 - 对于动态加载的页面元素,建议使用
MutationObserver替代固定延迟,确保元素存在后再操作 - 调试时可在Chrome扩展管理页开启"开发者模式",查看背景页和内容脚本的控制台日志
内容的提问来源于stack exchange,提问作者Goofy Programmer
相关产品推荐
相关产品推荐

