chrome-extension

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Chrome Extension Development (Manifest V3)

Chrome扩展开发(Manifest V3)

This skill covers everything needed to build, debug, and publish Chrome extensions with MV3. It is organized as a routing document: read this file first to understand the architecture and decision points, then load the relevant reference file for implementation details.
本技能涵盖使用MV3构建、调试和发布Chrome扩展所需的全部内容。它作为一份路由文档组织:先阅读本文档以了解架构和决策要点,然后加载相关参考文件获取实现细节。

Reference files

参考文件

Read only the reference files relevant to the current task. Each file is self-contained.
FileWhen to read
references/manifest-v3.md
Setting up or modifying manifest.json, configuring icons, versioning
references/service-worker.md
Background logic, lifecycle, state persistence, alarms, events
references/content-scripts.md
Injecting code into pages, isolated/main world, dynamic injection, SPA handling, orphaning
references/messaging-rpc.md
Communication between any contexts, typed protocols, RPC layer, async handler patterns
references/ui-surfaces.md
Popup, options page, side panel, context menus, commands, notifications, omnibox, devtools panel
references/storage.md
chrome.storage (local/sync/session), quotas, reactive patterns, framework hooks
references/network-csp.md
HTTP requests from content scripts, CSP bypass relay, declarativeNetRequest, offscreen docs, CORS
references/permissions.md
Required/optional permissions, host permissions, activeTab, runtime request flow
references/web-accessible-resources.md
Exposing extension files to web pages, security implications
references/typescript-build.md
TypeScript setup, project structure, build tools comparison, bundling
references/publishing.md
Chrome Web Store submission, review process, rejection reasons, updates, privacy policy
references/execution-contexts.md
Communication flow diagrams, per-context capabilities/limits, choosing the right messaging method
references/debugging-mistakes.md
DevTools for extensions, testing SW termination, common gotchas, error patterns
仅阅读与当前任务相关的参考文件,每个文件都是独立完整的。
文件阅读场景
references/manifest-v3.md
设置或修改manifest.json、配置图标、版本管理
references/service-worker.md
后台逻辑、生命周期、状态持久化、定时任务、事件
references/content-scripts.md
向页面注入代码、隔离/主环境、动态注入、SPA处理、孤立脚本检测
references/messaging-rpc.md
任意上下文间通信、类型化协议、RPC层、异步处理器模式
references/ui-surfaces.md
Popup、选项页、侧边栏、上下文菜单、命令、通知、地址栏建议、开发者工具面板
references/storage.md
chrome.storage(local/sync/session)、配额、响应式模式、框架钩子
references/network-csp.md
从content script发起HTTP请求、CSP绕过中继、declarativeNetRequest、离屏文档、CORS
references/permissions.md
必填/可选权限、主机权限、activeTab、运行时请求流程
references/web-accessible-resources.md
向网页暴露扩展文件、安全影响
references/typescript-build.md
TypeScript配置、项目结构、构建工具对比、打包
references/publishing.md
Chrome Web Store提交、审核流程、拒绝原因、更新、隐私政策
references/execution-contexts.md
通信流程图、各上下文能力/限制、选择合适的通信方式
references/debugging-mistakes.md
扩展专用DevTools、测试Service Worker终止、常见陷阱、错误模式

Architecture overview

架构概述

A Chrome extension has up to 5 execution contexts that communicate via message passing:
┌──────────────────────────────────────────────────────────┐
│ Extension Process                                        │
│  ┌─────────────────┐  ┌───────┐  ┌─────────┐  ┌──────┐ │
│  │ Service Worker   │  │ Popup │  │ Options │  │ Side │ │
│  │ (background)     │  │       │  │  Page   │  │Panel │ │
│  │ - No DOM         │  │ Full  │  │  Full   │  │ Full │ │
│  │ - Ephemeral      │  │ DOM   │  │  DOM    │  │ DOM  │ │
│  │ - All chrome.*   │  │ All   │  │  All    │  │ All  │ │
│  │   APIs           │  │ APIs  │  │  APIs   │  │ APIs │ │
│  └────────┬─────────┘  └───┬───┘  └────┬────┘  └──┬───┘ │
│           │ chrome.runtime.sendMessage / connect   │     │
└───────────┼────────────────┼───────────┼──────────┼──────┘
            │                │           │          │
    chrome.tabs.sendMessage  │           │          │
            │                │           │          │
