浏览器扩展(Browser Extension)是一种能够增强浏览器功能的小型软件程序。通过学习浏览器扩展开发,你可以为 Chrome、Firefox、Edge 等主流浏览器创建自定义功能。本文将详细介绍浏览器扩展开发的基础知识、核心概念和实际开发示例。

什么是浏览器扩展?

浏览器扩展是基于 Web 技术(HTML、CSS 和 JavaScript)构建的应用程序,它们可以修改和增强浏览器的行为。扩展运行在浏览器的沙盒环境中,可以访问特定的浏览器 API,但不能直接访问网页的 DOM。

扩展的核心组件

一个典型的浏览器扩展包含以下核心文件:

  1. manifest.json - 扩展的配置文件,定义扩展的基本信息和权限
  2. popup.html - 点击扩展图标时显示的弹出窗口
  3. background.js - 后台脚本,处理扩展的长期运行任务
  4. content.js - 内容脚本,注入到网页中与页面交互
  5. icons/ - 扩展的图标资源

开发环境准备

必需工具

  • 现代浏览器(Chrome、Firefox 或 Edge)
  • 代码编辑器(如 VS Code)
  • 基本的 HTML、CSS 和 JavaScript 知识

安装步骤

  1. 下载并安装 Visual Studio Code
  2. 安装浏览器(推荐 Chrome 或 Firefox)
  3. 创建一个新文件夹作为项目目录

创建第一个扩展:页面文本高亮工具

我们将创建一个简单的扩展,它可以在当前页面上高亮显示用户指定的关键词。

1. 创建 manifest.json

