Chrome 扩展开发指南:从入门到精通 Manifest V3

本文基于 Manifest V3 标准,系统性地介绍 Chrome 浏览器扩展开发的核心知识,涵盖项目架构、核心组件、消息通信、存储方案、网络请求、安全实践、现代工具链集成等内容,并提供大量可运行的代码示例。

前言

Chrome 扩展是一种能够扩展浏览器功能的小型程序。它可以修改网页内容、添加新功能、与 Web 服务交互,甚至构建完整的应用程序。随着 Manifest V3 的全面推行,扩展开发迎来了重大变革:Background Pages 被 Service Workers 取代,网络请求拦截改用 declarativeNetRequest,安全策略更加严格。

特性 Manifest V2 Manifest V3
后台脚本 Background Pages Service Workers
远程代码 允许 禁止
eval() 允许 禁止
网络请求拦截 webRequest (blocking) declarativeNetRequest
内容安全策略 较宽松 更严格
Host Permissions 在 permissions 中 单独的 host_permissions
Promise 支持 部分支持 全面支持

本文将带你全面掌握现代 Chrome 扩展开发。

一、快速开始

1.1 最小可运行扩展

一个 Chrome 扩展至少需要一个 manifest.json 文件:

json 复制代码
{
  "manifest_version": 3,
  "name": "我的第一个扩展",
  "version": "1.0.0",
  "description": "一个简单的 Chrome 扩展示例",
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  }
}

创建 popup.html

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    body { width: 200px; padding: 16px; font-family: system-ui; }
    h1 { font-size: 16px; margin: 0; }
  </style>
</head>
<body>
  <h1>👋 Hello Extension!</h1>
</body>
</html>

1.2 加载扩展

  1. 打开 chrome://extensions/
  2. 启用右上角的"开发者模式"
  3. 点击"加载已解压的扩展程序"
  4. 选择项目目录

二、项目结构

2.1 标准项目结构

bash 复制代码
my-extension/
├── manifest.json          # 扩展清单(必需)
├── background.js          # Service Worker(后台脚本)
├── content-script.js      # 内容脚本(注入网页)
├── popup.html/js/css      # 弹出页面
├── sidebar.html/js/css    # 侧边栏(Side Panel)
├── options.html/js/css    # 设置页面
├── icons/                 # 图标
│   ├── icon16.png
│   ├── icon48.png
│   └── icon128.png
├── _locales/              # 国际化
│   ├── en/messages.json
│   └── zh_CN/messages.json
└── lib/                   # 第三方库

2.2 架构概览图

scss 复制代码
┌───────────────────────────────────────────────────────────────────────────┐
│                            浏览器扩展架构图                                  │
├───────────────────────────────────────────────────────────────────────────┤
│                                                                           │
│  ┌─────────────────┐    消息通信      ┌─────────────────┐                  │
│  │   Web Page      │◄──────────────► │  Content Script │                  │
│  │   (网页)        │   postMessage   │    (内容脚本)     │                  │
│  └─────────────────┘                 └────────┬────────┘                  │
│                                               │                           │
│                                    chrome.runtime.sendMessage             │
│                                               │                           │
│                                               ▼                           │
│  ┌─────────────────────────────────────────────────────────────────┐      │
│  │                    Background Service Worker                    │      │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐         │      │
│  │  │  消息路由 │   │ API 调用 │  │  状态管理 │  │  定时任务 │          │      │
│  │  └──────────┘  └──────────┘  └──────────┘  └──────────┘         │      │
│  └─────────────────────────────────────────────────────────────────┘      │
│           │                    │                    │                     │
│           ▼                    ▼                    ▼                     │
│  ┌─────────────┐      ┌─────────────┐      ┌─────────────┐                │
│  │   Popup     │      │  Side Panel │      │   Options   │                │
│  │   (弹窗)     │      │  (侧边栏)    │      │   (设置页)  │                 │
│  └─────────────┘      └─────────────┘      └─────────────┘                │
│                                                                           │
│  ┌─────────────────────────────────────────────────────────────────┐      │
│  │                        存储层 (Storage Layer)                    │      │
│  │  ┌────────────┐  ┌────────────┐  ┌────────────┐  ┌────────────┐ │      │
│  │  │   Local    │  │    Sync    │  │  Session   │  │  IndexedDB │ │      │
│  │  │  Storage   │  │  Storage   │  │  Storage   │  │            │ │      │
│  │  └────────────┘  └────────────┘  └────────────┘  └────────────┘ │      │
│  └─────────────────────────────────────────────────────────────────┘      │
└───────────────────────────────────────────────────────────────────────────┘

2.3 各文件职责

文件 职责 运行环境
manifest.json 扩展配置和元数据 -
background.js 后台任务、事件监听、API调用 Service Worker
content-script.js 与网页交互、DOM操作 网页上下文
sidebar.js/popup.js 用户界面逻辑 扩展页面上下文

三、Manifest 配置详解

3.1 完整配置示例

