Skip to content

浏览器插件

INFO

  • manifest.json:配置文件,定义插件的基本信息和权限
  • Content Scripts:在网页中运行的脚本,可以访问和修改网页内容
  • Background Scripts:在后台运行的脚本,处理事件和持久化数据
  • Popup:点击插件图标时显示的弹出页面
  • Options Page:插件设置页面
  • Icons:插件图标

manifest.json

Manifest V3

json
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "description": "浏览器插件描述",
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"],
      "css": ["content.css"],
      "run_at": "document_idle"
    }
  ],
  "permissions": ["storage", "tabs", "activeTab"],
  "host_permissions": ["https://*/*", "http://*/*"]
}

常用配置项

配置项说明
manifest_version清单版本,V3 为最新
name插件名称
version版本号
description插件描述
icons插件图标
action工具栏按钮配置
background后台脚本配置
content_scripts内容脚本配置
permissions权限声明
host_permissions主机权限

Content Scripts

基本使用

javascript
// content.js

// 访问DOM
const title = document.querySelector('h1').textContent;

// 修改页面内容
document.body.style.backgroundColor = '#f0f0f0';

// 监听页面事件
document.addEventListener('click', e => {
  console.log('点击了:', e.target);
});

// 向background发送消息
chrome.runtime.sendMessage({
  type: 'pageData',
  data: { title, url: window.location.href },
});

// 接收来自background的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.type === 'highlight') {
    highlightText(request.text);
  }
});

注入时机

run_at 值说明
document_startCSS 加载后,DOM 构建前
document_endDOM 构建完成,资源加载前
document_idle浏览器选择最佳时机(默认)

匹配模式

json
{
  "matches": [
    "<all_urls>", // 所有网页
    "https://*/*", // 所有HTTPS网站
    "http://*.example.com/*", // 特定域名
    "file:///*", // 本地文件
    "https://example.com/*" // 特定网站
  ]
}

Background Scripts

Service Worker(V3)

javascript
// background.js

// 监听插件安装
chrome.runtime.onInstalled.addListener(() => {
  console.log('插件已安装');

  // 设置默认值
  chrome.storage.local.set({
    settings: {
      enabled: true,
      theme: 'light',
    },
  });
});

// 监听消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.type === 'pageData') {
    console.log('收到页面数据:', request.data);
    sendResponse({ status: 'success' });
  }
});

// 监听标签页更新
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  if (changeInfo.status === 'complete') {
    console.log('页面加载完成:', tab.url);
  }
});

// 监听命令
chrome.commands.onCommand.addListener(command => {
  if (command === 'toggle-feature') {
    toggleFeature();
  }
});

// 存储数据
async function saveData(key, value) {
  await chrome.storage.local.set({ [key]: value });
}

// 读取数据
async function loadData(key) {
  const result = await chrome.storage.local.get(key);
  return result[key];
}

HTML 结构

html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <style>
      body {
        width: 300px;
        padding: 10px;
      }
      button {
        width: 100%;
        padding: 8px;
        margin: 5px 0;
      }
    </style>
  </head>
  <body>
    <h3>我的插件</h3>
    <button id="actionBtn">执行操作</button>
    <button id="settingsBtn">打开设置</button>
    <script src="popup.js"></script>
  </body>
</html>

JavaScript 逻辑

javascript
// popup.js

document.getElementById('actionBtn').addEventListener('click', async () => {
  // 获取当前标签页
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

  // 向content script发送消息
  chrome.tabs.sendMessage(tab.id, {
    type: 'executeAction',
  });
});

document.getElementById('settingsBtn').addEventListener('click', () => {
  // 打开设置页面
  chrome.runtime.openOptionsPage();
});

// 加载设置
chrome.storage.local.get(['settings'], result => {
  if (result.settings) {
    console.log('设置:', result.settings);
  }
});

常用 API

Storage API

javascript
// 保存数据
chrome.storage.local.set({ key: 'value' });

// 读取数据
chrome.storage.local.get(['key'], result => {
  console.log(result.key);
});

// 删除数据
chrome.storage.local.remove(['key']);

// 清空所有数据
chrome.storage.local.clear();