┌───────────┼────────────────┼───────────┼──────────┼──────┐
│ Web Page  ▼                                              │
│  ┌──────────────────┐    ┌──────────────────┐            │
│  │ Content Script    │    │ Main World Script │            │
│  │ (isolated world)  │◄──►│ (page context)    │            │
│  │ - Shared DOM      │    │ - Shared DOM      │            │
│  │ - Own JS scope    │    │ - Page JS scope   │            │
│  │ - chrome.runtime  │    │ - No chrome.* API │            │
│  │ - chrome.storage  │    │ - Full page access│            │
│  │ - Subject to CSP  │    │ - Subject to CSP  │            │
│  │   (network only)  │    │   (fully)         │            │
│  └──────────────────┘    └──────────────────┘            │
│           ▲ window.postMessage                           │
│           │ (through shared DOM)                         │
└──────────────────────────────────────────────────────────┘
Chrome扩展最多包含5个执行上下文,它们通过消息传递进行通信:
┌──────────────────────────────────────────────────────────┐
│ 扩展进程                                        │
│  ┌─────────────────┐  ┌───────┐  ┌─────────┐  ┌──────┐ │
│  │ Service Worker   │  │ Popup │  │ 选项页 │  │ 侧边栏 │ │
│  │ (后台)     │  │       │  │        │  │        │ │
│  │ - 无DOM         │  │ 完整DOM   │  │ 完整DOM    │  │ 完整DOM  │ │
│  │ - 临时存在      │  │ 所有API  │  │ 所有API   │  │ 所有API │ │
│  │ - 支持所有chrome.*   │  │ APIs  │  │ APIs   │  │ APIs │ │
│  │   API           │  │       │  │        │  │        │ │
│  └────────┬─────────┘  └───┬───┘  └────┬────┘  └──┬───┘ │
│           │ chrome.runtime.sendMessage / connect   │     │
└───────────┼────────────────┼───────────┼──────────┼──────┘
            │                │           │          │
    chrome.tabs.sendMessage  │           │          │
            │                │           │          │
┌───────────┼────────────────┼───────────┼──────────┼──────┐
│ 网页  ▼                                              │
│  ┌──────────────────┐    ┌──────────────────┐            │
│  │ Content Script    │    │ 主世界脚本 │            │
│  │ (隔离环境)  │◄──►│ (页面上下文)    │            │
│  │ - 共享DOM      │    │ - 共享DOM      │            │
│  │ - 独立JS作用域    │    │ - 页面JS作用域   │            │
│  │ - 支持chrome.runtime  │    │ - 无chrome.* API │            │
│  │ - 支持chrome.storage  │    │ - 完全访问页面│            │
│  │ - 受CSP限制  │    │ - 受CSP限制  │            │
│  │   (仅网络)  │    │   (完全)         │            │
│  └──────────────────┘    └──────────────────┘            │
│           ▲ window.postMessage                           │
│           │ (通过共享DOM)                         │
└──────────────────────────────────────────────────────────┘

Communication flows (labeled channels)

通信流程(标记通道)

┌───────────────────────────────────────────────────────────────────────────┐
│ Extension Process                                                         │
│                                                                           │
│  ┌─────────────────┐  chrome.runtime   ┌───────┐  ┌─────────┐  ┌──────┐ │
│  │ Service Worker   │◄─.sendMessage()──│ Popup │  │ Options │  │ Side │ │
│  │ (background)     │◄─.connect()──────│       │  │  Page   │  │Panel │ │
│  │                  │                  └───────┘  └─────────┘  └──────┘ │
│  │ - No DOM         │  ┌────────────────────────────────────────────┐   │
│  │ - Ephemeral 30s  │  │ SW cannot push to these pages.             │   │
│  │ - All chrome.*   │  │ Use: ports (.connect) or storage.onChanged │   │
│  └────────┬─────────┘  └────────────────────────────────────────────┘   │
│           │                                                              │
│  chrome.storage.onChanged ◄── fires across ALL contexts simultaneously  │
│                                                                           │
└───────────┼──────────────────────────────────────────────────────────────┘
            │ chrome.tabs.sendMessage(tabId, ...) [SW must know tabId]