json 复制代码
{
  "manifest_version": 3,
  "name": "__MSG_extName__",
  "version": "1.0.0",
  "description": "__MSG_extDescription__",
  "default_locale": "zh_CN",

  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },

  "permissions": [
    "storage",
    "tabs",
    "activeTab",
    "scripting",
    "sidePanel",
    "contextMenus",
    "alarms",
    "notifications"
  ],

  "optional_permissions": ["history", "bookmarks"],

  "host_permissions": [
    "https://*.example.com/*",
    "https://api.openai.com/*"
  ],

  "background": {
    "service_worker": "background.js",
    "type": "module"
  },

  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content-script.js"],
      "css": ["content-style.css"],
      "run_at": "document_idle"
    }
  ],

  "side_panel": {
    "default_path": "sidebar.html"
  },

  "action": {
    "default_popup": "popup.html",
    "default_icon": { "16": "icons/icon16.png", "48": "icons/icon48.png" },
    "default_title": "点击打开"
  },

  "options_ui": {
    "page": "options.html",
    "open_in_tab": false
  },

  "commands": {
    "toggle-sidebar": {
      "suggested_key": { "default": "Ctrl+Shift+S", "mac": "Command+Shift+S" },
      "description": "切换侧边栏"
    }
  },

  "web_accessible_resources": [
    { "resources": ["images/*"], "matches": ["<all_urls>"] }
  ],

  "externally_connectable": {
    "matches": ["http://localhost:*/*", "https://*.yourdomain.com/*"]
  }
}

3.2 关键字段说明

permissions vs host_permissions

json 复制代码
// API 权限 - 访问 Chrome API
"permissions": [
  "tabs",          // 访问标签页信息
  "storage",       // 本地存储
  "sidePanel",     // 侧边栏功能
  "activeTab",     // 当前活动标签页
  "scripting",     // 动态注入脚本
  "notifications", // 桌面通知
  "contextMenus",  // 右键菜单
  "alarms"         // 定时器
],

// 主机权限 - 访问指定网站
"host_permissions": [
  "<all_urls>",                  // 所有网站
  "https://*.google.com/*",      // 特定域名
  "http://localhost:*/*"         // 本地开发
]

run_at 取值说明

  • document_start: DOM 开始构建时注入
  • document_end: DOM 构建完成时注入(DOMContentLoaded 之前)
  • document_idle: DOMContentLoaded 之后注入(默认,推荐)

3.3 常用权限速查表

权限 用途
storage 本地存储
tabs 标签页管理
activeTab 临时访问当前标签页
scripting 动态注入脚本
sidePanel 侧边栏功能
contextMenus 右键菜单
alarms 定时器
notifications 系统通知
cookies Cookie 管理
history 浏览历史
bookmarks 书签管理
downloads 下载管理
offscreen 离屏文档
declarativeNetRequest 网络请求拦截

四、核心组件详解

4.1 Background Service Worker

Service Worker 是扩展的"大脑",负责事件监听、状态管理和跨组件通信。

javascript 复制代码
// background.js

// ============ 生命周期事件 ============
chrome.runtime.onInstalled.addListener(async (details) => {
  console.log('扩展已安装/更新:', details.reason)

  if (details.reason === 'install') {
    // 首次安装:初始化存储
    await chrome.storage.local.set({
      settings: { theme: 'light', enabled: true }
    })

    // 创建右键菜单
    chrome.contextMenus.create({
      id: 'main-menu',
      title: '使用扩展处理',
      contexts: ['selection', 'page']
    })
  }
})

// ============ 消息处理中心 ============
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  handleMessage(message, sender)
    .then(sendResponse)
    .catch(err => sendResponse({ error: err.message }))
  return true // 异步响应必须返回 true
})

async function handleMessage(message, sender) {
  switch (message.action) {
    case 'getData':
      return chrome.storage.local.get(message.key)
    case 'setData':
      await chrome.storage.local.set({ [message.key]: message.value })
      return { success: true }
    case 'openSidebar':
      await chrome.sidePanel.open({ tabId: sender.tab.id })
      return { success: true }
    default:
      throw new Error(`未知操作: ${message.action}`)
  }
}

// ============ 定时任务 ============
chrome.alarms.create('keepAlive', { periodInMinutes: 0.5 })
chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name === 'keepAlive') {
    console.log('Service Worker 保持活跃')
  }
})

// ============ 快捷键 ============
chrome.commands.onCommand.addListener(async (command) => {
  if (command === 'toggle-sidebar') {
    const [tab] = await chrome.tabs.query({ active: true, currentWindow: true })
    if (tab) await chrome.sidePanel.open({ tabId: tab.id })
  }
})

// ============ 侧边栏配置 ============
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true })

console.log('Background Service Worker 已启动')

4.2 Content Script

Content Script 注入到网页中运行,可以访问和操作 DOM。

