chrome-extensions

chrome-extensions

热门

使用 Manifest V3 最佳实践构建和发布 Chrome 扩展。每当用户要求创建、修改、调试或理解 Chrome 浏览器扩展、插件或任何涉及 Chrome 扩展 API 的内容时,使用此技能。触发词包括:'Chrome 扩展'、'浏览器扩展'、'manifest.json'、'内容脚本'、'service worker'(在浏览器上下文中)、'弹出窗口'(在浏览器扩展上下文中)、'侧边栏'、'chrome.* API'、'declarativeNetRequest'、'omnibox'、'上下文菜单'(在扩展上下文中)、'userScripts'、'用户脚本'、'脚本管理器',或任何请求构建与 Chrome 浏览器 UI 集成的功能。也适用于发布到 Chrome 网上应用店:'发布扩展'、准备扩展以发布、回应审核拒绝、编写权限理由或起草隐私政策。

1628Star
64Fork
更新于 2026/7/24
SKILL.md
只读
名称
chrome-extensions
描述

使用 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" 字段:

  1. 打包扩展一次(chrome://extensions → 打包扩展)
  2. 从 .crx 中提取公钥
  3. "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.setBadgeTextchrome.action.setIconchrome.action.onClicked 需要在 manifest.json 中有 "action" 键 — 即使它是空的。没有它,chrome.actionundefined

// ❌ 错误 — 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" 上下文)
  • 来自 commands API 的键盘快捷键
  • 接受 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.downloadschrome.tabschrome.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.onMessage
  • chrome.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.tabCapturechrome.desktopCapturechrome.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 条目必须恰好具有 codefile 之一。id 约束: 不能以 _ 开头。

参见 references/extensions/user-scripts.md

19. chrome.windows 没有 .query() 方法 — 使用 getAllgetLastFocusedgetCurrent

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 方法: getAllgetLastFocusedgetCurrentget(windowId)createupdateremove。参见 references/extensions/tab-management.md

始终使用 Manifest V3

绝不生成 Manifest V2 代码。

  • background.service_worker 而不是 background.scripts
  • chrome.action 而不是 chrome.browserAction
  • chrome.scripting.executeScript 而不是 chrome.tabs.executeScript
  • host_permissionspermissions 分开
  • 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 拒绝,使用修复更新文件并在版本历史中添加注释

如何填写

对于每个部分,从实际项目文件中提取信息:

  1. 读取 manifest.json 以提取名称、版本、描述、权限、host_permissions
  2. 扫描代码库以了解数据收集(存储、fetch 调用、分析)
  3. 检查图标文件及其尺寸
  4. 查看扩展的 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/.envCHROMEWEBSTORE.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.urltab.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.downloadschrome.action
  • [ ] chrome.notificationschrome.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 条目每个恰好具有 codefile 之一(不能两者都有,也不能都没有)
  • [ ] 用户脚本 id 值不以 _ 开头
  • [ ] sidePanel.setPanelBehavior 使用 openPanelOnActionClick — 而不是 openPanelOnActionIconClick
  • [ ] 所有异步操作都有错误处理
  • [ ] host_permissions 限定到特定域(除非需要,否则不是 <all_urls>
  • [ ] 在具有异步响应的 onMessage 监听器中 return true
  • [ ] 在 chrome.contextMenus contexts 中使用 "tab" 需要 Chrome M150+