┌───────────┼──────────────────────────────────────────────────────────────┐
│ Web Page  ▼                                                              │
│  ┌──────────────────┐  window.postMessage  ┌──────────────────┐         │
│  │ Content Script    │◄───────────────────►│ Main World Script │         │
│  │ (isolated world)  │  Custom DOM events  │ (page context)    │         │
│  │                   │                     │                   │         │
│  │ chrome.runtime ───┼── to/from SW        │ No chrome.* APIs  │         │
│  │ chrome.storage    │                     │ Full page JS      │         │
│  │ Shared DOM        │                     │ Shared DOM        │         │
│  │ Page CSP (network)│                     │ Page CSP (full)   │         │
│  └──────────────────┘                     └──────────────────┘         │
└──────────────────────────────────────────────────────────────────────────┘
For detailed flow diagrams (three-layer bridge, cross-extension, storage broadcast) and a per-context breakdown of permissions, limits, and workarounds: → Read
references/execution-contexts.md
┌───────────────────────────────────────────────────────────────────────────┐
│ 扩展进程                                                         │
│                                                                           │
│  ┌─────────────────┐  chrome.runtime   ┌───────┐  ┌─────────┐  ┌──────┐ │
│  │ Service Worker   │◄─.sendMessage()──│ Popup │  │ 选项页 │  │ 侧边栏 │ │
│  │ (后台)     │◄─.connect()──────│       │  │        │  │        │ │
│  │                  │                  └───────┘  └─────────┘  └──────┘ │
│  │ - 无DOM         │  ┌────────────────────────────────────────────┐   │
│  │ - 临时存在30s  │  │ SW无法主动向这些页面推送消息。             │   │
│  │ - 支持所有chrome.*   │  │ 推荐使用:端口(.connect)或storage.onChanged │   │
│  └────────┬─────────┘  └────────────────────────────────────────────┘   │
│           │                                                              │
│  chrome.storage.onChanged ◄── 同时向所有上下文触发事件  │
│                                                                           │
└───────────┼──────────────────────────────────────────────────────────────┘
            │ chrome.tabs.sendMessage(tabId, ...) [SW必须知晓tabId]
┌───────────┼──────────────────────────────────────────────────────────────┐
│ 网页  ▼                                                              │
│  ┌──────────────────┐  window.postMessage  ┌──────────────────┐         │
│  │ Content Script    │◄───────────────────►│ 主世界脚本 │         │
│  │ (隔离环境)  │  自定义DOM事件  │ (页面上下文)    │         │
│  │                   │                     │                   │         │
│  │ chrome.runtime ───┼── 与SW通信        │ 无chrome.* APIs  │         │
│  │ chrome.storage    │                     │ 完全访问页面JS      │         │
│  │ 共享DOM        │                     │ 共享DOM        │         │
│  │ 页面CSP(仅网络)│                     │ 页面CSP(完全)   │         │
│  └──────────────────┘                     └──────────────────┘         │
└──────────────────────────────────────────────────────────────────────────┘
如需详细的流程图(三层桥、跨扩展、存储广播)以及各上下文的权限、限制和解决方案说明,请阅读
references/execution-contexts.md

Communication methods at a glance

通信方法概览