{
  "manifest_version": 3,
  "name": "Text Highlighter",
  "version": "1.0",
  "description": "Highlight text on web pages",
  "permissions": ["activeTab", "scripting"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

说明

  • manifest_version: 使用版本3(当前最新标准)
  • permissions: 声明需要的权限,activeTab 允许访问当前标签页,scripting 允许注入脚本
  • action: 定义扩展图标和弹出窗口
  • icons: 定义不同尺寸的图标

2. 创建 popup.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    body {
      width: 250px;
      padding: 15px;
      font-family: Arial, sans-serif;
    }
    input {
      width: 100%;
      padding: 8px;
      margin-bottom: 10px;
      box-sizing: border-box;
    }
    button {
      width: 100%;
      padding: 8px;
      background-color: #4CAF50;
      color: white;
      border: none;
      cursor: pointer;
    }
    button:hover {
      background-color: #45a049;
    }
    #status {
      margin-top: 10px;
      font-size: 12px;
      color: #666;
    }
  </style>
</head>
<body>
  <h3>Text Highlighter</h3>
  <input type="text" id="keyword" placeholder="输入要高亮的关键词">
  <button id="highlight">高亮显示</button>
  <button id="clear">清除高亮</button>
  <div id="status"></div>

  <script src="popup.js"></script>
</body>
</html>

3. 创建 popup.js

document.addEventListener('DOMContentLoaded', function() {
  const highlightBtn = document.getElementById('highlight');
  const clearBtn = document.getElementById('clear');
  const keywordInput = document.getElementById('keyword');
  const statusDiv = document.getElementById('status');

  // 高亮按钮点击事件
  highlightBtn.addEventListener('click', async function() {
    const keyword = keywordInput.value.trim();
    
    if (!keyword) {
      statusDiv.textContent = '请输入关键词';
      statusDiv.style.color = 'red';
      return;
    }

    try {
      // 获取当前活动标签页
      const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
      
      // 注入内容脚本
      await chrome.scripting.executeScript({
        target: { tabId: tab.id },
        func: highlightText,
        args: [keyword]
      });

      statusDiv.textContent = '高亮成功!';
      statusDiv.style.color = 'green';
    } catch (error) {
      statusDiv.textContent = '错误:' + error.message;
      statusDiv.style.color = 'red';
    }
  });

  // 清除按钮点击事件
  clearBtn.addEventListener('click', async function() {
    try {
      const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
      
      await chrome.scripting.executeScript({
        target: { tabId: tab.id },
        func: removeHighlight
      });

      statusDiv.textContent = '已清除高亮';
      statusDiv.style.color = 'green';
      keywordInput.value = '';
    } catch (error) {
      statusDiv.textContent = '错误:' + error.message;
      statusDiv.style.color = 'red';
    }
  });

  // 高亮函数(将在页面上下文中执行)
  function highlightText(keyword) {
    // 首先移除现有的高亮
    removeHighlight();
    
    // 创建高亮样式
    const style = document.createElement('style');
    style.id = 'highlight-style';
    style.textContent = `
      .highlighted-text {
        background-color: yellow !important;
        font-weight: bold !important;
        padding: 2px !important;
        border-radius: 3px !important;
      }
    `;
    document.head.appendChild(style);

    // 在页面中搜索并高亮关键词
    const walker = document.createTreeWalker(
      document.body,
      NodeFilter.SHOW_TEXT,
      null,
      false
    );

    const textNodes = [];
    let node;
    while (node = walker.nextNode()) {
      textNodes.push(node);
    }

    textNodes.forEach(textNode => {
      const text = textNode.textContent;
      const regex = new RegExp(`(${escapeRegExp(keyword)})`, 'gi');
      
      if (regex.test(text)) {
        const span = document.createElement('span');
        span.innerHTML = text.replace(regex, '<span class="highlighted-text">$1</span>');
        textNode.parentNode.replaceChild(span, textNode);
      }
    });
  }

  // 清除高亮函数
  function removeHighlight() {
    const highlights = document.querySelectorAll('.highlighted-text');
    highlights.forEach(highlight => {
      const parent = highlight.parentNode;
      parent.replaceChild(document.createTextNode(highlight.textContent), highlight);
      parent.normalize(); // 合并相邻的文本节点
    });

    const style = document.getElementById('highlight-style');
    if (style) {
      style.remove();
    }
  }

  // 转义正则表达式特殊字符
  function escapeRegExp(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  }
});

4. 创建图标

在项目文件夹中创建 icons 文件夹,并放入以下尺寸的图标:

  • icon16.png (16x16 像素)
  • icon48.png (48x48 像素)
  • icon128.png (128x128 像素)

你可以使用任何图片编辑工具创建这些图标,或者暂时用纯色方块代替。

安装和测试扩展

Chrome 浏览器

  1. 打开 Chrome 浏览器,输入 chrome://extensions/ 进入扩展管理页面
  2. 开启右上角的 “开发者模式”
  3. 点击 “加载已解压的扩展程序”
  4. 选择你的项目文件夹
  5. 扩展将被加载,你可以测试它的功能

Firefox 浏览器

  1. 打开 Firefox 浏览器,输入 about:debugging#/runtime/this-firefox
  2. 点击 “临时加载附加组件”
  3. 选择你的项目文件夹中的任意文件(如 manifest.json)
  4. 点击 “临时加载”
  5. 扩展将被加载,你可以测试它的功能

扩展的核心概念详解

1. Manifest 文件详解

Manifest 文件是扩展的配置中心,以下是关键字段:

{
  "manifest_version": 3,
  "name": "扩展名称",
  "version": "1.0.0",
  "description": "扩展描述",
  
  // 权限声明
  "permissions": [
    "storage",      // 访问本地存储
    "activeTab",    // 访问当前标签页
    "scripting",    // 注入脚本
    "tabs",         // 访问标签页信息
    "notifications" // 发送通知
  ],
  
  // 主机权限(需要访问特定网站时)
  "host_permissions": [
    "*://*.example.com/*"
  ],
  
  // 浏览器工具栏按钮
  "action": {
    "default_popup": "popup.html",
    "default_title": "点击打开",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png"
    }
  },
  
  // 后台服务
  "background": {
    "service_worker": "background.js"
  },
  
  // 内容脚本(注入到网页中)
  "content_scripts": [
    {
      "matches": ["*://*.example.com/*"],
      "js": ["content.js"],
      "css": ["content.css"]
    }
  ],
  
  // 选项页面
  "options_page": "options.html",
  
  // 图标
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

2. 后台脚本(Background Script)

后台脚本是扩展的”大脑”,它在后台运行,处理事件和维护状态。

// background.js

// 监听扩展安装事件
chrome.runtime.onInstalled.addListener(() => {
  console.log('扩展已安装');
  // 初始化存储
  chrome.storage.local.set({ highlights: [] });
});

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

// 监听消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === 'saveHighlight') {
    // 保存高亮信息到存储
    chrome.storage.local.get(['highlights'], (result) => {
      const highlights = result.highlights || [];
      highlights.push({
        keyword: message.keyword,
        url: message.url,
        timestamp: Date.now()
      });
      chrome.storage.local.set({ highlights }, () => {
        sendResponse({ success: true });
      });
    });
    return true; // 表示异步响应
  }
});

// 监听浏览器按钮点击
chrome.action.onClicked.addListener((tab) => {
  // 可以在这里执行一些操作
  console.log('扩展图标被点击');
});

3. 内容脚本(Content Script)

