extension-anti-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Browser 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-PatternImpactSolution
<all_urls>
permission
Store rejectionUse specific host permissions
Blocking background operationsExtension suspend issuesUse async/Promise patterns
DOM polling in content scriptsHigh CPU usageUse MutationObserver
Unbounded storage growthMemory exhaustionImplement retention policies
eval()
or
new Function()
CSP violation, store rejectionUse static code
反模式影响解决方案
<all_urls>
权限
应用商店拒绝使用特定主机权限
阻塞式后台操作扩展挂起问题使用async/Promise模式
内容脚本中的DOM轮询高CPU占用使用MutationObserver
存储无限制增长内存耗尽实现保留策略
eval()
new Function()
CSP违规、应用商店拒绝使用静态代码

Performance Anti-Patterns

性能反模式

1. DOM Polling

1. DOM轮询

Problem: Using
setInterval
to check for DOM changes.
javascript
// 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 });
问题: 使用
setInterval
检测DOM变化。
javascript
// 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

ReasonTriggerFix
Broad host permissions
<all_urls>
or
*://*/*
without justification
Narrow to specific domains
Remote code executionLoading scripts from external URLsBundle all code locally
Misleading metadataDescription doesn't match functionalityAccurate description
Excessive permissionsRequesting unused permissionsRemove unnecessary permissions
Privacy violationCollecting data without disclosureAdd privacy policy
Single purpose violationMultiple unrelated featuresSplit into separate extensions
Affiliate/redirect abuseHidden affiliate linksTransparent disclosure
原因触发条件修复方案
宽泛的主机权限使用
<all_urls>
*://*/*
且无合理理由
缩小到特定域名
远程代码执行从外部URL加载脚本所有代码本地打包
误导性元数据描述与功能不符准确描述功能
过度权限申请请求未使用的权限删除不必要的权限
隐私违规未披露就收集数据添加隐私政策
违反单一用途原则包含多个不相关功能拆分为独立扩展
联盟链接/重定向滥用隐藏联盟链接透明披露

Firefox Add-ons (AMO)

Firefox Add-ons (AMO)

ReasonTriggerFix
Obfuscated codeMinified code without sourceSubmit source code
eval() usageDynamic code executionRefactor to static code
Missing gecko IDNo browser_specific_settingsAdd gecko.id to manifest
CSP violationsInline scripts in HTMLMove to external files
Tracking without consentAnalytics without disclosureAdd opt-in consent
原因触发条件修复方案
混淆代码压缩代码但未提供源码提交源代码
使用eval()动态代码执行重构为静态代码
缺少Gecko ID无browser_specific_settings在清单中添加gecko.id
CSP违规HTML中使用内联脚本移至外部文件
未经同意的跟踪未披露就使用分析工具添加 opt-in 同意机制

Safari App Store

Safari App Store

ReasonTriggerFix
Missing privacy manifestiOS 17+ requirementAdd PrivacyInfo.xcprivacy
Guideline 2.3 violationsInaccurate metadataMatch screenshots to functionality
Guideline 4.2 violationsSpam/low qualityAdd meaningful functionality
Missing entitlementsUsing APIs without entitlementConfigure 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 APIFirefox AlternativeSafari Alternative
chrome.sidePanel
Not availableNot available
chrome.offscreen
Not availableNot available
chrome.declarativeNetRequest
Partial supportLimited support
Chrome APIFirefox替代方案Safari替代方案
chrome.sidePanel
chrome.offscreen
chrome.declarativeNetRequest
部分支持有限支持

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
    <all_urls>
    without justification
  • No
    eval()
    or
    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调用都有错误处理