MethodDirectionBest for
chrome.runtime.sendMessage
Any ext context → SWOne-shot request/response (90% of cases)
chrome.tabs.sendMessage
SW → content script (by tabId)Pushing data to a specific tab
chrome.runtime.connect
(Port)
BidirectionalStreaming, progress, SW ↔ popup
window.postMessage
Between worlds on same pagePage JS ↔ content script bridge
chrome.storage.onChanged
Broadcast to all contextsSettings sync, no messaging needed
→ Full matrix with limits and edge cases:
references/execution-contexts.md
→ Implementation patterns, typed protocols, RPC layer:
references/messaging-rpc.md
方法方向适用场景
chrome.runtime.sendMessage
任意扩展上下文 → SW一次性请求/响应(90%的场景)
chrome.tabs.sendMessage
SW → content script(通过tabId)向特定标签页推送数据
chrome.runtime.connect
(Port)
双向流传输、进度跟踪、SW ↔ popup
window.postMessage
同一页面的不同环境间页面JS ↔ content script桥接
chrome.storage.onChanged
广播至所有上下文设置同步、无需消息传递
→ 包含限制和边缘情况的完整矩阵:
references/execution-contexts.md
→ 实现模式、类型化协议、RPC层:
references/messaging-rpc.md

Key architectural rules

核心架构规则

  1. Service worker is ephemeral. It terminates after 30s of inactivity. All state must be persisted to chrome.storage. All event listeners must be registered synchronously at the top level. Never use setTimeout/setInterval for anything beyond a few seconds. → Read
    references/service-worker.md
  2. Content scripts run in the page's origin. Network requests from content scripts are subject to the page's CSP and CORS. To bypass, relay through the service worker. → Read
    references/network-csp.md
  3. Messaging is the backbone. Every cross-context interaction uses chrome.runtime messaging. The #1 bug: forgetting to
    return true
    from async message listeners. → Read
    references/messaging-rpc.md
  4. Permissions determine CWS review speed. Broad host_permissions trigger manual review (weeks). activeTab + optional permissions = fast automated review. → Read
    references/permissions.md
  5. Popup is destroyed on blur. Side panel persists. Choose based on interaction duration. → Read
    references/ui-surfaces.md
  1. Service Worker是临时存在的:闲置30秒后会终止。所有状态必须持久化到chrome.storage。所有事件监听器必须在顶层同步注册。任何超过几秒的任务都不要使用setTimeout/setInterval。→ 阅读
    references/service-worker.md
  2. Content Script运行在页面源环境:从content script发起的网络请求受页面CSP和CORS限制。如需绕过,通过service worker中继。→ 阅读
    references/network-csp.md
  3. 消息传递是核心:所有跨上下文交互都使用chrome.runtime消息传递。最常见的错误:异步消息监听器忘记返回
    true
    。→ 阅读
    references/messaging-rpc.md
  4. 权限决定Chrome Web Store审核速度:宽泛的host_permissions会触发人工审核(耗时数周)。activeTab + 可选权限 = 快速自动审核。→ 阅读
    references/permissions.md
  5. Popup失去焦点即销毁:侧边栏会持久存在。根据交互时长选择合适的UI。→ 阅读
    references/ui-surfaces.md

Decision tree: which context handles what?

决策树:选择合适的处理上下文

"I need to run code when the user visits a page"

"我需要在用户访问页面时运行代码"

→ Content script. Static (manifest) for known URL patterns, dynamic (chrome.scripting) for user-triggered injection. Default to isolated world unless you need page JS access. → Read
references/content-scripts.md
→ 使用Content Script。已知URL模式使用静态注入(manifest配置),用户触发场景使用动态注入(chrome.scripting)。默认使用隔离环境,除非需要访问页面JS。→ 阅读
references/content-scripts.md

"I need to make an HTTP request to my API"

"我需要向我的API发起HTTP请求"

  • From popup/options/side panel: direct fetch() works (extension origin, no CSP issues)
  • From content script on a page with restrictive CSP: relay through service worker
  • From service worker: direct fetch() works (requires host_permissions for the target domain) → Read
    references/network-csp.md
  • 从popup/选项页/侧边栏发起:直接使用fetch()即可(扩展源,无CSP问题)
  • 从受严格CSP限制的页面中的content script发起:通过service worker中继
  • 从service worker发起:直接使用fetch()即可(需目标域名的host_permissions)→ 阅读
    references/network-csp.md

"I need to store user settings"

