返回搜索
Chrome 扩展在部分页面出现循环刷新与脚本加载失败的排障记录
34次浏览11/20/2025
chrome-extensioncontent-scriptjavascriptmanifest-v3web-accessible-resources

问题描述

"智能页面总结器" Chrome 扩展在大多数页面运行正常,但在部分页面出现: - 页面循环不停刷新或行为异常。 - 控制台出现脚本加载相关报错,例如: - `GET chrome-extension://invalid/ net::ERR_FAILED` - `资源加载失败: chrome-extension://xxx/js/selector-recorder.js` - `资源加载失败: chrome-extension://xxx/js/hover-highlight.js` - 有时还伴随 Content Script 重复初始化、事件监听器重复注册。

根本原因

该问题由多种因素叠加导致: 1. **Manifest V3 资源声明缺失** - 通过 `chrome.runtime.getURL()` 动态加载的 `js/selector-recorder.js`、`js/hover-highlight.js` 未在 `web_accessible_resources` 中声明。 - 在 Manifest V3 中,这类资源不声明就无法被 content script 正常访问,导致资源请求返回 `chrome-extension://invalid/`。 2. **Content Script 在多 frame 中重复执行** - `manifest.json` 中配置了 `all_frames: true`。 - 没有防重复初始化的机制,导致在多个 frame 中多次创建 `ContentExtractor`、多次注册 `chrome.runtime.onMessage` 监听器。 3. **特殊页面处理不当** - 在 `chrome://`、`edge://`、`about:` 等特殊协议页面,或其他扩展页面中,`chrome.runtime` 行为与普通网页不同。 - 缺少"跳过这些页面"的逻辑,导致在不受支持的环境里仍尝试加载脚本。 4. **缺少 chrome.runtime 可用性检查** - 直接调用 `chrome.runtime.getURL()`,未检查 `chrome` 和 `chrome.runtime` 是否存在。 - 在部分环境下返回 `invalid` URL 或抛出异常。 5. **消息监听器重复注册** - `ContentExtractor.init()` 在错误路径(异常重试等)下可能被多次调用。 - 未加"已注册标记"的监听器会重复处理消息,引发难以追踪的副作用。

解决方案

1. **在 manifest.json 中声明 web_accessible_resources** ```json { "web_accessible_resources": [ { "resources": [ "js/selector-recorder.js", "js/hover-highlight.js" ], "matches": ["<all_urls>"] } ] } ``` 2. **为 Content Script 添加防重复初始化标记** ```javascript // 防止重复初始化 if (typeof window !== 'undefined' && !window.__CONTENT_EXTRACTOR_INITIALIZED__) { window.__CONTENT_EXTRACTOR_INITIALIZED__ = true; const isTopFrame = window === window.top; const isSpecial = isSpecialPage(); if (isTopFrame || !isSpecial) { try { const contentExtractor = new ContentExtractor(); window.__contentExtractor = contentExtractor; } catch (error) { console.error('ContentExtractor 初始化失败:', error); } } else { console.log('在特殊页面或子 frame 中,跳过 ContentExtractor 初始化'); } } else { console.log('ContentExtractor 已初始化,跳过重复初始化'); } ``` 3. **为 HoverHighlighter 添加防重复初始化标记** ```javascript function initializeHoverHighlighter() { try { if (typeof window !== 'undefined' && !window.__HOVER_HIGHLIGHTER_INITIALIZED__) { window.__HOVER_HIGHLIGHTER_INITIALIZED__ = true; if (!window.hoverHighlighter) { if (typeof HoverHighlighter !== 'undefined') { window.hoverHighlighter = new HoverHighlighter(); console.log('悬停高亮系统初始化成功'); } else { window.hoverHighlighter = { activate: () => {}, deactivate: () => {}, isActive: false, highlightElement: () => {}, removeHighlight: () => {} }; } } } } catch (error) { if (typeof window !== 'undefined' && !window.hoverHighlighter) { window.hoverHighlighter = { activate: () => {}, deactivate: () => {}, isActive: false, highlightElement: () => {}, removeHighlight: () => {} }; } } } // 只在顶层 frame 中初始化 if (typeof window !== 'undefined' && window === window.top) { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { initializeHoverHighlighter(); }); } else { initializeHoverHighlighter(); } } ``` 4. **添加 chrome.runtime 可用性检查** ```javascript function isChromeRuntimeAvailable() { try { return typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function'; } catch (e) { return false; } } ``` 5. **添加特殊页面检测** ```javascript function isSpecialPage() { try { const url = window.location.href; const isSpecialProtocol = /^(chrome|edge|about|moz-extension|chrome-extension):\/\//i.test(url); if (!isSpecialProtocol) return false; if (url.startsWith('chrome-extension://')) { if (isChromeRuntimeAvailable()) { try { const extensionId = chrome.runtime.id; return !url.startsWith('chrome-extension://' + extensionId); } catch (e) { return true; } } return true; } return true; } catch (e) { return true; } } ``` 6. **增强外部脚本加载错误处理** ```javascript function loadExternalScripts() { return new Promise((resolve) => { if (!isChromeRuntimeAvailable()) { console.warn('chrome.runtime 不可用,跳过外部脚本加载'); resolve(); return; } if (isSpecialPage()) { console.warn('特殊页面,跳过外部脚本加载'); resolve(); return; } let loadedCount = 0; const totalScripts = 2; let hasError = false; const checkCompletion = () => { loadedCount++; if (loadedCount === totalScripts) { if (hasError) { console.warn('部分外部脚本加载失败,但将继续运行'); } resolve(); } }; // 此处分别加载 selector-recorder.js 和 hover-highlight.js, // 每个都要检查 URL 是否包含 'invalid',并处理 onerror 和 document.head 不存在的情况 // (具体实现略,同当前项目中的修复代码)。 }); } ``` 7. **防止消息监听器重复注册** ```javascript init() { // 防止重复注册消息监听器 if (this.messageListenerRegistered) { console.log('消息监听器已注册,跳过重复注册'); return; } this.messageListenerRegistered = true; chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { // 正常消息处理逻辑 }); } ``` 上述修改后,问题页面不再出现循环刷新和脚本加载失败的报错。

背景信息

- 技术栈:Chrome Extension Manifest V3,JavaScript。 - 相关文件: - `manifest.json`:定义 content scripts、web_accessible_resources。 - `chrome-extension/js/content.js`:内容脚本,负责内容提取与交互。 - `selector-recorder.js` / `hover-highlight.js`:动态加载的辅助脚本。