javascript 复制代码
// content-script.js
;(function() {
  'use strict'

  // 防止重复注入
  if (window.__EXTENSION_LOADED__) return
  window.__EXTENSION_LOADED__ = true

  console.log('[扩展] Content Script 已注入:', location.href)

  // 发送消息到 Background
  async function sendMessage(action, data = {}) {
    return new Promise((resolve, reject) => {
      chrome.runtime.sendMessage({ action, ...data }, (response) => {
        if (chrome.runtime.lastError) {
          reject(new Error(chrome.runtime.lastError.message))
        } else if (response?.error) {
          reject(new Error(response.error))
        } else {
          resolve(response)
        }
      })
    })
  }

  // 接收来自 Background 的消息
  chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    switch (message.action) {
      case 'getPageInfo':
        sendResponse({
          url: location.href,
          title: document.title,
          content: document.body.innerText.slice(0, 5000)
        })
        break
      case 'highlight':
        highlightText(message.text)
        sendResponse({ success: true })
        break
    }
    return false
  })

  // 高亮文本
  function highlightText(text) {
    const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT)
    while (walker.nextNode()) {
      const node = walker.currentNode
      if (node.textContent.includes(text)) {
        const mark = document.createElement('mark')
        mark.style.cssText = 'background: yellow; padding: 2px 4px;'
        mark.textContent = node.textContent
        node.parentNode.replaceChild(mark, node)
      }
    }
  }

  // 与网页通信(可选)
  window.addEventListener('message', async (event) => {
    if (event.source !== window || event.data?.type !== 'FROM_PAGE') return
    try {
      const response = await sendMessage(event.data.action, event.data.payload)
      window.postMessage({ type: 'FROM_EXTENSION', response }, '*')
    } catch (error) {
      window.postMessage({ type: 'FROM_EXTENSION', error: error.message }, '*')
    }
  })
})()

4.3 Side Panel(侧边栏)

html 复制代码
<!-- sidebar.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: system-ui; background: #f5f5f5; }
    .container { height: 100vh; display: flex; flex-direction: column; }
    .header { padding: 16px; background: #4285f4; color: white; }
    .content { flex: 1; padding: 16px; overflow-y: auto; }
    .message { padding: 12px; margin: 8px 0; background: white; border-radius: 8px; }
    .message.user { background: #e3f2fd; margin-left: 20%; }
    .input-area { padding: 16px; border-top: 1px solid #ddd; background: white; }
    textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 8px; resize: none; }
    button { margin-top: 8px; width: 100%; padding: 10px; background: #4285f4; color: white; border: none; border-radius: 8px; cursor: pointer; }
    button:hover { background: #3367d6; }
    button:disabled { background: #ccc; }
  </style>
</head>
<body>
  <div class="container">
    <header class="header"><h1>🚀 我的扩展</h1></header>
    <main class="content" id="messages"></main>
    <div class="input-area">
      <textarea id="input" rows="3" placeholder="输入内容... (Ctrl+Enter 发送)"></textarea>
      <button id="send">发送</button>
    </div>
  </div>
  <script src="sidebar.js"></script>
</body>
</html>
javascript 复制代码
// sidebar.js
document.addEventListener('DOMContentLoaded', () => {
  const input = document.getElementById('input')
  const sendBtn = document.getElementById('send')
  const messagesContainer = document.getElementById('messages')

  function appendMessage(content, type = 'user') {
    const div = document.createElement('div')
    div.className = `message ${type}`
    div.textContent = content
    messagesContainer.appendChild(div)
    messagesContainer.scrollTop = messagesContainer.scrollHeight
  }

  async function handleSend() {
    const text = input.value.trim()
    if (!text) return

    appendMessage(text, 'user')
    input.value = ''
    sendBtn.disabled = true

    try {
      const response = await chrome.runtime.sendMessage({ action: 'process', text })
      appendMessage(response.result || '处理完成', 'assistant')
    } catch (error) {
      appendMessage('错误: ' + error.message, 'error')
    } finally {
      sendBtn.disabled = false
    }
  }

  sendBtn.addEventListener('click', handleSend)
  input.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) handleSend()
  })
})

五、消息通信机制

5.1 通信架构图

css 复制代码
┌──────────┐    postMessage    ┌──────────────┐   chrome.runtime   ┌──────────────┐
│ Web Page │ ◄──────────────►  │Content Script│ ◄────────────────► │  Background  │
└──────────┘                   └──────────────┘                    └──────────────┘
                                      ▲                                  ▲
                                      │     chrome.runtime.sendMessage   │
                                      ▼                                  │
                               ┌──────────────┐                          │
                               │ Popup/Sidebar│ ◄────────────────────────┘
                               └──────────────┘     chrome.tabs.sendMessage

5.2 消息发送模式

javascript 复制代码
// 1. Content Script / Popup / Sidebar → Background
chrome.runtime.sendMessage({ action: 'getData', key: 'settings' }, (response) => {
  if (chrome.runtime.lastError) {
    console.error('发送失败:', chrome.runtime.lastError.message)
    return
  }
  console.log('响应:', response)
})

// 2. Background → Content Script(需要指定 tabId)
async function sendToActiveTab(message) {
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true })
  return chrome.tabs.sendMessage(tab.id, message)
}

// 3. 网页 ↔ Content Script(使用 postMessage)
// 网页端
window.postMessage({ type: 'FROM_PAGE', action: 'getData' }, '*')

// Content Script 端
window.addEventListener('message', (event) => {
  if (event.source !== window || event.data.type !== 'FROM_PAGE') return
  window.postMessage({ type: 'FROM_EXTENSION', data: {} }, '*')
})