"我需要存储用户设置"

  • Settings that sync across devices: chrome.storage.sync (100KB limit)
  • Large data or caches: chrome.storage.local (10MB, or unlimited with permission)
  • Ephemeral state surviving SW restarts: chrome.storage.session → Read
    references/storage.md
  • 跨设备同步的设置:chrome.storage.sync(100KB限制)
  • 大数据或缓存:chrome.storage.local(10MB,可通过权限解锁无限空间)
  • 可在SW重启后保留的临时状态:chrome.storage.session → 阅读
    references/storage.md

"I need to modify HTTP headers or block requests"

"我需要修改HTTP头或拦截请求"

→ declarativeNetRequest (NOT webRequest, which lost blocking in MV3) → Read
references/network-csp.md
→ 使用declarativeNetRequest(不要使用webRequest,MV3已移除其拦截功能)→ 阅读
references/network-csp.md

"I need the page's JavaScript to talk to my extension"

"我需要让页面的JavaScript与我的扩展通信"

→ Three-layer bridge: page (window.postMessage) → content script → service worker → Read
references/messaging-rpc.md
→ 使用三层桥:页面(window.postMessage)→ content script → service worker → 阅读
references/messaging-rpc.md

"I need to understand what each context can and cannot do"

"我需要了解每个上下文的能力和限制"

→ Read
references/execution-contexts.md
— per-context cards listing chrome.* access, DOM, network, storage, lifetime, hard limits, and practical workarounds.
→ 阅读
references/execution-contexts.md
—— 按上下文分类的卡片,列出chrome.* API访问权限、DOM、网络、存储、生命周期、硬限制及实用解决方案。

"I need periodic background tasks"

"我需要定期执行后台任务"

→ chrome.alarms (minimum 30s interval). NOT setTimeout. → Read
references/service-worker.md
→ 使用chrome.alarms(最小间隔30秒)。不要使用setTimeout。→ 阅读
references/service-worker.md

"I need DOM APIs in the background" (DOMParser, Canvas, Audio)

"我需要在后台使用DOM API"(DOMParser、Canvas、Audio)

→ Offscreen document. One per extension, only chrome.runtime available. → Read
references/network-csp.md
→ 使用离屏文档。每个扩展只能有一个,仅支持chrome.runtime。→ 阅读
references/network-csp.md

"I need to authenticate with OAuth"

"我需要OAuth认证"

→ chrome.identity.launchWebAuthFlow() or chrome.identity.getAuthToken() (Google only) → Read
references/service-worker.md
(identity section)
→ 使用chrome.identity.launchWebAuthFlow()或chrome.identity.getAuthToken()(仅支持Google)→ 阅读
references/service-worker.md
(身份认证章节)

Workflow: new extension from scratch

工作流:从零创建新扩展

  1. Define the manifest with minimum permissions. Start with
    activeTab
    +
    scripting
    . → Read
    references/manifest-v3.md
  2. Set up TypeScript and build tooling (or use CRXJS for Vite-based dev). → Read
    references/typescript-build.md
  3. Implement the service worker with all event listeners at the top level. → Read
    references/service-worker.md
  4. Add content scripts if you need page interaction. → Read
    references/content-scripts.md
  5. Build UI surfaces (popup, options, side panel) as needed. → Read
    references/ui-surfaces.md
  6. Wire up messaging between all contexts. → Read
    references/messaging-rpc.md
  7. Test with DevTools, specifically test service worker termination. → Read
    references/debugging-mistakes.md
  8. Publish to Chrome Web Store. → Read
    references/publishing.md
  1. 定义manifest,仅包含必要权限。从
    activeTab
    +
    scripting
    开始。→ 阅读
    references/manifest-v3.md
  2. 配置TypeScript和构建工具(或使用CRXJS基于Vite进行开发)。→ 阅读
    references/typescript-build.md
  3. 实现service worker,所有事件监听器在顶层注册。→ 阅读
    references/service-worker.md
  4. 添加content script(如需与页面交互)。→ 阅读
    references/content-scripts.md
  5. 构建UI界面(popup、选项页、侧边栏)(按需)。→ 阅读
    references/ui-surfaces.md
  6. 完成所有上下文间的消息传递配置。→ 阅读
    references/messaging-rpc.md
  7. 使用DevTools测试,重点测试service worker终止场景。→ 阅读
    references/debugging-mistakes.md
  8. 发布到Chrome Web Store。→ 阅读
    references/publishing.md