// 监听数据变化
chrome.storage.onChanged.addListener((changes, namespace) => {
  if (namespace === 'local' && changes.key) {
    console.log('数据已变化:', changes.key.newValue);
  }
});

Tabs API

javascript
// 获取当前标签页
chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
  console.log(tabs[0]);
});

// 创建新标签页
chrome.tabs.create({ url: 'https://example.com' });

// 更新标签页
chrome.tabs.update(tabId, { url: 'https://newurl.com' });

// 关闭标签页
chrome.tabs.remove(tabId);

// 向标签页注入脚本
chrome.tabs.executeScript(tabId, {
  code: 'document.body.style.backgroundColor = "red"',
});

Runtime API

javascript
// 获取插件ID
chrome.runtime.id;

// 获取插件URL
chrome.runtime.getURL('icon.png');

// 发送消息
chrome.runtime.sendMessage({ type: 'ping' }, response => {
  console.log(response);
});

// 监听消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  sendResponse({ status: 'received' });
});

Notifications API

javascript
// 显示通知
chrome.notifications.create({
  type: 'basic',
  iconUrl: 'icon.png',
  title: '通知标题',
  message: '通知内容',
});

// 监听通知点击
chrome.notifications.onClicked.addListener(notificationId => {
  console.log('通知被点击:', notificationId);
});

权限说明

常用权限

权限说明
storage访问存储 API
tabs访问标签页信息
activeTab访问当前活动标签页
scripting动态注入脚本
notifications显示通知
bookmarks访问书签
history访问浏览历史
downloads管理下载
cookies访问 Cookie

Host Permissions

json
{
  "host_permissions": ["https://*.google.com/*", "http://localhost:*/*"]
}

开发工具

Plasmo

Plasmo 是一个现代化的浏览器插件开发框架,支持 React、TypeScript 等。

安装

bash
npm create plasmo
cd my-extension
npm install
npm run dev

项目结构

my-extension/
├── package.json
├── plasmo.config.ts
├── src/
│   ├── popup.tsx
│   ├── content.tsx
│   ├── background.ts
│   └── options.tsx
└── assets/
    └── icon.png

示例代码

typescript
// src/content.tsx
import type { PlasmoCSConfig } from 'plasmo';

export const config: PlasmoCSConfig = {
  matches: ['<all_urls>'],
};

export default function ContentScript() {
  return (
    <div
      style={{
        position: 'fixed',
        top: '10px',
        right: '10px',
        background: 'white',
        padding: '10px',
        boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
      }}
    >
      Hello from Content Script!
    </div>
  );
}

Chrome Extensions CLI

bash
npm install -g chrome-extension-cli
chrome-extension-cli my-extension

调试

加载插件

  1. 打开 Chrome 浏览器
  2. 访问 chrome://extensions/
  3. 开启"开发者模式"
  4. 点击"加载已解压的扩展程序"
  5. 选择插件目录

查看日志

  • Content Script:在网页控制台查看
  • Background Script:在扩展程序页面点击"service worker"查看
  • Popup:右键点击 popup 选择"检查"

热重载

修改代码后,在扩展程序页面点击刷新按钮即可。

打包发布

打包

bash
# 使用Plasmo
npm run build

# 手动打包
# 将插件文件夹压缩为zip文件

发布到 Chrome Web Store

  1. 访问 Chrome Web Store Developer Dashboard
  2. 上传 zip 文件
  3. 填写商店信息
  4. 提交审核

最佳实践

1. 使用 Manifest V3

V3 是最新版本,提供更好的安全性和性能。

2. 最小化权限

只申请必要的权限,提高用户信任度。

3. 使用 Content Security Policy

json
{
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'"
  }
}

4. 异步编程

使用 Promise 和 async/await 处理异步操作。

5. 错误处理

javascript
try {
  const result = await chrome.storage.local.get('key');
} catch (error) {
  console.error('操作失败:', error);
}

常见问题

Content Script 无法访问页面变量

Content Script 运行在隔离的环境中,无法直接访问页面的 JavaScript 变量。需要通过window.postMessage通信。

Background Script 无法访问 DOM

Background Script 运行在独立的环境中,无法直接访问页面 DOM。需要通过 Content Script 操作。

跨域问题

需要在host_permissions中声明目标域名。

参考资源