内容脚本运行在网页的上下文中,可以直接访问和修改页面 DOM。

// content.js

// 监听来自后台或弹出窗口的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'highlight') {
    highlightText(request.keyword);
    sendResponse({ result: 'highlighted' });
  } else if (request.action === 'clear') {
    removeHighlight();
    sendResponse({ result: 'cleared' });
  } else if (request.action === 'getStats') {
    const count = document.querySelectorAll('.highlighted-text').length;
    sendResponse({ count });
  }
});

// 高亮函数
function highlightText(keyword) {
  // 实现与之前popup.js中相同的高亮逻辑
  // ...
}

// 清除高亮
function removeHighlight() {
  // 实现与之前popup.js中相同的清除逻辑
  // ...
}

4. 选项页面(Options Page)

选项页面允许用户配置扩展设置。

<!-- options.html -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 600px;
      margin: 0 auto;
      padding: 20px;
    }
    .option-group {
      margin-bottom: 20px;
      padding: 15px;
      border: 1px solid #ddd;
      border-radius: 5px;
    }
    label {
      display: block;
      margin-bottom: 5px;
      font-weight: bold;
    }
    input[type="text"], input[type="color"] {
      width: 100%;
      padding: 8px;
      margin-bottom: 10px;
    }
    button {
      padding: 10px 20px;
      background-color: #4CAF50;
      color: white;
      border: none;
      cursor: pointer;
      margin-right: 10px;
    }
    button:hover {
      background-color: #45a049;
    }
    #status {
      margin-top: 10px;
      color: #666;
    }
  </style>
</head>
<body>
  <h1>Text Highlighter 设置</h1>
  
  <div class="option-group">
    <label for="defaultKeyword">默认高亮关键词</label>
    <input type="text" id="defaultKeyword" placeholder="输入默认关键词">
  </div>
  
  <div class="option-group">
    <label for="highlightColor">高亮颜色</label>
    <input type="color" id="highlightColor" value="#ffff00">
  </div>
  
  <div class="option-group">
    <label>
      <input type="checkbox" id="autoHighlight"> 自动高亮
    </label>
  </div>
  
  <button id="save">保存设置</button>
  <button id="reset">重置为默认值</button>
  <div id="status"></div>

  <script src="options.js"></script>
</body>
</html>
// options.js

document.addEventListener('DOMContentLoaded', function() {
  const defaultKeywordInput = document.getElementById('defaultKeyword');
  const highlightColorInput = document.getElementById('highlightColor');
  const autoHighlightCheckbox = document.getElementById('autoHighlight');
  const saveBtn = document.getElementById('save');
  const resetBtn = document.getElementById('reset');
  const statusDiv = document.getElementById('status');

  // 加载保存的设置
  chrome.storage.sync.get(['settings'], (result) => {
    if (result.settings) {
      defaultKeywordInput.value = result.settings.defaultKeyword || '';
      highlightColorInput.value = result.settings.highlightColor || '#ffff00';
      autoHighlightCheckbox.checked = result.settings.autoHighlight || false;
    }
  });

  // 保存设置
  saveBtn.addEventListener('click', function() {
    const settings = {
      defaultKeyword: defaultKeywordInput.value,
      highlightColor: highlightColorInput.value,
      autoHighlight: autoHighlightCheckbox.checked,
      lastSaved: new Date().toISOString()
    };

    chrome.storage.sync.set({ settings }, () => {
      statusDiv.textContent = '设置已保存!';
      statusDiv.style.color = 'green';
      
      // 发送消息给后台脚本
      chrome.runtime.sendMessage({ action: 'settingsUpdated', settings });
    });
  });

  // 重置设置
  resetBtn.addEventListener('click', function() {
    const defaultSettings = {
      defaultKeyword: '',
      highlightColor: '#ffff00',
      autoHighlight: false
    };

    defaultKeywordInput.value = defaultSettings.defaultKeyword;
    highlightColorInput.value = defaultSettings.highlightColor;
    autoHighlightCheckbox.checked = defaultSettings.autoHighlight;

    chrome.storage.sync.set({ settings: defaultSettings }, () => {
      statusDiv.textContent = '设置已重置!';
      statusDiv.style.color = 'blue';
    });
  });
});

高级功能和最佳实践

1. 使用 Chrome Storage API

// 保存数据
chrome.storage.local.set({ key: 'value' }, () => {
  console.log('数据已保存');
});

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

// 删除数据
chrome.storage.local.remove('key', () => {
  console.log('数据已删除');
});

