chrome-extension

作者: samber

使用 Manifest V3 建置 Chrome 擴充功能的完整指南。每當使用者提及 Chrome 擴充功能、瀏覽器擴充功能、manifest.json、內容腳本、服務工作者(在擴充功能情境中)、彈出視窗、側邊面板、chrome.runtime、chrome.tabs、chrome.storage、chrome.scripting、背景腳本、MV3、Manifest V3 或任何 Chrome 擴充功能 API 時,請使用此技能。當使用者想要將腳本注入網頁、在頁面與背景之間進行通訊、繞過 CSP 時,也請觸發此技能。

npx skills add https://github.com/samber/cc-skills --skill chrome-extension

Chrome Extension Development (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.

Reference files

Read only the reference files relevant to the current task. Each file is self-contained.

FileWhen to read
references/manifest-v3.mdSetting up or modifying manifest.json, configuring icons, versioning
references/service-worker.mdBackground logic, lifecycle, state persistence, alarms, events
references/content-scripts.mdInjecting code into pages, isolated/main world, dynamic injection, SPA handling, orphaning
references/messaging-rpc.mdCommunication between any contexts, typed protocols, RPC layer, async handler patterns
references/ui-surfaces.mdPopup, options page, side panel, context menus, commands, notifications, omnibox, devtools panel
references/storage.mdchrome.storage (local/sync/session), quotas, reactive patterns, framework hooks
references/network-csp.mdHTTP requests from content scripts, CSP bypass relay, declarativeNetRequest, offscreen docs, CORS
references/permissions.mdRequired/optional permissions, host permissions, activeTab, runtime request flow
references/web-accessible-resources.mdExposing extension files to web pages, security implications
references/typescript-build.mdTypeScript setup, project structure, build tools comparison, bundling
references/publishing.mdChrome Web Store submission, review process, rejection reasons, updates, privacy policy
references/execution-contexts.mdCommunication flow diagrams, per-context capabilities/limits, choosing the right messaging method
references/debugging-mistakes.mdDevTools for extensions, testing SW termination, common gotchas, error patterns

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)                         │
└──────────────────────────────────────────────────────────┘

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

Communication methods at a glance

MethodDirectionBest for
chrome.runtime.sendMessageAny ext context → SWOne-shot request/response (90% of cases)
chrome.tabs.sendMessageSW → content script (by tabId)Pushing data to a specific tab
chrome.runtime.connect (Port)BidirectionalStreaming, progress, SW ↔ popup
window.postMessageBetween worlds on same pagePage JS ↔ content script bridge
chrome.storage.onChangedBroadcast 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

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

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

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

  • 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

"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

"I need to modify HTTP headers or block requests"

→ declarativeNetRequest (NOT webRequest, which lost blocking in MV3) → Read references/network-csp.md

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

→ Three-layer bridge: page (window.postMessage) → content script → service worker → Read 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.

"I need periodic background tasks"

→ chrome.alarms (minimum 30s interval). NOT setTimeout. → Read references/service-worker.md

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

→ Offscreen document. One per extension, only chrome.runtime available. → Read references/network-csp.md

"I need to authenticate with OAuth"

→ chrome.identity.launchWebAuthFlow() or chrome.identity.getAuthToken() (Google only) → Read references/service-worker.md (identity section)

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

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)

Minimal manifest.json template

{
  "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

Code patterns quick reference

Async message handler (the safe pattern)

// 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)

// 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

// 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

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).

來自 samber 的更多技能

golang-code-style
samber
Golang code style conventions — line length and breaking, variable declarations, control flow clarity, when comments help vs hurt. Use when writing or reviewing Go code, asking about style or clarity, or establishing project coding standards. Not for naming conventions (→ See `samber/cc-skills-golang@golang-naming` skill), linter configuration (→ See `samber/cc-skills-golang@golang-lint` skill), or doc comments (→ See `samber/cc-skills-golang@golang-documentation` skill).
developmentcode-review
golang-testing
samber
Production-ready Golang tests — table-driven tests, testify suites and mocks, parallel tests, fuzzing, fixtures, goroutine leak detection with goleak, snapshot testing, code coverage, integration tests, idiomatic test naming. Use when writing or reviewing Go tests, choosing a testing approach, setting up Go test CI, or debugging flaky/slow tests. For testify-specific APIs see `samber/cc-skills-golang@golang-stretchr-testify`; for measurement methodology see...
developmenttestingcode-review
golang-design-patterns
samber
符合慣例的 Golang 設計模式 — 函數選項、建構子、錯誤流程與串聯、資源管理與生命週期、優雅關閉、韌性、架構、依賴注入、資料處理、串流等。適用於明確選擇架構模式、實作函數選項、設計建構子 API、設定優雅關閉、應用韌性模式,或詢問哪種慣用 Go 模式適合特定問題時。
developmentdesigncode-review
golang-error-handling
samber
Idiomatic Golang error handling — creation, wrapping with %w, errors.Is/As, errors.Join, custom error types, sentinel errors, panic/recover, the single handling rule, structured logging with slog, HTTP request logging middleware, and samber/oops for production errors. Built to make logs usable at scale with log aggregation 3rd-party tools. Apply when creating, wrapping, inspecting, or logging errors in Go code. For samber/oops specifics → See `samber/cc-skills-golang@golang-samber-oops`...
developmentcode-review
golang-performance
samber
Golang 性能優化模式與方法論 - 若遇到 X 瓶頸,則應用 Y。涵蓋減少分配、CPU 效率、記憶體佈局、GC 調校、池化、快取以及熱路徑優化。適用於當性能分析或基準測試已識別出瓶頸,且需要正確的優化模式來解決時。亦適用於進行性能代碼審查時,提出改進建議或可協助快速識別性能增益的基準測試。不適用於測量方法論(→...
developmentcode-review
golang-security
samber
Golang的安全最佳實踐與漏洞防範。涵蓋注入攻擊(SQL、命令、XSS)、密碼學、檔案系統安全、網路安全、Cookie、機密管理、記憶體安全及日誌記錄。適用於撰寫、審查或稽核Go程式碼的安全性,或處理涉及加密、I/O、機密管理、使用者輸入處理或身分驗證的高風險程式碼。包含安全工具的配置。
securitycode-reviewdevelopment
golang-database
samber
Go 資料庫存取的全面指南 — 參數化查詢、結構掃描、可空欄位、交易、隔離層級、SELECT FOR UPDATE、連線池、批次處理、上下文傳遞與遷移工具。適用於撰寫、審查或除錯與 PostgreSQL、MariaDB、MySQL 或 SQLite 互動的 Golang 程式碼;資料庫測試;或關於 database/sql、sqlx 或 pgx 的問題。不產生資料庫結構或遷移 SQL。
developmentdatabase
golang-lint
samber
針對 Golang 專案的 lint 最佳實務與 golangci-lint 配置 — 執行 linter、設定 .golangci.yml、使用 nolint 指令抑制警告、解讀 lint 輸出,以及選擇 linter。適用於配置 golangci-lint、詢問 lint 警告或 nolint 抑制方式、設定程式碼品質工具,或挑選 linter 時。亦適用於使用者提及 golangci-lint、go vet、staticcheck 或 revive 時。
developmentcode-reviewtesting