5.3 长连接(Port)

javascript 复制代码
// 建立连接
const port = chrome.runtime.connect({ name: 'sidebar' })

port.onMessage.addListener((message) => {
  console.log('收到:', message)
})

port.postMessage({ type: 'subscribe', channel: 'updates' })

// Background 端监听
const connections = new Map()

chrome.runtime.onConnect.addListener((port) => {
  connections.set(port.name, port)

  port.onMessage.addListener((message) => {
    port.postMessage({ type: 'ack', id: message.id })
  })

  port.onDisconnect.addListener(() => {
    connections.delete(port.name)
  })
})

六、数据存储

6.1 chrome.storage API

javascript 复制代码
// 本地存储(无大小限制,不同步)
await chrome.storage.local.set({ key: 'value', settings: { theme: 'dark' } })
const { key, settings } = await chrome.storage.local.get(['key', 'settings'])

// 同步存储(跟随用户账号,限制 100KB)
await chrome.storage.sync.set({ preferences: { fontSize: 14 } })

// 会话存储(扩展关闭后清除)
await chrome.storage.session.set({ tempData: 'xxx' })

// 监听存储变化
chrome.storage.onChanged.addListener((changes, areaName) => {
  for (const [key, { oldValue, newValue }] of Object.entries(changes)) {
    console.log(`[${areaName}] ${key}: ${oldValue} → ${newValue}`)
  }
})

6.2 封装存储工具类

javascript 复制代码
class Storage {
  constructor(area = 'local') {
    this.storage = chrome.storage[area]
    this.cache = new Map()
  }

  async get(key, defaultValue = null) {
    if (this.cache.has(key)) return this.cache.get(key)
    const result = await this.storage.get(key)
    const value = result[key] ?? defaultValue
    this.cache.set(key, value)
    return value
  }

  async set(key, value) {
    await this.storage.set({ [key]: value })
    this.cache.set(key, value)
  }

  async remove(key) {
    await this.storage.remove(key)
    this.cache.delete(key)
  }
}

const storage = new Storage('local')
await storage.set('user', { name: 'John' })
const user = await storage.get('user')

七、网络请求

7.1 HTTP 请求封装

javascript 复制代码
class HttpClient {
  constructor(baseURL = '', defaultHeaders = {}) {
    this.baseURL = baseURL
    this.defaultHeaders = { 'Content-Type': 'application/json', ...defaultHeaders }
  }

  async request(endpoint, options = {}) {
    const response = await fetch(this.baseURL + endpoint, {
      method: options.method || 'GET',
      headers: { ...this.defaultHeaders, ...options.headers },
      body: options.body ? JSON.stringify(options.body) : undefined
    })

    const data = await response.json()
    if (!response.ok) throw new Error(data.message || `HTTP ${response.status}`)
    return data
  }

  get(endpoint) { return this.request(endpoint) }
  post(endpoint, body) { return this.request(endpoint, { method: 'POST', body }) }
}

const api = new HttpClient('https://api.example.com')
const data = await api.get('/users')

7.2 流式 AI 对话

javascript 复制代码
class AIStreamClient {
  constructor(apiKey, provider = 'openai') {
    this.apiKey = apiKey
    this.provider = provider
  }

  async chat(messages, onChunk, onComplete) {
    const isAnthropic = this.provider === 'anthropic'
    const url = isAnthropic
      ? 'https://api.anthropic.com/v1/messages'
      : 'https://api.openai.com/v1/chat/completions'

    const headers = {
      'Content-Type': 'application/json',
      ...(isAnthropic
        ? { 'x-api-key': this.apiKey, 'anthropic-version': '2023-06-01' }
        : { 'Authorization': `Bearer ${this.apiKey}` })
    }

    const response = await fetch(url, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        model: isAnthropic ? 'claude-3-sonnet-20240229' : 'gpt-4',
        messages,
        stream: true,
        max_tokens: 4096
      })
    })

    const reader = response.body.getReader()
    const decoder = new TextDecoder()
    let fullContent = ''

    while (true) {
      const { done, value } = await reader.read()
      if (done) break

      const lines = decoder.decode(value).split('\n')
      for (const line of lines) {
        if (!line.startsWith('data: ') || line.includes('[DONE]')) continue
        try {
          const json = JSON.parse(line.slice(6))
          const content = isAnthropic
            ? json.delta?.text
            : json.choices?.[0]?.delta?.content
          if (content) {
            fullContent += content
            onChunk?.(content, fullContent)
          }
        } catch {}
      }
    }

    onComplete?.(fullContent)
    return fullContent
  }
}

八、安全最佳实践

8.1 输入验证与净化

