extension-anti-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBrowser Extension Anti-Patterns
浏览器扩展开发反模式
Common mistakes to avoid when developing browser extensions for Chrome, Firefox, and Safari.
开发Chrome、Firefox和Safari浏览器扩展时需避免的常见错误。
Overview
概述
This skill catalogs anti-patterns that lead to:
- Poor performance and memory leaks
- Store rejections (Chrome Web Store, AMO, Safari App Store)
- Security vulnerabilities
- Cross-browser incompatibilities
- Poor user experience
This skill covers:
- Performance anti-patterns
- Store rejection reasons
- API misuse patterns
- Manifest configuration mistakes
- Content script pitfalls
This skill does NOT cover:
- General JavaScript anti-patterns
- Server-side code issues
- Native messaging host problems
本文整理了会导致以下问题的反模式:
- 性能低下和内存泄漏
- 应用商店拒绝(Chrome Web Store、AMO、Safari App Store)
- 安全漏洞
- 跨浏览器兼容性问题
- 糟糕的用户体验
本文涵盖:
- 性能反模式
- 应用商店拒绝原因
- API误用模式
- 清单文件配置错误
- 内容脚本陷阱
本文不涵盖:
- 通用JavaScript反模式
- 服务端代码问题
- 原生消息主机问题
Quick Reference
快速参考
Red Flags Checklist
风险信号检查表
| Anti-Pattern | Impact | Solution |
|---|---|---|
| Store rejection | Use specific host permissions |
| Blocking background operations | Extension suspend issues | Use async/Promise patterns |
| DOM polling in content scripts | High CPU usage | Use MutationObserver |
| Unbounded storage growth | Memory exhaustion | Implement retention policies |
| CSP violation, store rejection | Use static code |
| 反模式 | 影响 | 解决方案 |
|---|---|---|
| 应用商店拒绝 | 使用特定主机权限 |
| 阻塞式后台操作 | 扩展挂起问题 | 使用async/Promise模式 |
| 内容脚本中的DOM轮询 | 高CPU占用 | 使用MutationObserver |
| 存储无限制增长 | 内存耗尽 | 实现保留策略 |
| CSP违规、应用商店拒绝 | 使用静态代码 |
Performance Anti-Patterns
性能反模式
1. DOM Polling
1. DOM轮询
Problem: Using to check for DOM changes.
setIntervaljavascript
// BAD: Polls every 100ms, wastes CPU
setInterval(() => {
const element = document.querySelector('.target');
if (element) {
processElement(element);
}
}, 100);Solution: Use MutationObserver.
javascript
// GOOD: Only fires when DOM changes
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
const element = document.querySelector('.target');
if (element) {
processElement(element);
observer.disconnect();
}
}
});
observer.observe(document.body, { childList: true, subtree: true });问题: 使用检测DOM变化。
setIntervaljavascript
// BAD: Polls every 100ms, wastes CPU
setInterval(() => {
const element = document.querySelector('.target');
if (element) {
processElement(element);
}
}, 100);解决方案: 使用MutationObserver。
javascript
// GOOD: Only fires when DOM changes
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
const element = document.querySelector('.target');
if (element) {
processElement(element);
observer.disconnect();
}
}
});
observer.observe(document.body, { childList: true, subtree: true });2. Synchronous Storage Access
2. 同步存储访问
Problem: Using synchronous storage patterns that block execution.
javascript
// BAD: Blocks until storage returns
const data = await browser.storage.local.get('key');
// 50+ more sequential awaits...Solution: Batch storage operations.
javascript
// GOOD: Single storage call
const data = await browser.storage.local.get(['key1', 'key2', 'key3']);问题: 使用同步存储模式阻塞执行。
javascript
// BAD: Blocks until storage returns
const data = await browser.storage.local.get('key');
// 50+ more sequential awaits...解决方案: 批量存储操作。
javascript
// GOOD: Single storage call
const data = await browser.storage.local.get(['key1', 'key2', 'key3']);3. Memory Leaks in Content Scripts
3. 内容脚本中的内存泄漏
Problem: Event listeners not cleaned up when navigating away.
javascript
// BAD: Listener persists after navigation
window.addEventListener('scroll', handleScroll);Solution: Use AbortController or cleanup handlers.
javascript
// GOOD: Cleanup on unload
const controller = new AbortController();
window.addEventListener('scroll', handleScroll, { signal: controller.signal });
window.addEventListener('beforeunload', () => controller.abort());问题: 页面导航后未清理事件监听器。
javascript
// BAD: Listener persists after navigation
window.addEventListener('scroll', handleScroll);解决方案: 使用AbortController或清理处理程序。
javascript
// GOOD: Cleanup on unload
const controller = new AbortController();
window.addEventListener('scroll', handleScroll, { signal: controller.signal });
window.addEventListener('beforeunload', () => controller.abort());4. Large Message Payloads
4. 大消息负载
Problem: Sending large data between background and content scripts.
javascript
// BAD: Serializing megabytes of data
browser.runtime.sendMessage({ type: 'data', payload: hugeArray });Solution: Use chunking or IndexedDB for large data.
javascript
// GOOD: Store in IndexedDB, pass reference
await idb.put('largeData', hugeArray);
browser.runtime.sendMessage({ type: 'dataReady', key: 'largeData' });问题: 在后台脚本与内容脚本之间发送大量数据。
javascript
// BAD: Serializing megabytes of data
browser.runtime.sendMessage({ type: 'data', payload: hugeArray });解决方案: 对大数据使用分块或IndexedDB。
javascript
// GOOD: Store in IndexedDB, pass reference
await idb.put('largeData', hugeArray);
browser.runtime.sendMessage({ type: 'dataReady', key: 'largeData' });5. Blocking Service Worker
5. 阻塞式Service Worker
Problem: Long-running operations in service worker prevent suspension.
javascript
// BAD: Service worker can't sleep
background.js:
while (processing) {
await processChunk();
// Runs for minutes...
}Solution: Use alarms for long operations.
javascript
// GOOD: Let service worker sleep between chunks
browser.alarms.create('processChunk', { delayInMinutes: 0.1 });
browser.alarms.onAlarm.addListener(async (alarm) => {
if (alarm.name === 'processChunk') {
const done = await processNextChunk();
if (!done) {
browser.alarms.create('processChunk', { delayInMinutes: 0.1 });
}
}
});问题: Service Worker中的长时间运行操作阻止其挂起。
javascript
// BAD: Service worker can't sleep
background.js:
while (processing) {
await processChunk();
// Runs for minutes...
}解决方案: 使用alarms处理长时间操作。
javascript
// GOOD: Let service worker sleep between chunks
browser.alarms.create('processChunk', { delayInMinutes: 0.1 });
browser.alarms.onAlarm.addListener(async (alarm) => {
if (alarm.name === 'processChunk') {
const done = await processNextChunk();
if (!done) {
browser.alarms.create('processChunk', { delayInMinutes: 0.1 });
}
}
});Store Rejection Reasons
应用商店拒绝原因
Chrome Web Store
Chrome Web Store
| Reason | Trigger | Fix |
|---|---|---|
| Broad host permissions | | Narrow to specific domains |
| Remote code execution | Loading scripts from external URLs | Bundle all code locally |
| Misleading metadata | Description doesn't match functionality | Accurate description |
| Excessive permissions | Requesting unused permissions | Remove unnecessary permissions |
| Privacy violation | Collecting data without disclosure | Add privacy policy |
| Single purpose violation | Multiple unrelated features | Split into separate extensions |
| Affiliate/redirect abuse | Hidden affiliate links | Transparent disclosure |
| 原因 | 触发条件 | 修复方案 |
|---|---|---|
| 宽泛的主机权限 | 使用 | 缩小到特定域名 |
| 远程代码执行 | 从外部URL加载脚本 | 所有代码本地打包 |
| 误导性元数据 | 描述与功能不符 | 准确描述功能 |
| 过度权限申请 | 请求未使用的权限 | 删除不必要的权限 |
| 隐私违规 | 未披露就收集数据 | 添加隐私政策 |
| 违反单一用途原则 | 包含多个不相关功能 | 拆分为独立扩展 |
| 联盟链接/重定向滥用 | 隐藏联盟链接 | 透明披露 |
Firefox Add-ons (AMO)
Firefox Add-ons (AMO)
| Reason | Trigger | Fix |
|---|---|---|
| Obfuscated code | Minified code without source | Submit source code |
| eval() usage | Dynamic code execution | Refactor to static code |
| Missing gecko ID | No browser_specific_settings | Add gecko.id to manifest |
| CSP violations | Inline scripts in HTML | Move to external files |
| Tracking without consent | Analytics without disclosure | Add opt-in consent |
| 原因 | 触发条件 | 修复方案 |
|---|---|---|
| 混淆代码 | 压缩代码但未提供源码 | 提交源代码 |
| 使用eval() | 动态代码执行 | 重构为静态代码 |
| 缺少Gecko ID | 无browser_specific_settings | 在清单中添加gecko.id |
| CSP违规 | HTML中使用内联脚本 | 移至外部文件 |
| 未经同意的跟踪 | 未披露就使用分析工具 | 添加 opt-in 同意机制 |
Safari App Store
Safari App Store
| Reason | Trigger | Fix |
|---|---|---|
| Missing privacy manifest | iOS 17+ requirement | Add PrivacyInfo.xcprivacy |
| Guideline 2.3 violations | Inaccurate metadata | Match screenshots to functionality |
| Guideline 4.2 violations | Spam/low quality | Add meaningful functionality |
| Missing entitlements | Using APIs without entitlement | Configure in Xcode |
| 原因 | 触发条件 | 修复方案 |
|---|---|---|
| 缺少隐私清单 | iOS 17+ 要求 | 添加PrivacyInfo.xcprivacy |
| 违反准则2.3 | 元数据不准确 | 截图与功能匹配 |
| 违反准则4.2 | 垃圾内容/低质量 | 添加有意义的功能 |
| 缺少权限配置 | 使用未配置权限的API | 在Xcode中配置 |
API Misuse Patterns
API误用模式
1. tabs.query Without Filters
1. 无过滤的tabs.query
Problem: Querying all tabs unnecessarily.
javascript
// BAD: Gets ALL tabs across ALL windows
const tabs = await browser.tabs.query({});Solution: Use specific filters.
javascript
// GOOD: Only active tab in current window
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });问题: 不必要地查询所有标签页。
javascript
// BAD: Gets ALL tabs across ALL windows
const tabs = await browser.tabs.query({});解决方案: 使用特定过滤器。
javascript
// GOOD: Only active tab in current window
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });2. executeScript Without Target
2. 无目标的executeScript
Problem: Injecting scripts without specifying target.
javascript
// BAD: Injects into wrong tab or fails silently
browser.scripting.executeScript({
func: myFunction
});Solution: Always specify target.
javascript
// GOOD: Explicit target
browser.scripting.executeScript({
target: { tabId: tab.id },
func: myFunction
});问题: 未指定目标就注入脚本。
javascript
// BAD: Injects into wrong tab or fails silently
browser.scripting.executeScript({
func: myFunction
});解决方案: 始终指定目标。
javascript
// GOOD: Explicit target
browser.scripting.executeScript({
target: { tabId: tab.id },
func: myFunction
});3. Ignoring Promise Rejections
3. 忽略Promise拒绝
Problem: Not handling API errors.
javascript
// BAD: Silent failures
browser.tabs.sendMessage(tabId, message);Solution: Handle errors appropriately.
javascript
// GOOD: Handle disconnected tabs
try {
await browser.tabs.sendMessage(tabId, message);
} catch (error) {
if (error.message.includes('disconnected')) {
// Tab closed or navigated away - expected
} else {
console.error('Unexpected error:', error);
}
}问题: 未处理API错误。
javascript
// BAD: Silent failures
browser.tabs.sendMessage(tabId, message);解决方案: 适当处理错误。
javascript
// GOOD: Handle disconnected tabs
try {
await browser.tabs.sendMessage(tabId, message);
} catch (error) {
if (error.message.includes('disconnected')) {
// Tab closed or navigated away - expected
} else {
console.error('Unexpected error:', error);
}
}4. Storage Without Limits
4. 无限制存储
Problem: Writing unlimited data to storage.
javascript
// BAD: Storage grows unbounded
const history = await browser.storage.local.get('history');
history.items.push(newItem); // Never removes old items
await browser.storage.local.set({ history });Solution: Implement retention policy.
javascript
// GOOD: Limit to last 1000 items
const MAX_HISTORY = 1000;
const history = await browser.storage.local.get('history');
history.items.push(newItem);
if (history.items.length > MAX_HISTORY) {
history.items = history.items.slice(-MAX_HISTORY);
}
await browser.storage.local.set({ history });问题: 向存储写入无限制的数据。
javascript
// BAD: Storage grows unbounded
const history = await browser.storage.local.get('history');
history.items.push(newItem); // Never removes old items
await browser.storage.local.set({ history });解决方案: 实现保留策略。
javascript
// GOOD: Limit to last 1000 items
const MAX_HISTORY = 1000;
const history = await browser.storage.local.get('history');
history.items.push(newItem);
if (history.items.length > MAX_HISTORY) {
history.items = history.items.slice(-MAX_HISTORY);
}
await browser.storage.local.set({ history });Manifest Anti-Patterns
清单文件反模式
1. Over-Permissioning
1. 过度权限申请
json
// BAD: Requests everything
{
"permissions": [
"<all_urls>",
"tabs",
"history",
"bookmarks",
"downloads",
"webRequest",
"webRequestBlocking"
]
}json
// GOOD: Minimum viable permissions
{
"permissions": ["storage", "activeTab"],
"optional_permissions": ["tabs"],
"host_permissions": ["*://example.com/*"]
}json
// BAD: Requests everything
{
"permissions": [
"<all_urls>",
"tabs",
"history",
"bookmarks",
"downloads",
"webRequest",
"webRequestBlocking"
]
}json
// GOOD: Minimum viable permissions
{
"permissions": ["storage", "activeTab"],
"optional_permissions": ["tabs"],
"host_permissions": ["*://example.com/*"]
}2. Missing Icons
2. 缺少图标
json
// BAD: Only one icon size
{
"icons": {
"128": "icon.png"
}
}json
// GOOD: Multiple sizes for different contexts
{
"icons": {
"16": "icons/16.png",
"32": "icons/32.png",
"48": "icons/48.png",
"128": "icons/128.png"
}
}json
// BAD: Only one icon size
{
"icons": {
"128": "icon.png"
}
}json
// GOOD: Multiple sizes for different contexts
{
"icons": {
"16": "icons/16.png",
"32": "icons/32.png",
"48": "icons/48.png",
"128": "icons/128.png"
}
}3. Insecure CSP
3. 不安全的CSP
json
// BAD: Allows unsafe-eval
{
"content_security_policy": {
"extension_pages": "script-src 'self' 'unsafe-eval'; object-src 'self'"
}
}json
// GOOD: Strict CSP
{
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
}
}json
// BAD: Allows unsafe-eval
{
"content_security_policy": {
"extension_pages": "script-src 'self' 'unsafe-eval'; object-src 'self'"
}
}json
// GOOD: Strict CSP
{
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
}
}Content Script Pitfalls
内容脚本陷阱
1. Global Namespace Pollution
1. 全局命名空间污染
Problem: Variables leak into page scope.
javascript
// BAD: Pollutes global namespace
var myExtensionData = {};
// Also bad: top-level const/let in non-module scripts
const config = {};Solution: Use IIFE or modules.
javascript
// GOOD: IIFE isolation
(function() {
const myExtensionData = {};
// All code here
})();
// BETTER: Use ES modules (MV3)
// manifest.json: "content_scripts": [{ "js": ["content.js"], "type": "module" }]问题: 变量泄漏到页面作用域。
javascript
// BAD: Pollutes global namespace
var myExtensionData = {};
// Also bad: top-level const/let in non-module scripts
const config = {};解决方案: 使用IIFE或模块。
javascript
// GOOD: IIFE isolation
(function() {
const myExtensionData = {};
// All code here
})();
// BETTER: Use ES modules (MV3)
// manifest.json: "content_scripts": [{ "js": ["content.js"], "type": "module" }]2. Race Conditions with Page Scripts
2. 与页面脚本的竞态条件
Problem: Page scripts modify DOM before content script runs.
javascript
// BAD: Element may not exist yet or be replaced
const button = document.querySelector('.submit');
button.addEventListener('click', handler);Solution: Wait for element with timeout.
javascript
// GOOD: Wait for element with timeout
function waitForElement(selector, timeout = 5000) {
return new Promise((resolve, reject) => {
const element = document.querySelector(selector);
if (element) return resolve(element);
const observer = new MutationObserver(() => {
const element = document.querySelector(selector);
if (element) {
observer.disconnect();
resolve(element);
}
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => {
observer.disconnect();
reject(new Error(`Timeout waiting for ${selector}`));
}, timeout);
});
}问题: 内容脚本运行前页面脚本已修改DOM。
javascript
// BAD: Element may not exist yet or be replaced
const button = document.querySelector('.submit');
button.addEventListener('click', handler);解决方案: 带超时等待元素加载。
javascript
// GOOD: Wait for element with timeout
function waitForElement(selector, timeout = 5000) {
return new Promise((resolve, reject) => {
const element = document.querySelector(selector);
if (element) return resolve(element);
const observer = new MutationObserver(() => {
const element = document.querySelector(selector);
if (element) {
observer.disconnect();
resolve(element);
}
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => {
observer.disconnect();
reject(new Error(`Timeout waiting for ${selector}`));
}, timeout);
});
}Cross-Browser Pitfalls
跨浏览器陷阱
1. Chrome-Only APIs
1. Chrome专属API
| Chrome API | Firefox Alternative | Safari Alternative |
|---|---|---|
| Not available | Not available |
| Not available | Not available |
| Partial support | Limited support |
| Chrome API | Firefox替代方案 | Safari替代方案 |
|---|---|---|
| 无 | 无 |
| 无 | 无 |
| 部分支持 | 有限支持 |
2. Callback vs Promise APIs
2. 回调与Promise API
javascript
// BAD: Chrome callback style
chrome.tabs.query({}, function(tabs) {
// Works in Chrome, fails in Firefox
});javascript
// GOOD: Use webextension-polyfill or browser.*
const tabs = await browser.tabs.query({});javascript
// BAD: Chrome callback style
chrome.tabs.query({}, function(tabs) {
// Works in Chrome, fails in Firefox
});javascript
// GOOD: Use webextension-polyfill or browser.*
const tabs = await browser.tabs.query({});Checklist Before Submission
提交前检查清单
- No without justification
<all_urls> - No or
eval()new Function() - No remote code loading
- No obfuscated/minified code (or source provided)
- Privacy policy if collecting data
- Accurate store description
- Multiple icon sizes
- Gecko ID for Firefox
- Tested on all target browsers
- Storage limits implemented
- Error handling for all API calls
- 无合理理由不使用
<all_urls> - 未使用或
eval()new Function() - 未加载远程代码
- 无混淆/压缩代码(或已提供源码)
- 收集数据时已添加隐私政策
- 应用商店描述准确
- 提供多种尺寸的图标
- Firefox扩展已添加Gecko ID
- 在所有目标浏览器上测试过
- 已实现存储限制
- 所有API调用都有错误处理