// 清空所有数据
chrome.storage.local.clear(() => {
  console.log('所有数据已清空');
});

2. 消息传递

// 从弹出窗口发送消息到后台脚本
chrome.runtime.sendMessage({ action: 'updateCount', count: 5 }, (response) => {
  console.log('收到响应:', response);
});

// 从后台脚本发送消息到内容脚本
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
  chrome.tabs.sendMessage(tabs[0].id, { action: 'highlight', keyword: 'test' }, (response) => {
    console.log('内容脚本响应:', response);
  });
});

// 从内容脚本发送消息到后台脚本
chrome.runtime.sendMessage({ action: 'logEvent', event: 'pageLoaded' }, (response) => {
  console.log('后台脚本响应:', response);
});

3. 处理错误和调试

// 错误处理
try {
  // 可能出错的操作
  chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
    if (chrome.runtime.lastError) {
      console.error('Chrome API 错误:', chrome.runtime.lastError.message);
      return;
    }
    // 正常处理
  });
} catch (error) {
  console.error('运行时错误:', error);
}

// 调试技巧
// 1. 在 Chrome 扩展管理页面查看背景页的控制台
// 2. 在弹出窗口右键 -> 检查
// 3. 在网页中查看内容脚本的控制台

4. 性能优化

// 使用防抖(debounce)减少频繁操作
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// 在事件处理中使用
const debouncedHighlight = debounce(highlightText, 300);
input.addEventListener('input', debouncedHighlight);

// 避免不必要的 DOM 操作
// 不好的做法:每次输入都重新扫描整个页面
// 好的做法:只在用户停止输入后执行

5. 安全考虑

// 验证输入,防止 XSS 攻击
function sanitizeInput(input) {
  const div = document.createElement('div');
  div.textContent = input;
  return div.innerHTML;
}

// 只在指定的网站上运行内容脚本
// 在 manifest.json 中配置
"content_scripts": [
  {
    "matches": ["*://*.example.com/*", "*://*.trustedsite.com/*"],
    "js": ["content.js"],
    "exclude_matches": ["*://*.untrusted.com/*"]
  }
]

// 使用 CSP(内容安全策略)
// 在 manifest.json 中添加
"content_security_policy": {
  "extension_pages": "script-src 'self'; object-src 'self'"
}

调试技巧

1. 查看扩展日志

  • 背景脚本日志:在扩展管理页面,点击”背景页”链接
  • 弹出窗口日志:右键点击弹出窗口,选择”检查”
  • 内容脚本日志:在网页上右键”检查”,查看控制台

2. 使用 Chrome 开发者工具

// 在代码中添加断点
debugger; // 这会在执行到时暂停

// 使用 console.log 调试
console.log('当前状态:', { variable1, variable2 });

// 使用 Chrome 的 performance 工具分析性能

3. 常见问题排查

问题:扩展图标不显示

  • 检查 manifest.json 中的图标路径是否正确
  • 确保图标文件存在且格式正确

问题:内容脚本不工作

  • 检查 manifest.json 中的 matches 配置
  • 确认权限是否足够
  • 查看内容脚本的控制台错误

问题:存储数据丢失

  • 检查是否使用了正确的存储 API(local vs sync)
  • 检查存储配额(local: ∞, sync: 100KB)

发布扩展

1. 准备发布包

# 创建发布包
1. 移除所有调试代码和 console.log
2. 压缩图片资源
3. 确保 manifest.json 版本号正确
4. 创建 README.md 和隐私政策

2. Chrome 网上应用店

  1. 访问 Chrome 开发者仪表板
  2. 上传扩展包
  3. 填写商店信息(名称、描述、截图等)
  4. 支付一次性注册费($5)
  5. 提交审核

3. Firefox 附加组件商店

  1. 访问 Firefox 开发者中心
  2. 上传扩展包
  3. 通过自动验证
  4. 提交人工审核(如果需要)

总结

浏览器扩展开发是一个强大且灵活的领域,通过掌握上述知识和技能,你可以创建各种实用的工具来增强浏览器功能。从简单的页面操作到复杂的数据处理,浏览器扩展都能胜任。

下一步学习建议

  1. 深入学习 Chrome API:探索更多可用的 API,如 bookmarks、history、downloads 等
  2. 学习框架集成:尝试使用 React、Vue 等框架开发扩展
  3. 研究开源扩展:查看 GitHub 上的优秀扩展项目
  4. 参与社区:加入 Chrome 扩展开发者社区,学习最佳实践

推荐资源

通过实践和不断学习,你将能够开发出功能强大、用户友好的浏览器扩展!