javascript 复制代码
class SecurityUtils {
  // HTML 转义 - 防止 XSS
  static escapeHtml(str) {
    const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#x27;' }
    return String(str).replace(/[&<>"']/g, c => map[c])
  }

  // 验证 URL
  static isValidUrl(string) {
    try {
      const url = new URL(string)
      return ['http:', 'https:'].includes(url.protocol)
    } catch { return false }
  }

  // 安全 JSON 解析
  static safeJsonParse(str, defaultValue = null) {
    try { return JSON.parse(str) } catch { return defaultValue }
  }
}

8.2 加密存储

javascript 复制代码
class SecureStorage {
  constructor() {
    this.encryptionKey = null
  }

  async init() {
    if (this.encryptionKey) return
    const result = await chrome.storage.local.get('encryption

Key')
    if (result.encryptionKey) {
      const keyData = new Uint8Array(result.encryptionKey)
      this.encryptionKey = await crypto.subtle.importKey(
        'raw', keyData, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']
      )
    } else {
      this.encryptionKey = await crypto.subtle.generateKey(
        { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']
      )
      const exported = await crypto.subtle.exportKey('raw', this.encryptionKey)
      await chrome.storage.local.set({ encryptionKey: Array.from(new Uint8Array(exported)) })
    }
  }

  async encrypt(data) {
    const iv = crypto.getRandomValues(new Uint8Array(12))
    const encoded = new TextEncoder().encode(JSON.stringify(data))
    const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, this.encryptionKey, encoded)
    return { iv: Array.from(iv), data: Array.from(new Uint8Array(encrypted)) }
  }

  async decrypt(encryptedData) {
    const iv = new Uint8Array(encryptedData.iv)
    const data = new Uint8Array(encryptedData.data)
    const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, this.encryptionKey, data)
    return JSON.parse(new TextDecoder().decode(decrypted))
  }

  async setSecure(key, value) {
    await this.init()
    const encrypted = await this.encrypt(value)
    await chrome.storage.local.set({ [key]: encrypted })
  }

  async getSecure(key) {
    await this.init()
    const result = await chrome.storage.local.get(key)
    if (!result[key]) return null
    return this.decrypt(result[key])
  }
}

// 使用示例
const secureStorage = new SecureStorage()
await secureStorage.setSecure('apiKey', 'sk-secret-key')
const apiKey = await secureStorage.getSecure('apiKey')

8.3 权限最小化原则

json 复制代码
{
  "permissions": [
    "storage",    // 必需:数据存储
    "activeTab"   // 必需:获取当前页面信息
  ],
  "optional_permissions": [
    "tabs",       // 可选:需要时再请求
    "history"     // 可选:需要时再请求
  ],
  "host_permissions": [
    "https://api.example.com/*"  // 只允许访问必要的 API
  ]
}

九、高级 Chrome API

9.1 declarativeNetRequest API

声明式网络请求拦截,替代 Manifest V2 的 webRequest:

json 复制代码
// manifest.json
{
  "permissions": ["declarativeNetRequest"],
  "declarative_net_request": {
    "rule_resources": [{ "id": "ruleset_1", "enabled": true, "path": "rules.json" }]
  }
}
json 复制代码
// rules.json
[
  {
    "id": 1,
    "priority": 1,
    "action": { "type": "block" },
    "condition": {
      "urlFilter": "*://ads.example.com/*",
      "resourceTypes": ["script", "image"]
    }
  },
  {
    "id": 2,
    "priority": 2,
    "action": {
      "type": "modifyHeaders",
      "requestHeaders": [{ "header": "X-Custom", "operation": "set", "value": "value" }]
    },
    "condition": { "urlFilter": "*://api.example.com/*", "resourceTypes": ["xmlhttprequest"] }
  }
]
javascript 复制代码
// 动态添加规则
async function addBlockRule(domain) {
  const rules = await chrome.declarativeNetRequest.getDynamicRules()
  const nextId = Math.max(0, ...rules.map(r => r.id)) + 1

  await chrome.declarativeNetRequest.updateDynamicRules({
    addRules: [{
      id: nextId,
      priority: 1,
      action: { type: 'block' },
      condition: { urlFilter: `*://${domain}/*`, resourceTypes: ['script'] }
    }]
  })
}

9.2 Offscreen Documents

在 Manifest V3 中创建隐藏的 DOM 环境:

json 复制代码
// manifest.json
{ "permissions": ["offscreen"] }
javascript 复制代码
// background.js
async function createOffscreenDocument() {
  const contexts = await chrome.runtime.getContexts({ contextTypes: ['OFFSCREEN_DOCUMENT'] })
  if (contexts.length > 0) return

  await chrome.offscreen.createDocument({
    url: 'offscreen.html',
    reasons: ['DOM_PARSER', 'CLIPBOARD'],
    justification: '需要解析 HTML 和操作剪贴板'
  })
}

// offscreen.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.target !== 'offscreen') return

  if (message.action === 'parseHTML') {
    const parser = new DOMParser()
    const doc = parser.parseFromString(message.html, 'text/html')
    sendResponse({ title: doc.title, headings: [...doc.querySelectorAll('h1,h2')].map(h => h.textContent) })
  }

  if (message.action === 'copyToClipboard') {
    navigator.clipboard.writeText(message.text).then(() => sendResponse({ success: true }))
    return true
  }
})

十、国际化支持

10.1 配置国际化

