关于 chrome-extensions
Chrome Extensions 是面向开发者的技能,用于构建 Manifest V3 扩展并准备发布到 Chrome Web Store。它整理了扩展组件、权限、API 和发布环节的实现规则与常见问题。
主要功能
- 01Manifest V3 实现指南
- 02图标与清单检查
- 03侧边栏设置模式
- 04符合 CSP 的代码执行指南
- 05权限与 API 常见问题
- 06Service Worker 与捕获指南
适用场景
- 01构建 Manifest V3 扩展
- 02添加弹出窗口或侧边栏
- 03实现扩展认证或捕获功能
- 04准备将扩展发布到 Chrome Web Store
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ googlechrome/ modern-web-guidance --skill chrome-extensions安装后在 Claude Code 中输入/chrome-extensions确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/extensions/api-calling.mdauth-identity.mdcontent-scripts.mdcontext-menus.mdcsp-sandbox.mddeclarative-net-request.mddevtools.mdicons.mdmedia-capture.mdmessage-passing.mdomnibox.mdpermissions.mdpopup-ui.mdprompt-api.mdservice-worker.mdside-panel.mdstorage.mdtab-management.mduser-scripts.mdreferences/webstore/chromewebstore-template.mdprivacy-policy.mdreview-checklist.mdstore-listing.md详细介绍
Chrome 扩展程序
使用 Manifest V3 构建可用于生产环境的 Chrome 扩展程序,并发布到 Chrome Web Store。
第 1 部分 — 构建扩展程序
强制规则
以下是导致扩展程序损坏的最常见原因。违反任何一条都会导致构建无法正常运行。
1. 图标:只引用已创建的文件,或者完全省略图标
❌ BROKEN — referencing files that don't exist or reusing one file for all sizes:
"icons": { "16": "icon.png", "48": "icon.png", "128": "icon.png" }
✅ CORRECT — each size is a separate file at the correct pixel dimensions:
"icons": { "16": "icons/icon-16.png", "48": "icons/icon-48.png", "128": "icons/icon-128.png" }
(where icon-16.png is 16×16px, icon-48.png is 48×48px, icon-128.png is 128×128px)
✅ ALSO CORRECT — omit icons from manifest if you cannot generate real PNG files:
(just remove the "icons" and "default_icon" fields — Chrome uses a default icon)
如果包含图标引用,就必须创建对应的图像文件。 使用脚本生成图像(参见 references/extensions/icons.md),或者省略图标。切勿引用不存在的文件。
2. 侧边栏:必须提供打开方式
定义 "side_panel": {"default_path": "..."} 并不会使其可以打开。请添加触发方式:
// In service-worker.js — open side panel on extension icon click
// IMPORTANT: chrome.action.onClicked ONLY fires when there is NO 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,并静默中止服务工作线程。使用 setPanelBehavior 时,不要同时定义 default_popup。参见 references/extensions/side-panel.md。
3. 代码执行:只能使用沙盒 iframe
扩展程序的 CSP 会阻止在所有扩展程序页面中使用 eval()、new Function() 和内联 <script>。
// ❌ BROKEN — direct iframe DOM access throws SecurityError
iframe.contentDocument.write(html);
// ❌ BROKEN — eval in extension page
eval(userCode); // CSP blocks this
// ✅ OPTION A: Sandbox in manifest + postMessage
// manifest.json: { "sandbox": { "pages": ["sandbox.html"] } }
iframe.contentWindow.postMessage({ html, css, js }, '*');
// sandbox.html receives and runs:
window.addEventListener('message', (e) => { eval(e.data.js); /* allowed in sandbox */ });
// ✅ OPTION B: Blob URL (creates separate origin, bypasses extension CSP)
iframe.src = URL.createObjectURL(new Blob([doc], { type: 'text/html' }));
// ✅ OPTION C: srcdoc
iframe.srcdoc = `<style>${css}</style>${html}<script>${js}<\/script>`;
完整详情请参见 references/extensions/csp-sandbox.md。
4. tab.url 需要 tabs 权限
如果没有该权限,tab.url 会静默返回 undefined,不会抛出错误。参见
references/extensions/permissions.md。
5. 始终使用 async/await,切勿使用 .then() 链
// ❌ BAD
chrome.tabs.query({active: true, currentWindow: true}).then(tabs => {
chrome.scripting.executeScript({target: {tabId: tabs[0].id}, files: ['content.js']}).then(() => {});
});
// ✅ GOOD
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; // keeps channel open
});
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. 服务工作线程是临时的,切勿将状态存储在变量中
// ❌ BROKEN — state lost when SW terminates (~30s of inactivity)
let count = 0;
chrome.tabs.onUpdated.addListener(() => { count++; });
// ✅ CORRECT — persist in chrome.storage, read on every event
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 绑定。未打包的开发版本和 Chrome Web Store 版本的 ID 会有所不同。
如需在开发期间固定 ID,请在 manifest.json 中添加 "key" 字段:
- 将扩展程序打包一次(chrome://extensions → Pack)
- 从 .crx 中提取公钥
- 在 manifest.json 中添加
"key": "MIIBIjANBgkqh..."
务必注明:“发布到 Chrome Web Store 后,请使用商店分配的扩展程序 ID 更新 OAuth 客户端。”参见 references/extensions/auth-identity.md。
9. 上下文菜单:操作完成后向用户提供反馈
上下文菜单项执行操作(保存、复制等)时,请向用户确认操作已完成。使用通知、徽章闪烁或注入的提示框,不要让操作悄无声息地发生。完整的提示框实现请参见 references/extensions/context-menus.md。
10. Prompt API:可在服务工作线程、弹出窗口和侧边栏中使用
LanguageModel API 可在所有扩展程序上下文中使用——服务工作线程、弹出窗口和侧边栏均可使用,且无需额外的清单权限。扩展程序还可以使用 LanguageModel.params(),而网页无法使用此方法:
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 需要在清单中定义 action
使用 chrome.action.setBadgeText、chrome.action.setIcon 或 chrome.action.onClicked,需要在 manifest.json 中添加 "action" 键——即使其值为空也一样。否则,chrome.action 将为 undefined。
// ❌ BROKEN — manifest has no "action" key
await chrome.action.setBadgeText({ text: '5' });
// TypeError: Cannot read properties of undefined (reading 'setBadgeText')
// ✅ FIX — add "action" to manifest.json (at minimum an empty object)
{ "action": {} }
// or with a popup:
{ "action": { "default_popup": "popup/popup.html" } }
12. activeTab 仅在用户直接触发时有效——不适用于侧边栏
activeTab 仅在用户直接触发操作(点击操作图标、选择上下文菜单项、使用键盘快捷键或选择地址栏建议)时,授予对当前标签页的临时访问权限——在侧边栏或弹出窗口内点击按钮则不行。请改用 tabs + host_permissions。参见 references/extensions/permissions.md 和 references/extensions/side-panel.md。
13. DevTools 面板 URL 相对于扩展根目录
创建 DevTools 面板时,面板 HTML 路径相对于扩展根目录,而不是相对于调用 chrome.devtools.panels.create() 的 DevTools 页面。
// ❌ BROKEN — path relative to devtools/ directory
chrome.devtools.panels.create("My Panel", "", "panel/panel.html");
// ✅ CORRECT — full path from extension root
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 等。
// ❌ BROKEN — chrome.downloads is undefined in offscreen documents
chrome.downloads.download({ url, filename: 'recording.webm' }); // TypeError
// ❌ BROKEN — chrome.action is undefined in offscreen documents
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 相关工作(录制、解析、音频)。服务工作线程负责所有 chrome.* API 相关工作(下载、更新徽章、通知)。使用 chrome.runtime.sendMessage 在两者之间传递消息。参见 references/extensions/message-passing.md。
15. 通知和徽章图标必须指向真实图像文件
chrome.notifications.create() 要求有效的 iconUrl 指向实际图像文件。
如果文件不存在或路径错误,调用将失败,并显示 "Unable to download all specified images."
// ❌ BROKEN — icon file doesn't exist
chrome.notifications.create('reminder', {
type: 'basic',
iconUrl: 'icons/icon-128.png', // File not in extension!
title: 'Reminder',
message: 'Time is up!'
});
// ✅ Generate a data URL at runtime via OffscreenCanvas — no file needed.
// See `references/extensions/icons.md` for a reusable implementation.
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"。快速双击扩展图标很容易触发这种情况。请使用显式状态锁定:
// ❌ BROKEN — no guard against rapid clicks
let isRecording = false;
chrome.action.onClicked.addListener(async (tab) => {
if (isRecording) { stopRecording(); isRecording = false; }
else { isRecording = true; startRecording(tab); } // Second click = "active stream" error
});
// ✅ CORRECT — use transitional states to lock out concurrent operations
// State machine: 'idle' → 'starting' → 'recording' → 'stopping' → 'idle'
// Store state in chrome.storage.session (survives SW restart, cleared on browser close)
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 访问权限的目标标签页
从服务工作线程调用 chrome.desktopCapture.chooseDesktopMedia() 时,必须将当前活动标签页作为 targetTab 参数传入。标签页对象必须填充 url 字段,这需要 "tabs" 权限。
// ❌ BROKEN — called without targetTab from service worker
chrome.desktopCapture.chooseDesktopMedia(['screen', 'window'], (streamId) => { ... });
// Error: "A target tab is required when called from a service worker context."
// ❌ BROKEN — tab doesn't have url field (missing "tabs" permission)
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
chrome.desktopCapture.chooseDesktopMedia(['screen', 'window'], tab, (streamId) => { ... });
// Error: "targetTab doesn't have URL field set."
// ✅ CORRECT — "tabs" permission in manifest + pass tab object
// 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; // User cancelled
});
注意: 对于仅录制标签页的情况,优先使用 chrome.tabCapture.getMediaStreamId()。只有在用户需要选择要捕获的屏幕或窗口时,才使用 chrome.desktopCapture。参见 references/extensions/media-capture.md。
18. 用户脚本:四个不太明显的陷阱
chrome.userScripts 会在运行时执行用户提供的代码。请将其用于脚本管理器和
用户自动化,而非扩展内置脚本。
- 未启用时,访问属性会导致 API 抛出异常。 Chrome 138+ 要求用户在扩展程序详情页面中开启“允许用户脚本”;Chrome < 138 则要求开启开发者模式。调用任何
chrome.userScripts.*方法前,都要先调用isUserScriptsAvailable();若返回 false,则显示错误界面。 - 扩展更新时,已注册的脚本会被清除。 请将配置保存在
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() 方法。
// ❌ BROKEN — chrome.windows.query does not exist
const windows = await chrome.windows.query({ focused: true });
// TypeError: chrome.windows.query is not a function
// ✅ CORRECT — use the right method for your need
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。
20. 在服务工作线程中调用 chrome.permissions.request() 时,消息监听器中不得在此调用前使用 await
来自 UI 上下文(侧边栏、弹出窗口)的用户手势会通过 chrome.runtime.sendMessage
传递到服务工作线程的 onMessage 监听器——但只在当前同步执行期间有效。如果监听器在调用
chrome.permissions.request() 前执行了 await(即使只是短暂等待),用户手势也会失效,该调用会抛出
"This function must be called during a user gesture"。请将此调用作为监听器中的第一项操作,前面不要有任何等待——参见
references/extensions/permissions.md。
始终使用 Manifest V3
绝不生成 Manifest V2 代码。
- 使用
background.service_worker,而非background.scripts - 使用
chrome.action,而非chrome.browserAction - 使用
chrome.scripting.executeScript,而非chrome.tabs.executeScript host_permissions与permissions分开配置- HTML 中不得包含内联脚本——请使用
<script src="file.js"> - 不得使用内联事件处理程序——请使用
addEventListener
第 2 部分 — 发布到 Chrome Web Store
维护 CHROMEWEBSTORE.md——这是 Chrome 扩展项目中所有 Chrome Web Store 商品信息元数据、权限使用理由、隐私披露、版本历史和发布准备情况的唯一真实来源。
核心工作流
每次以会影响 Chrome 扩展商品页面的方式处理项目时,都要更新(或创建)项目根目录下的
CHROMEWEBSTORE.md。此文件会记录开发者需要在 Chrome Developer Dashboard 中填写的所有内容,这样他们就能从
一份文档中复制粘贴,而不必等到发布时才手忙脚乱。
何时创建 CHROMEWEBSTORE.md
出现以下任何情况时,立即创建:
- 用户表示要发布扩展
- 用户要求“为应用商店做好准备”或“准备发布”
- 你正在构建一个显然最终会发布到应用商店的新扩展
- 用户询问应用商店的商品信息要求
请使用 references/webstore/chromewebstore-template.md 中的模板作为起点。生成文件前请先阅读。
何时更新 CHROMEWEBSTORE.md
遇到以下情况时更新:
- 面向用户的更改:更新“Last Updated”日期,更新描述中的功能列表,并在版本历史记录中添加条目
- manifest.json 更改:如果 permissions、host_permissions 或 content_scripts 有所更改,请更新“权限说明”部分——每项权限都需要提供审核团队能理解的通俗理由
- 发布新版本:添加一条版本历史记录,包含版本号、日期和摘要
- 与隐私相关的更改:如果数据收集、存储或传输方式有所更改,请更新“隐私与数据使用”部分和隐私政策
- 素材更改:如果图标或 UI 有所更改,请注明哪些截图需要更新
- 审核未通过后的回复:如果用户报告 CWS 审核未通过,请在文件中记录修复内容,并在版本历史记录中添加说明
如何填写
对于每个部分,请从实际项目文件中提取信息:
- 阅读
manifest.json,提取名称、版本、描述、permissions、host_permissions - 扫描代码库,查找数据收集相关内容(存储、fetch 调用、分析)
- 检查图标文件及其尺寸
- 查看扩展程序的 UI,了解描述中应介绍的功能
面向商店的文案应具体、诚实,并突出用户收益。Chrome 网上应用店审核团队会拒绝含糊的描述。“让生活更轻松”会被拒绝。“突出显示任意网页中的搜索结果,并让你将其保存到本地列表”则会通过。
切勿提及实现细节。 用户关心的是扩展程序能为他们做什么,而不是它是如何构建的。删除任何关于 API、库、框架或代码模式的描述:
| ❌ 实现细节(删除) | ✅ 用户收益(保留) |
|---|---|
| “使用 MutationObserver 检测页面变化” | “浏览时自动检测新内容” |
| “使用自定义元素和 Shadow DOM 构建” | “无缝运行,不会影响网页样式” |
| “借助服务工作线程进行后台处理” | “在后台安静运行,不会拖慢浏览器” |
| “利用 chrome.storage.sync API” | “你的设置会在所有设备间同步” |
| “使用 declarativeNetRequest 进行过滤” | “拦截广告和跟踪器,且不会读取网页内容” |
CHROMEWEBSTORE.md 各部分
生成文件前,请阅读 references/webstore/chromewebstore-template.md——其中定义了各部分的内容和填写方法。风险最高的部分是“权限说明”:请为每项权限和每项 host_permission 提供具体、通俗的理由。“扩展程序正常运行所必需”会被拒绝。有关生成隐私政策的指导,请阅读 references/webstore/privacy-policy.md。
发布前检查清单
提交前,请逐项检查 references/webstore/review-checklist.md。首次提交最常见的失败原因:
- 每项 permission 和 host_permission 都必须有具体说明(不能写“正常运行所必需”)
- 隐私政策 URL 必须可访问,并且与数据使用披露表单一致
- 至少有 1 张分辨率为 1280×800 或 640×400 的截图
- ZIP 中不得包含
.git/、node_modules/、.env、CHROMEWEBSTORE.md
商店商品信息文案指南
有关文案指南和常见的审核未通过原因,请参阅 references/webstore/store-listing.md。
关键规则:开头应介绍功能(“突出显示任意网页中的搜索词”),而不是表达感受(“重新享受搜索的乐趣”)。
参考文件
编写代码或内容前,请阅读相关文件,了解详细的 API 模式和发布指南:
| 主题 | 参考文件 |
|---|---|
| 权限 | references/extensions/permissions.md |
| 侧边栏 | 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 |
| 地址栏 | 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 - 清单中引用的所有图标文件都是真实文件且尺寸正确,或者省略图标
- 侧边栏有明确的打开触发方式(不能只有清单声明)
- 代码执行使用 sandbox/blob/srcdoc——扩展页面中不得使用
eval() - 如果访问
tab.url或tab.title,则声明tabs权限 - 所有代码都使用
async/await——不使用.then()链 - 内容脚本使用
requestAnimationFrame批量更新 DOM - 服务工作线程不在全局变量中存储任何状态——使用
chrome.storage - HTML 中没有内联脚本或事件处理程序
- 上下文菜单操作会向用户显示确认信息
- 如果使用
chrome.action.*API,清单中包含"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()(该方法不存在) - 在服务工作线程的
onMessage监听器中调用chrome.permissions.request()时,调用前不得使用await(首次异步间隙后用户手势会失效) - 使用前检查
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.contextMenus的contexts中使用"tab"时,需要 Chrome M150+