Workflow: adding a feature to an existing extension

工作流:为现有扩展添加功能

  1. Identify which context the feature belongs to (see decision tree above).
  2. Read the relevant reference file(s) for that context.
  3. Check if new permissions are needed. Prefer optional_permissions for new capabilities. → Read
    references/permissions.md
  4. Update the manifest if adding new content scripts, UI surfaces, or permissions.
  5. Handle extension updates gracefully (content script orphaning). → Read
    references/content-scripts.md
    (orphaning section)
  1. 根据上述决策树确定功能所属的上下文。
  2. 阅读该上下文对应的参考文件。
  3. 检查是否需要新增权限。优先为新功能使用optional_permissions。→ 阅读
    references/permissions.md
  4. 若新增content script、UI界面或权限,更新manifest。
  5. 优雅处理扩展更新(content script孤立问题)。→ 阅读
    references/content-scripts.md
    (孤立脚本章节)

Minimal manifest.json template

极简manifest.json模板

json
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "description": "What it does in one sentence",
  "permissions": ["storage", "activeTab", "scripting"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}
→ For the full manifest reference with all fields:
references/manifest-v3.md
json
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "description": "What it does in one sentence",
  "permissions": ["storage", "activeTab", "scripting"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}
→ 包含所有字段的完整manifest参考:
references/manifest-v3.md

Code patterns quick reference

代码模式速查

Async message handler (the safe pattern)

异步消息处理器(安全模式)

typescript
// Wrap async handlers to avoid the return-true trap
function asyncHandler(
  fn: (msg: any, sender: chrome.runtime.MessageSender) => Promise<any>,
) {
  return (
    message: any,
    sender: chrome.runtime.MessageSender,
    sendResponse: (r: any) => void,
  ) => {
    fn(message, sender)
      .then(sendResponse)
      .catch((e) => sendResponse({ __error: true, message: e.message }));
    return true; // literal true, not Promise<true>
  };
}

chrome.runtime.onMessage.addListener(
  asyncHandler(async (msg, sender) => {
    if (msg.type === "FETCH") {
      const res = await fetch(msg.url);
      return { ok: res.ok, data: await res.text() };
    }
  }),
);
typescript
// Wrap async handlers to avoid the return-true trap
function asyncHandler(
  fn: (msg: any, sender: chrome.runtime.MessageSender) => Promise<any>,
) {
  return (
    message: any,
    sender: chrome.runtime.MessageSender,
    sendResponse: (r: any) => void,
  ) => {
    fn(message, sender)
      .then(sendResponse)
      .catch((e) => sendResponse({ __error: true, message: e.message }));
    return true; // literal true, not Promise<true>
  };
}

chrome.runtime.onMessage.addListener(
  asyncHandler(async (msg, sender) => {
    if (msg.type === "FETCH") {
      const res = await fetch(msg.url);
      return { ok: res.ok, data: await res.text() };
    }
  }),
);

CSP bypass relay (content script → service worker → API)

CSP绕过中继(content script → service worker → API)

typescript
// content-script.ts
async function apiCall(endpoint: string, options?: RequestInit) {
  return chrome.runtime.sendMessage({ type: "API_RELAY", endpoint, options });
}

// background.ts
const ALLOWED_ENDPOINTS = ["https://api.example.com"];
chrome.runtime.onMessage.addListener(
  asyncHandler(async (msg) => {
    if (msg.type !== "API_RELAY") return;
    if (!ALLOWED_ENDPOINTS.some((e) => msg.endpoint.startsWith(e))) {
      throw new Error("Blocked endpoint");
    }
    const res = await fetch(msg.endpoint, msg.options);
    return { ok: res.ok, status: res.status, data: await res.text() };
  }),
);
typescript
// content-script.ts
async function apiCall(endpoint: string, options?: RequestInit) {
  return chrome.runtime.sendMessage({ type: "API_RELAY", endpoint, options });
}

