
chrome-extensions
热门使用 Manifest V3 最佳实践构建和发布 Chrome 扩展。每当用户要求创建、修改、调试或理解 Chrome 浏览器扩展、插件或任何涉及 Chrome 扩展 API 的内容时,使用此技能。触发词包括:'Chrome 扩展'、'浏览器扩展'、'manifest.json'、'内容脚本'、'service worker'(在浏览器上下文中)、'弹出窗口'(在浏览器扩展上下文中)、'侧边栏'、'chrome.* API'、'declarativeNetRequest'、'omnibox'、'上下文菜单'(在扩展上下文中)、'userScripts'、'用户脚本'、'脚本管理器',或任何请求构建与 Chrome 浏览器 UI 集成的功能。也适用于发布到 Chrome 网上应用店:'发布扩展'、准备扩展以发布、回应审核拒绝、编写权限理由或起草隐私政策。
使用 Manifest V3 最佳实践构建和发布 Chrome 扩展。每当用户要求创建、修改、调试或理解 Chrome 浏览器扩展、插件或任何涉及 Chrome 扩展 API 的内容时,使用此技能。触发词包括:'Chrome 扩展'、'浏览器扩展'、'manifest.json'、'内容脚本'、'service worker'(在浏览器上下文中)、'弹出窗口'(在浏览器扩展上下文中)、'侧边栏'、'chrome.* API'、'declarativeNetRequest'、'omnibox'、'上下文菜单'(在扩展上下文中)、'userScripts'、'用户脚本'、'脚本管理器',或任何请求构建与 Chrome 浏览器 UI 集成的功能。也适用于发布到 Chrome 网上应用店:'发布扩展'、准备扩展以发布、回应审核拒绝、编写权限理由或起草隐私政策。
Chrome 扩展
使用 Manifest V3 构建生产级 Chrome 扩展,并将其发布到 Chrome 网上应用店。
第一部分 — 构建扩展
强制规则
这些规则解决了导致扩展损坏的最常见原因。违反任何一条都会导致构建无法正常工作。
1. 图标:只引用你创建的文件 — 或者完全省略图标
❌ 错误 — 引用不存在的文件或对所有尺寸复用同一文件:
"icons": { "16": "icon.png", "48": "icon.png", "128": "icon.png" }
✅ 正确 — 每个尺寸都是独立文件,且像素尺寸正确:
"icons": { "16": "icons/icon-16.png", "48": "icons/icon-48.png", "128": "icons/icon-128.png" }
(其中 icon-16.png 为 16×16 像素,icon-48.png 为 48×48 像素,icon-128.png 为 128×128 像素)
✅ 也正确 — 如果无法生成真实的 PNG 文件,则从 manifest 中省略图标:
(只需移除 "icons" 和 "default_icon" 字段 — Chrome 会使用默认图标)
如果你包含图标引用,则必须创建实际的图像文件。 使用脚本生成它们(参见 references/extensions/icons.md)或将其省略。切勿引用不存在的文件。
2. 侧边栏:你必须提供打开它的方式
定义 "side_panel": {"default_path": "..."} 并不会使其可打开。添加一个触发器:
// 在 service-worker.js 中 — 点击扩展图标时打开侧边栏
// 重要:chrome.action.onClicked 仅在没有 default_popup 时触发
chrome.action.onClicked.addListener(async (tab) => {
await chrome.sidePanel.open({ windowId: tab.windowId });
});
如果扩展同时具有弹出窗口和侧边栏,请在弹出窗口中添加一个按钮,调用 chrome.sidePanel.open()。或者,使用 chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }) — 但属性是 openPanelOnActionClick,而不是 openPanelOnActionIconClick;"Icon" 变体会导致同步 TypeError,静默中止 service worker。使用 setPanelBehavior 时,不要同时定义 default_popup。参见 references/extensions/side-panel.md。
3. 代码执行:仅限沙盒 iframe
扩展 CSP 阻止在所有扩展页面中使用 eval()、new Function()、内联 <script>。
// ❌ 错误 — 直接访问 iframe DOM 会抛出 SecurityError
iframe.contentDocument.write(html);
// ❌ 错误 — 在扩展页面中使用 eval
eval(userCode); // CSP 阻止此操作
// ✅ 选项 A:在 manifest 中沙盒 + postMessage
// manifest.json: { "sandbox": { "pages": ["sandbox.html"] } }
iframe.contentWindow.postMessage({ html, css, js }, '*');
// sandbox.html 接收并运行:
window.addEventListener('message', (e) => { eval(e.data.js); /* 在沙盒中允许 */ });
// ✅ 选项 B:Blob URL(创建独立源,绕过扩展 CSP)
iframe.src = URL.createObjectURL(new Blob([doc], { type: 'text/html' }));
// ✅ 选项 C:srcdoc
iframe.srcdoc = `<style>${css}</style>${html}<script>${js}<\/script>`;
有关完整详细信息,请参见 references/extensions/csp-sandbox.md。
4. tab.url 需要 tabs 权限
没有它,tab.url 会静默返回 undefined — 不会抛出错误。
// manifest.json — 如果你在任何地方读取 tab.url 或 tab.title,则必需:
{ "permissions": ["tabs"] }
参见 references/extensions/tab-management.md。
5. 始终使用 async/await — 绝不使用 .then() 链
// ❌ 不好
chrome.tabs.query({active: true, currentWindow: true}).then(tabs => {
chrome.scripting.executeScript({target: {tabId: tabs[0].id}, files: ['content.js']}).then(() => {});
});
// ✅ 好
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['content.js'] });
对于执行异步工作的 runtime.onMessage 监听器:
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
(async () => {
const data = await chrome.storage.local.get('key');
sendResponse({ data });
})();
return true; // 保持通道打开
});
6. 内容脚本:不要阻塞主线程
当修改许多 DOM 元素时,使用 requestAnimationFrame 分批处理,并在批次之间让出:
async function highlightAll(elements) {
const BATCH = 20;
for (let i = 0; i < elements.length; i += BATCH) {
await new Promise(r => requestAnimationFrame(() => {
elements.slice(i, i + BATCH).forEach(el => el.style.backgroundColor = 'yellow');
r();
}));
if (globalThis.scheduler?.yield) await scheduler.yield();
}
}
参见 references/extensions/content-scripts.md。
7. Service worker 是短暂的 — 绝不要将状态存储在变量中
// ❌ 错误 — 当 SW 终止时状态丢失(约 30 秒不活动)
let count = 0;
chrome.tabs.onUpdated.addListener(() => { count++; });
// ✅ 正确 — 持久化在 chrome.storage 中,在每个事件上读取
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo) => {
if (changeInfo.status !== 'complete') return;
const { count = 0 } = await chrome.storage.local.get('count');
await chrome.storage.local.set({ count: count + 1 });
await chrome.action.setBadgeText({ text: String(count + 1) });
});
使用 chrome.alarms 而不是 setTimeout/setInterval。参见 references/extensions/service-worker.md。
8. chrome.identity:开发和生产之间的扩展 ID 不同
使用 Google 登录时,OAuth client_id 与特定的扩展 ID 绑定。ID 在未打包的开发版本和 Chrome 网上应用店之间会变化。
为了在开发期间稳定 ID,请在 manifest.json 中添加 "key" 字段:
- 打包扩展一次(chrome://extensions → 打包扩展)
- 从 .crx 中提取公钥
- 将
"key": "MIIBIjANBgkqh..."添加到 manifest.json
始终记录:"发布到 Chrome 网上应用店后,使用商店分配的扩展 ID 更新 OAuth 客户端。" 参见 references/extensions/auth-identity.md。
9. 上下文菜单:操作后向用户显示反馈
当上下文菜单项执行操作(保存、复制等)时,向用户确认。使用通知、徽章闪烁或注入的 toast — 不要让操作静默发生。参见 references/extensions/context-menus.md 获取完整的 toast 实现。
10. Prompt API:在 service worker、弹出窗口和侧边栏中可用
LanguageModel API 在所有扩展上下文中工作 — service worker、弹出窗口和侧边栏 — 无需额外的 manifest 权限。扩展还可以使用 LanguageModel.params(),这在 Web 上不可用:
const params = await LanguageModel.params();
// { defaultTopK: 3, maxTopK: 128, defaultTemperature: 1, maxTemperature: 2 }
对于一般的 Prompt API 模式(可用性检查、会话创建、流式传输),使用 modern-web-guidance 技能。参见 references/extensions/prompt-api.md 获取扩展特定的接线示例。
11. chrome.action API 需要 manifest 中的 action
使用 chrome.action.setBadgeText、chrome.action.setIcon 或 chrome.action.onClicked 需要在 manifest.json 中有 "action" 键 — 即使它是空的。没有它,chrome.action 是 undefined。
// ❌ 错误 — manifest 中没有 "action" 键
await chrome.action.setBadgeText({ text: '5' });
// TypeError: Cannot read properties of undefined (reading 'setBadgeText')
// ✅ 修复 — 在 manifest.json 中添加 "action"(至少是一个空对象)
{ "action": {} }
// 或带弹出窗口:
{ "action": { "default_popup": "popup/popup.html" } }
12. activeTab 仅在直接用户手势时有效 — 而不是从侧边栏
activeTab 仅在以下情况触发时授予对当前标签页的临时访问权限:
- 点击扩展操作图标
- 上下文菜单项(包括
"tab"上下文) - 来自
commandsAPI 的键盘快捷键 - 接受 omnibox 建议
它不授予在侧边栏中点击按钮、稍后打开的弹出窗口按钮或任何程序化触发器的访问权限。
// ❌ 错误 — activeTab 不能从侧边栏按钮点击中工作
document.getElementById('summarize').addEventListener('click', async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: () => document.body.innerText });
});
// ✅ 修复 — 改用 "tabs" 权限 + 特定的 host_permissions
// manifest.json: { "permissions": ["tabs", "scripting"], "host_permissions": ["<all_urls>"] }
参见 references/extensions/side-panel.md。
13. DevTools 面板 URL 相对于扩展根目录
创建 DevTools 面板时,面板 HTML 路径相对于扩展根目录,而不是相对于调用 chrome.devtools.panels.create() 的 devtools 页面。
// ❌ 错误 — 路径相对于 devtools/ 目录
chrome.devtools.panels.create("My Panel", "", "panel/panel.html");
// ✅ 正确 — 从扩展根目录的完整路径
chrome.devtools.panels.create("My Panel", "", "devtools/panel/panel.html");
参见 references/extensions/devtools.md。
14. 离屏文档无法访问大多数 chrome.* API
离屏文档(chrome.offscreen)受到严格限制。大多数 chrome.* API 不可用,包括 chrome.downloads、chrome.tabs、chrome.action 等。
// ❌ 错误 — chrome.downloads 在离屏文档中未定义
chrome.downloads.download({ url, filename: 'recording.webm' }); // TypeError
// ❌ 错误 — chrome.action 在离屏文档中未定义
chrome.action.setBadgeText({ text: 'REC' }); // TypeError
离屏文档中唯一可用的 API 是:
chrome.runtime.sendMessage/chrome.runtime.onMessagechrome.runtime.getURL- 标准 Web API(DOM、fetch、MediaRecorder、Canvas、Web Audio 等)
经验法则: 离屏文档执行 Web API 工作(录制、解析、音频)。Service worker 执行所有 chrome.* API 工作(下载、徽章更新、通知)。使用 chrome.runtime.sendMessage 在它们之间桥接。参见 references/extensions/message-passing.md。
15. 通知和徽章图标必须引用真实的图像文件
chrome.notifications.create() 需要一个指向实际图像文件的有效 iconUrl。如果文件不存在或路径错误,调用将失败并显示 "Unable to download all specified images."
// ❌ 错误 — 图标文件不存在
chrome.notifications.create('reminder', {
type: 'basic',
iconUrl: 'icons/icon-128.png', // 文件不在扩展中!
title: 'Reminder',
message: 'Time is up!'
});
// ✅ 在运行时通过 OffscreenCanvas 生成数据 URL — 无需文件。
// 参见 `references/extensions/icons.md` 获取可重用的实现。
const iconUrl = await getIconDataUrl();
chrome.notifications.create('reminder', { type: 'basic', iconUrl, title: 'Reminder', message: 'Time is up!' });
这适用于 chrome.* API 中的所有图像引用 — 通知、chrome.action.setIcon、上下文菜单图标等。如果你引用文件,它必须存在。
16. 标签页捕获:使用状态锁定防止双重启动
如果前一次捕获仍然活动,chrome.tabCapture.getMediaStreamId() 会失败并显示 "Cannot capture a tab with an active stream"。快速双击扩展图标很容易触发此问题。使用显式状态锁定:
// ❌ 错误 — 没有防止快速点击的保护
let isRecording = false;
chrome.action.onClicked.addListener(async (tab) => {
if (isRecording) { stopRecording(); isRecording = false; }
else { isRecording = true; startRecording(tab); } // 第二次点击 = "active stream" 错误
});
// ✅ 正确 — 使用过渡状态锁定并发操作
// 状态机:'idle' → 'starting' → 'recording' → 'stopping' → 'idle'
// 将状态存储在 chrome.storage.session 中(SW 重启后保留,浏览器关闭时清除)
chrome.action.onClicked.addListener(async (tab) => {
const { recordingState = 'idle' } = await chrome.storage.session.get('recordingState');
if (recordingState === 'starting' || recordingState === 'stopping') return;
if (recordingState === 'idle') {
await chrome.storage.session.set({ recordingState: 'starting' });
try {
await startRecording(tab);
await chrome.storage.session.set({ recordingState: 'recording' });
await chrome.action.setBadgeText({ text: 'REC' });
await chrome.action.setBadgeBackgroundColor({ color: '#FF0000' });
} catch (err) {
console.error('Failed to start recording:', err);
await chrome.storage.session.set({ recordingState: 'idle' });
}
} else if (recordingState === 'recording') {
await chrome.storage.session.set({ recordingState: 'stopping' });
try { await stopRecording(); }
finally {
await chrome.storage.session.set({ recordingState: 'idle' });
await chrome.action.setBadgeText({ text: '' });
}
}
});
此模式适用于任何管理独占资源的 chrome API:chrome.tabCapture、chrome.desktopCapture、chrome.offscreen.createDocument(一次只允许一个离屏文档)。参见 references/extensions/media-capture.md。
17. chrome.desktopCapture 需要具有 URL 访问权限的目标标签页
从 service worker 调用 chrome.desktopCapture.chooseDesktopMedia() 时,必须将活动标签页作为 targetTab 参数传递。标签页对象必须填充其 url 字段,这需要 "tabs" 权限。
// ❌ 错误 — 从 service worker 调用时没有 targetTab
chrome.desktopCapture.chooseDesktopMedia(['screen', 'window'], (streamId) => { ... });
// 错误:"A target tab is required when called from a service worker context."
// ❌ 错误 — 标签页没有 url 字段(缺少 "tabs" 权限)
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
chrome.desktopCapture.chooseDesktopMedia(['screen', 'window'], tab, (streamId) => { ... });
// 错误:"targetTab doesn't have URL field set."
// ✅ 正确 — manifest 中的 "tabs" 权限 + 传递标签页对象
// manifest.json: { "permissions": ["tabs", "desktopCapture"] }
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
chrome.desktopCapture.chooseDesktopMedia(['screen', 'window'], tab, (streamId) => {
if (!streamId) return; // 用户取消
});
注意: 对于仅标签页录制,优先使用 chrome.tabCapture.getMediaStreamId()。仅当用户应选择要捕获的屏幕/窗口时,才使用 chrome.desktopCapture。参见 references/extensions/media-capture.md。
18. 用户脚本:四个不明显的陷阱
chrome.userScripts 在运行时运行用户提供的代码。用于脚本管理器和用户自动化 — 而不是扩展捆绑的脚本。
- 如果未启用,API 在属性访问时抛出异常。 Chrome 138+ 要求用户在扩展详细信息页面上切换"允许用户脚本";Chrome < 138 需要开发者模式。在任何
chrome.userScripts.*调用之前,始终调用isUserScriptsAvailable(),并在返回 false 时显示错误 UI。 - 注册的脚本在扩展更新时被清除。 将配置持久化在
chrome.storage中;在runtime.onInstalled中为"update"原因重新注册它们。 - 消息传递需要显式选择加入。 首先调用
configureWorld({ messaging: true });监听runtime.onUserScriptMessage,而不是runtime.onMessage。 ScriptSource约束: 每个js条目必须恰好具有code或file之一。id约束: 不能以_开头。
参见 references/extensions/user-scripts.md。
19. chrome.windows 没有 .query() 方法 — 使用 getAll、getLastFocused 或 getCurrent
与 chrome.tabs.query() 不同,chrome.windows API 没有 .query() 方法。
// ❌ 错误 — chrome.windows.query 不存在
const windows = await chrome.windows.query({ focused: true });
// TypeError: chrome.windows.query is not a function
// ✅ 正确 — 根据你的需求使用正确的方法
const focused = await chrome.windows.getLastFocused({ populate: true });
const current = await chrome.windows.getCurrent({ populate: true });
const all = await chrome.windows.getAll({ populate: true });
chrome.windows 方法: getAll、getLastFocused、getCurrent、get(windowId)、create、update、remove。参见 references/extensions/tab-management.md。
始终使用 Manifest V3
绝不生成 Manifest V2 代码。
background.service_worker而不是background.scriptschrome.action而不是chrome.browserActionchrome.scripting.executeScript而不是chrome.tabs.executeScripthost_permissions与permissions分开- HTML 中无内联脚本 — 使用
<script src="file.js"> - 无内联事件处理程序 — 使用
addEventListener
第二部分 — 发布到 Chrome 网上应用店
管理 CHROMEWEBSTORE.md — 所有 Chrome 网上应用店列表元数据、权限理由、隐私披露、版本历史以及 Chrome 扩展项目发布就绪状态的唯一事实来源。
核心工作流程
每次你以影响商店存在的方式接触 Chrome 扩展项目时,更新(或创建)项目根目录中的 CHROMEWEBSTORE.md。该文件跟踪开发者在 Chrome 开发者信息中心需要填写的所有内容,以便他们可以从单个文档复制粘贴,而不是在发布时手忙脚乱。
何时创建 CHROMEWEBSTORE.md
在以下任何情况发生时立即创建:
- 用户表示想要发布扩展
- 用户要求"为商店准备"或"准备发布"
- 你正在构建一个显然最终会出现在商店中的新扩展
- 用户询问商店列表要求
使用 references/webstore/chromewebstore-template.md 中的模板作为起点。在生成文件之前阅读它。
何时更新 CHROMEWEBSTORE.md
在以下情况时更新:
- 面向用户的更改:更新"最后更新"日期,更新描述中的功能列表,并在版本历史中添加条目
- manifest.json 更改:如果权限、host_permissions 或 content_scripts 更改,更新权限理由部分 — 每个权限都需要一个审核团队能理解的通俗易懂的理由
- 新版本:在版本历史中添加条目,包含版本号、日期和摘要
- 隐私相关更改:如果数据收集、存储或传输更改,更新隐私与数据使用部分和隐私政策
- 资源更改:如果图标或 UI 更改,注明哪些截图需要刷新
- 拒绝响应:如果用户报告 CWS 拒绝,使用修复更新文件并在版本历史中添加注释
如何填写
对于每个部分,从实际项目文件中提取信息:
- 读取
manifest.json以提取名称、版本、描述、权限、host_permissions - 扫描代码库以了解数据收集(存储、fetch 调用、分析)
- 检查图标文件及其尺寸
- 查看扩展的 UI 以了解功能,用于描述
以具体、诚实且以利益为导向的语气编写面向商店的文案。Chrome 网上应用店审核团队会拒绝模糊的描述。"让您的生活更轻松"会被拒绝。"在任何网页上高亮搜索结果,并让您将高亮保存到本地列表"会通过。
绝不提及实现细节。 用户关心扩展为他们做什么,而不是它是如何构建的。删除任何提及 API、库、框架或代码模式的内容:
| ❌ 实现细节(删除) | ✅ 用户利益(保留) |
|---|---|
| "使用 MutationObserver 检测页面变化" | "在您浏览时自动检测新内容" |
| "使用自定义元素和 Shadow DOM 构建" | "无缝工作,不影响页面样式" |
| "由 service worker 提供后台处理" | "在后台安静运行,不拖慢浏览器" |
| "利用 chrome.storage.sync API" | "您的设置在所有设备间同步" |
| "实现 declarativeNetRequest 进行过滤" | "阻止广告和跟踪器,无需读取页面内容" |
CHROMEWEBSTORE.md 部分
在生成文件之前阅读 references/webstore/chromewebstore-template.md — 它定义了每个部分涵盖的内容以及如何填写。风险最高的部分是权限理由:为每个权限和每个 host_permission 编写具体的通俗易懂的理由。"扩展工作需要"会被拒绝。阅读 references/webstore/privacy-policy.md 以获取生成隐私政策的指导。
发布前检查清单
在提交之前,运行 references/webstore/review-checklist.md。最常见的首次提交失败:
- 每个权限和 host_permission 必须有具体的理由(不是"工作需要")
- 隐私政策 URL 必须有效且与数据使用披露表匹配
- 至少 1 张 1280×800 或 640×400 的截图
- ZIP 必须排除
.git/、node_modules/、.env、CHROMEWEBSTORE.md
商店列表文案指南
有关文案指南和常见拒绝原因,请参见 references/webstore/store-listing.md。关键规则:以功能开头("在任何网页上高亮搜索词"),而不是感受("再次享受搜索")。
参考文件
有关详细的 API 模式和发布指导,请在编写代码或内容之前阅读相关文件:
| 主题 | 参考 |
|---|---|
| 侧边栏 | references/extensions/side-panel.md |
| 内容脚本与 DOM | references/extensions/content-scripts.md |
| 弹出窗口 | references/extensions/popup-ui.md |
| Service worker 生命周期 | references/extensions/service-worker.md |
| 代码执行与 CSP | references/extensions/csp-sandbox.md |
| API 调用 | references/extensions/api-calling.md |
| 声明式网络请求 | references/extensions/declarative-net-request.md |
| Chrome Prompt API | references/extensions/prompt-api.md |
| DevTools 面板 | references/extensions/devtools.md |
| 身份验证 | references/extensions/auth-identity.md |
| 上下文菜单 | references/extensions/context-menus.md |
| Omnibox | references/extensions/omnibox.md |
| 存储 | references/extensions/storage.md |
| 标签页与窗口管理 | references/extensions/tab-management.md |
| 标签页/桌面捕获 | references/extensions/media-capture.md |
| 用户脚本 | references/extensions/user-scripts.md |
| 消息传递 | references/extensions/message-passing.md |
| 图标 | references/extensions/icons.md |
| CHROMEWEBSTORE.md 模板 | references/webstore/chromewebstore-template.md |
| 隐私政策指导 | references/webstore/privacy-policy.md |
| 发布前审核清单 | references/webstore/review-checklist.md |
| 商店列表提示与拒绝 | references/webstore/store-listing.md |
输出检查清单
在交付之前验证每一项:
- [ ]
manifest_version: 3— 任何地方都没有 V2 API - [ ] manifest 中引用的所有图标文件都是具有正确尺寸的真实文件 — 或省略图标
- [ ] 侧边栏有显式的打开触发器(不仅仅是 manifest 声明)
- [ ] 代码执行使用沙盒/blob/srcdoc — 扩展页面中没有
eval() - [ ] 如果访问
tab.url或tab.title,则声明tabs权限 - [ ] 所有代码使用
async/await— 没有.then()链 - [ ] 内容脚本使用
requestAnimationFrame批量更新 DOM - [ ] Service worker 不在全局变量中存储状态 — 使用
chrome.storage - [ ] HTML 中没有内联脚本或事件处理程序
- [ ] 上下文菜单操作显示用户确认
- [ ] 如果使用
chrome.action.*API,manifest 中存在"action": {}(或更多) - [ ] 如果从侧边栏读取/脚本化标签页:使用
tabs+host_permissions(而不是activeTab) - [ ]
chrome.devtools.panels.create()中的 DevTools 面板路径相对于扩展根目录 - [ ] 离屏文档仅使用
chrome.runtime消息传递 — 没有chrome.downloads、chrome.action等 - [ ]
chrome.notifications、chrome.action.setIcon等中的所有图像引用指向真实文件(或使用数据 URL) - [ ] 标签页/桌面捕获使用状态锁定以防止双重启动错误
- [ ]
chrome.desktopCapture.chooseDesktopMedia传递具有tabs权限的targetTab - [ ]
chrome.windows调用使用getAll/getLastFocused/getCurrent— 而不是.query()(它不存在) - [ ] 使用前检查
chrome.userScripts可用性(如果用户未启用,API 会抛出异常) - [ ] 用户脚本配置持久化在
chrome.storage中,并在runtime.onInstalled"update"原因时恢复 - [ ] 在用户脚本发送消息之前调用
configureWorld({ messaging: true });监听onUserScriptMessage而不是onMessage - [ ]
ScriptSource条目每个恰好具有code或file之一(不能两者都有,也不能都没有) - [ ] 用户脚本
id值不以_开头 - [ ]
sidePanel.setPanelBehavior使用openPanelOnActionClick— 而不是openPanelOnActionIconClick - [ ] 所有异步操作都有错误处理
- [ ]
host_permissions限定到特定域(除非需要,否则不是<all_urls>) - [ ] 在具有异步响应的
onMessage监听器中return true - [ ] 在
chrome.contextMenuscontexts中使用"tab"需要 Chrome M150+