json 复制代码
// manifest.json
{ "default_locale": "zh_CN" }
json 复制代码
// _locales/zh_CN/messages.json
{
  "extName": { "message": "我的扩展", "description": "扩展名称" },
  "extDescription": { "message": "一个强大的浏览器扩展", "description": "扩展描述" },
  "buttonSend": { "message": "发送", "description": "发送按钮文本" },
  "greeting": { "message": "你好,$USER$!", "placeholders": { "user": { "content": "$1", "example": "张三" } } }
}
json 复制代码
// _locales/en/messages.json
{
  "extName": { "message": "My Extension" },
  "extDescription": { "message": "A powerful browser extension" },
  "buttonSend": { "message": "Send" },
  "greeting": { "message": "Hello, $USER$!", "placeholders": { "user": { "content": "$1" } } }
}

10.2 使用国际化

javascript 复制代码
// 获取翻译
const name = chrome.i18n.getMessage('extName')
const greeting = chrome.i18n.getMessage('greeting', ['张三'])

// 获取语言
const uiLanguage = chrome.i18n.getUILanguage() // "zh-CN"

// HTML 中使用(需要手动替换)
document.querySelectorAll('[data-i18n]').forEach(el => {
  el.textContent = chrome.i18n.getMessage(el.dataset.i18n)
})
html 复制代码
<!-- HTML 使用 -->
<button data-i18n="buttonSend">Send</button>

<!-- CSS 使用(manifest.json 中的字段) -->
<!-- "__MSG_extName__" 会被自动替换 -->

十一、现代开发工具链

11.1 Vite + CRXJS

bash 复制代码
npm create vite@latest my-extension -- --template react-ts
cd my-extension
npm install @crxjs/vite-plugin -D
typescript 复制代码
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { crx } from '@crxjs/vite-plugin'
import manifest from './manifest.json'

export default defineConfig({
  plugins: [react(), crx({ manifest })]
})

11.2 TypeScript 配置

json 复制代码
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "types": ["chrome"]
  }
}
bash 复制代码
npm install @types/chrome -D

十二、UI 框架集成

12.1 React 集成

tsx 复制代码
// src/popup/App.tsx
import { useState, useEffect } from 'react'

export default function App() {
  const [settings, setSettings] = useState({ theme: 'light', enabled: true })

  useEffect(() => {
    chrome.storage.sync.get(['settings'], (result) => {
      if (result.settings) setSettings(result.settings)
    })
  }, [])

  const updateSettings = async (updates: Partial<typeof settings>) => {
    const newSettings = { ...settings, ...updates }
    setSettings(newSettings)
    await chrome.storage.sync.set({ settings: newSettings })
  }

  return (
    <div className={`app ${settings.theme}`}>
      <h1>扩展设置</h1>
      <label>
        <input
          type="checkbox"
          checked={settings.enabled}
          onChange={(e) => updateSettings({ enabled: e.target.checked })}
        />
        启用扩展
      </label>
    </div>
  )
}

12.2 Vue 3 集成

vue 复制代码
<!-- src/popup/App.vue -->
<template>
  <div :class="['app', settings.theme]">
    <h1>扩展设置</h1>
    <label>
      <input type="checkbox" v-model="settings.enabled" @change="saveSettings" />
      启用扩展
    </label>
  </div>
</template>

<script setup lang="ts">
import { reactive, onMounted } from 'vue'

const settings = reactive({ enabled: true, theme: 'light' })

onMounted(async () => {
  const result = await chrome.storage.sync.get(['settings'])
  if (result.settings) Object.assign(settings, result.settings)
})

async function saveSettings() {
  await chrome.storage.sync.set({ settings: { ...settings } })
}
</script>

12.3 Vue Composables

typescript 复制代码
// src/composables/useStorage.ts
import { ref, watch, onMounted } from 'vue'

export function useStorage<T>(key: string, defaultValue: T) {
  const data = ref<T>(defaultValue)
  const loading = ref(true)

  onMounted(async () => {
    const result = await chrome.storage.local.get(key)
    if (result[key] !== undefined) data.value = result[key]
    loading.value = false
  })

  watch(data, async (newValue) => {
    await chrome.storage.local.set({ [key]: newValue })
  }, { deep: true })

  chrome.storage.onChanged.addListener((changes, area) => {
    if (area === 'local' && changes[key]) data.value = changes[key].newValue
  })

  return { data, loading }
}

// 使用
// const { data: settings } = useStorage('settings', { theme: 'light' })

十三、调试技巧

13.1 调试各组件

组件 调试方法
Background chrome://extensions/ → 点击 "Service Worker" 链接
Content Script 目标网页 → F12 → Console/Sources
Popup 右键扩展图标 → 检查弹出内容
Side Panel 侧边栏内 → 右键 → 检查

13.2 常用调试代码

javascript 复制代码
// 查看扩展信息
console.log(chrome.runtime.getManifest())

// 查看所有存储数据
chrome.storage.local.get(null, console.log)
chrome.storage.sync.get(null, console.log)

// 查看当前标签页
chrome.tabs.query({ active: true, currentWindow: true }, console.log)

// 检查权限
chrome.permissions.getAll(console.log)

13.3 Service Worker 问题排查

javascript 复制代码
// 保持 Service Worker 活跃
chrome.alarms.create('keepAlive', { periodInMinutes: 0.5 })

chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name === 'keepAlive') console.log('Service Worker 保持活跃')
})

// 不要使用全局变量存储状态!
// ❌ 错误
let cachedData = {}

// ✅ 正确:使用 chrome.storage
async function getData(key) {
  const result = await chrome.storage.local.get(key)
  return result[key]
}

十四、发布与更新

14.1 准备工作

  1. 注册开发者账号(一次性费用 $5)
  2. 准备素材:
    • 128x128 图标
    • 1280x800 或 640x400 截图(1-5 张)
    • 440x280 宣传图(可选)
    • 详细描述和隐私政策

14.2 打包扩展

bash 复制代码
cd dist
zip -r ../extension.zip . -x "*.git*" -x "node_modules/*"

14.3 发布流程

  1. 访问 Chrome Web Store Developer Dashboard
  2. 点击"新建商品"
  3. 上传 ZIP 文件
  4. 填写商品详情
  5. 提交审核(通常 1-3 天)

十五、监控与分析

15.1 错误追踪

javascript 复制代码
/**
 * 错误追踪系统
 */
class ErrorTracker {
  constructor(options = {}) {
    this.endpoint = options.endpoint
    this.maxErrors = options.maxErrors || 100
    this.errors = []
    this.setupGlobalHandlers()
  }

  setupGlobalHandlers() {
    // 捕获未处理的错误
    self.addEventListener('error', (event) => {
      this.capture({
        type: 'uncaught_error',
        message: event.message,
        filename: event.filename,
        lineno: event.lineno,
        stack: event.error?.stack
      })
    })

    // 捕获未处理的 Promise 拒绝
    self.addEventListener('unhandledrejection', (event) => {
      this.capture({
        type: 'unhandled_rejection',
        reason: event.reason?.message || String(event.reason),
        stack: event.reason?.stack
      })
    })
  }

  capture(errorInfo) {
    const error = {
      ...errorInfo,
      timestamp: Date.now(),
      url: location.href,
      userAgent: navigator.userAgent,
      extensionVersion: chrome.runtime.getManifest().version
    }

    this.errors.push(error)
    if (this.errors.length > this.maxErrors) this.errors.shift()

    this.saveToStorage()
    if (this.endpoint) this.sendToServer(error)
    console.error('[ErrorTracker]', error)
  }

  async saveToStorage() {
    await chrome.storage.local.set({ errorLogs: this.errors })
  }

  async sendToServer(error) {
    try {
      await fetch(this.endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(error)
      })
    } catch (e) {
      console.warn('发送错误日志失败:', e)
    }
  }

  getErrors() { return this.errors }
  clearErrors() { this.errors = []; this.saveToStorage() }
}

// 初始化
const errorTracker = new ErrorTracker({
  endpoint: 'https://api.example.com/errors',
  maxErrors: 50
})

15.2 用户行为分析

javascript 复制代码
/**
 * 用户行为分析
 */