// background.ts
const ALLOWED_ENDPOINTS = ["https://api.example.com"];
chrome.runtime.onMessage.addListener(
  asyncHandler(async (msg) => {
    if (msg.type !== "API_RELAY") return;
    if (!ALLOWED_ENDPOINTS.some((e) => msg.endpoint.startsWith(e))) {
      throw new Error("Blocked endpoint");
    }
    const res = await fetch(msg.endpoint, msg.options);
    return { ok: res.ok, status: res.status, data: await res.text() };
  }),
);

Persist state across SW restarts

SW重启后持久化状态

typescript
// Use chrome.storage.session for ephemeral state
chrome.storage.session.setAccessLevel({
  accessLevel: "TRUSTED_AND_UNTRUSTED_CONTEXTS",
});

async function getState<T>(key: string, fallback: T): Promise<T> {
  const result = await chrome.storage.session.get(key);
  return result[key] ?? fallback;
}
async function setState<T>(key: string, value: T): Promise<void> {
  await chrome.storage.session.set({ [key]: value });
}
typescript
// Use chrome.storage.session for ephemeral state
chrome.storage.session.setAccessLevel({
  accessLevel: "TRUSTED_AND_UNTRUSTED_CONTEXTS",
});

async function getState<T>(key: string, fallback: T): Promise<T> {
  const result = await chrome.storage.session.get(key);
  return result[key] ?? fallback;
}
async function setState<T>(key: string, value: T): Promise<void> {
  await chrome.storage.session.set({ [key]: value });
}

Orphaned content script detection

孤立content script检测

typescript
function isExtensionContextValid(): boolean {
  try {
    return !!chrome.runtime?.id;
  } catch {
    return false;
  }
}

// Before any chrome.runtime call
if (!isExtensionContextValid()) {
  showRefreshBanner();
  return;
}
typescript
function isExtensionContextValid(): boolean {
  try {
    return !!chrome.runtime?.id;
  } catch {
    return false;
  }
}

// Before any chrome.runtime call
if (!isExtensionContextValid()) {
  showRefreshBanner();
  return;
}

What NOT to do

禁止操作

  • Do NOT use
    eval()
    ,
    new Function()
    , or load remote scripts. MV3 forbids it.
  • Do NOT use
    setTimeout
    /
    setInterval
    for anything > 5s in service workers.
  • Do NOT register event listeners inside callbacks or async functions.
  • Do NOT use
    <all_urls>
    host permission unless absolutely necessary.
  • Do NOT rely on DevTools keeping the service worker alive during testing.
  • Do NOT forget
    return true
    in async message listeners.
  • Do NOT use
    localStorage
    or
    sessionStorage
    in service workers (they don't exist there).
  • Do NOT assume content scripts survive extension updates.
  • Do NOT use
    webRequest
    blocking (removed in MV3). Use
    declarativeNetRequest
    .
  • Do NOT use
    chrome.extension.getBackgroundPage()
    (removed in MV3).
  • 禁止使用
    eval()
    new Function()
    或加载远程脚本。MV3已禁用这些功能。
  • 禁止在service worker中使用
    setTimeout
    /
    setInterval
    执行超过5秒的任务。
  • 禁止在回调或异步函数内部注册事件监听器。
  • 禁止使用
    <all_urls>
    主机权限,除非绝对必要。
  • 禁止依赖DevTools在测试期间保持service worker存活。
  • 禁止在异步消息监听器中忘记返回
    true
  • 禁止在service worker中使用
    localStorage
    sessionStorage
    (这些存储不存在于SW环境)。
  • 禁止假设content script在扩展更新后仍能存活。
  • 禁止使用
    webRequest
    拦截功能(MV3已移除)。请使用
    declarativeNetRequest
  • 禁止使用
    chrome.extension.getBackgroundPage()
    (MV3已移除)。