class Analytics {
  constructor(options = {}) {
    this.enabled = options.enabled ?? true
    this.events = []
    this.sessionId = `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
    this.startTime = Date.now()
  }

  track(eventName, properties = {}) {
    if (!this.enabled) return

    const event = {
      name: eventName,
      properties,
      timestamp: Date.now(),
      sessionId: this.sessionId,
      sessionDuration: Date.now() - this.startTime
    }

    this.events.push(event)
    this.saveToStorage()
    console.log('[Analytics] 事件:', eventName, properties)
  }

  // 常用事件追踪
  trackPageView(page) { this.track('page_view', { page }) }
  trackButtonClick(buttonId, buttonText) { this.track('button_click', { buttonId, buttonText }) }
  trackFeatureUse(feature) { this.track('feature_use', { feature }) }
  trackAPICall(endpoint, duration, success) { this.track('api_call', { endpoint, duration, success }) }

  async saveToStorage() {
    const recentEvents = this.events.slice(-1000)
    await chrome.storage.local.set({ analyticsEvents: recentEvents })
  }

  async getStats() {
    const events = this.events
    return {
      totalEvents: events.length,
      eventsByType: events.reduce((acc, e) => ({ ...acc, [e.name]: (acc[e.name] || 0) + 1 }), {}),
      sessionsCount: new Set(events.map(e => e.sessionId)).size
    }
  }
}

// 使用示例
const analytics = new Analytics({ enabled: true })
analytics.trackPageView('sidebar')
analytics.trackFeatureUse('ai-chat')

15.3 性能监控

javascript 复制代码
/**
 * 性能监控
 */
class PerformanceMonitor {
  constructor() {
    this.metrics = []
  }

  async measure(name, fn) {
    const start = performance.now()
    try {
      const result = await fn()
      this.record(name, performance.now() - start, true)
      return result
    } catch (error) {
      this.record(name, performance.now() - start, false, error.message)
      throw error
    }
  }

  record(name, duration, success, error = null) {
    this.metrics.push({ name, duration, success, error, timestamp: Date.now() })
    if (this.metrics.length > 500) this.metrics.shift()
  }

  getAverageTime(name) {
    const relevant = this.metrics.filter(m => m.name === name)
    if (!relevant.length) return 0
    return relevant.reduce((sum, m) => sum + m.duration, 0) / relevant.length
  }

  getSuccessRate(name) {
    const relevant = this.metrics.filter(m => m.name === name)
    if (!relevant.length) return 0
    return (relevant.filter(m => m.success).length / relevant.length) * 100
  }

  getReport() {
    const names = [...new Set(this.metrics.map(m => m.name))]
    return names.map(name => ({
      name,
      count: this.metrics.filter(m => m.name === name).length,
      averageTime: this.getAverageTime(name).toFixed(2) + 'ms',
      successRate: this.getSuccessRate(name).toFixed(1) + '%'
    }))
  }
}

// 使用示例
const perfMonitor = new PerformanceMonitor()

// 测量 API 调用
const response = await perfMonitor.measure('api_chat', async () => {
  return await fetch('/api/chat', { method: 'POST', body: JSON.stringify(data) })
})

// 获取性能报告
console.table(perfMonitor.getReport())

十六、最佳实践总结

16.1 安全建议

  • 不要使用 eval() 或动态执行远程代码
  • 验证所有输入,防止 XSS 攻击
  • 最小权限原则,只申请必要的权限
  • 加密敏感数据 存储前进行加密

16.2 性能优化

  • Service Worker 休眠处理 :使用 chrome.alarms 保持活跃
  • 批量存储操作:合并多次写入
  • 懒加载:按需加载模块
  • 使用 IndexedDB:存储大量数据

16.3 用户体验

  • 提供设置页面:让用户自定义行为
  • 国际化:支持多语言
  • 优雅降级:处理权限被拒绝的情况
  • 清晰的错误提示:帮助用户理解问题

十七、常见问题与解决方案

Q1: Service Worker 频繁休眠怎么办?

javascript 复制代码
chrome.alarms.create('keepAlive', { periodInMinutes: 0.5 })
chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name === 'keepAlive') console.log('保持活跃')
})

Q2: 消息发送后没有响应?

javascript 复制代码
// 确保返回 true 以支持异步响应
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  handleAsync(message).then(sendResponse)
  return true // ← 这很重要!
})

Q3: Content Script 无法接收消息?

javascript 复制代码
// 确保 Content Script 已注入
async function sendToContentScript(tabId, message) {
  try {
    await chrome.scripting.executeScript({
      target: { tabId },
      files: ['content-script.js']
    })
  } catch {}
  return chrome.tabs.sendMessage(tabId, message)
}

Q4: 如何在 Service Worker 中持久化数据?

javascript 复制代码
// 不要使用全局变量,使用 chrome.storage
async function getData(key) {
  const result = await chrome.storage.local.get(key)
  return result[key]
}

十八、参考资源

官方文档

开发工具

常用 Chrome API 速查

API 用途 权限
chrome.runtime 扩展生命周期、消息通信 -
chrome.storage 数据存储 storage
chrome.tabs 标签页管理 tabs
chrome.sidePanel 侧边栏 sidePanel
chrome.action 工具栏图标 -
chrome.contextMenus 右键菜单 contextMenus
chrome.notifications 系统通知 notifications
chrome.alarms 定时器 alarms
chrome.scripting 脚本注入 scripting
chrome.declarativeNetRequest 网络请求拦截 declarativeNetRequest
chrome.offscreen 离屏文档 offscreen
chrome.i18n 国际化 -

总结

本文系统性地介绍了 Chrome 扩展开发的核心知识:

  1. 项目结构:manifest.json 配置、各文件职责
  2. 核心组件:Background Service Worker、Content Script、Side Panel、Popup
  3. 消息通信:runtime.sendMessage、tabs.sendMessage、Port 长连接
  4. 数据存储:chrome.storage API、IndexedDB
  5. 网络请求:HTTP 封装、流式 AI 对话
  6. 安全实践:输入验证、加密存储、权限最小化
  7. 高级 API:declarativeNetRequest、Offscreen Documents
  8. 国际化:i18n 支持
  9. 现代工具链:Vite + CRXJS、TypeScript、React/Vue
  10. 监控与分析:错误追踪、用户行为分析、性能监控
  11. 发布流程:Chrome Web Store 发布步骤

本文基于实际项目经验整理,代码示例均经过测试验证。如有问题欢迎交流讨论。

相关推荐
布局呆星2 小时前
Vue 3 事件处理与列表渲染---02
前端·javascript·vue.js
漫天黄叶远飞2 小时前
🎄2025年圣诞节,单身的我只能用 Gemini 3 “嘴遁”出了一棵赛博圣诞树
前端·人工智能·gemini
开心_开心急了2 小时前
AI + PySide6 实现可缩放窗口
前端
weibkreuz2 小时前
组件三大核心属性-state@6
前端
未寒2 小时前
关于uni app vue2 和vue3 的区别
前端·javascript·vue.js·uni-app
至此流年莫相忘2 小时前
python基础语法
前端·python
小小鸟0082 小时前
大文件上传
前端
托马斯-酷涛2 小时前
圣诞树-圣诞节-HTML文件-光效粒子/支持360旋转
前端·html
Ingsuifon2 小时前
ReAct智能体实现示例
前端·react.js·前端框架