Chrome 扩展开发从入门到实践:以 Cookie 跨页提取工具为例,拆解核心模块与交互逻辑

在日常开发或测试工作中,我们经常需要将 A 页面的 Cookie 拷贝到 B 页面(比如模拟用户登录状态、同步环境配置),手动复制粘贴不仅繁琐,还容易出错。既然有痛点,不如动手开发一款 Chrome 插件来解决 ------ 这不仅能自动化重复操作,还能系统掌握 Chrome 扩展开发的核心能力。

本文将分为两部分:先梳理 Chrome 插件开发的基础知识,再基于实际需求实现 "Cookie 跨页面迁移插件",最终成品支持提取当前页 Cookie、向目标页注入 Cookie、本地保存 Cookie 集三大核心功能。

基础知识

在动手前,我们需要先了解 Chrome 扩展的核心组成和工作原理。建议结合Chrome 官方开发指南学习,以下是关键知识点梳理:

模块

Chrome 插件由多个功能模块组成,不同模块负责不同职责,协同完成插件功能:

模块 作用 核心特点
manifest.json 插件配置 根目录下的配置文件,记录插件名称、版本、权限、资源路径等元数据,是插件运行的基础
Service Worker 后台服务 无界面、在后台持续运行,处理浏览器事件(如标签切换)、管理全局状态、协调其他模块通信
Popup(弹窗) 用户交互界面 点击插件图标弹出的窗口(由 popup.html+popup.js 构成),负责接收用户操作(如按钮点击)

manifest.json

manifest.json 是插件的 "灵魂",所有模块和权限都需在此声明。目前 Chrome 推荐使用Manifest V3(取代旧版 V2),以下是基础配置模板(含关键注释):

json 复制代码
{
  "manifest_version": 3, // 必须为3,声明使用Manifest V3版本
  "name": "插件名称", // 插件显示名称(如"Cookie Maker")
  "version": "1.0", // 版本号(更新时需修改)
  "description": "插件功能描述", // 简短说明插件用途
  "icons": { // 插件图标(不同尺寸适配不同场景)
    "16": "images/icon-16.png",  // 扩展管理页面小图标
    "32": "images/icon-32.png",  // 扩展管理页面中图标
    "48": "images/icon-48.png",  // 扩展管理页面大图标
    "128": "images/icon-128.png" // Chrome应用商店图标
  },
  "background": { // 后台服务配置
    "service_worker": "background.js" // 指向后台脚本文件
  },
  "action": { // 插件图标点击行为
    "default_popup": "popup.html", // 点击图标弹出的页面
    "default_icon": { // 图标路径(与上方icons一致)
      "16": "images/icon-16.png",
      "32": "images/icon-32.png"
    }
  },
  "permissions": [ // 插件需要的权限
    "cookies", // 操作Cookie的权限
    "activeTab", // 访问当前活动标签页的权限
    "storage", // 本地存储数据的权限
    "scripting" // 注入脚本到网页的权限
  ],
  "host_permissions": [ // 允许操作的网站范围
    "<all_urls>" // 允许操作所有网站(根据需求可缩小范围)
  ]
}

popup.html

定义扩展工具栏图标的「弹出界面」(点击扩展图标时显示的小窗口),是扩展与用户交互的主要前端界面之一。本质是一个 HTML 文件,可包含 CSS 样式和 DOM 结构,支持常规 HTML 元素(按钮、输入框、列表等)。

popup.js

作为 popup.html 的配套脚本,负责处理弹出界面的交互逻辑(如按钮点击、数据渲染、与后台通信等)。

  • popup.html 绑定,通过 <script src="popup.js"></script> 引入,运行在弹出窗口的上下文环境中。
  • 生命周期与 popup.html 一致:窗口打开时加载,关闭时销毁,因此变量和状态不会持久保存。
  • 可直接操作 popup.html 的 DOM,也能通过 Chrome API(如 chrome.runtime.sendMessage)与 background.js 或内容脚本通信,获取 / 提交数据。

service_worker-background.js

作为扩展的「后台服务」,负责处理长期运行的逻辑、监听浏览器事件、管理扩展全局状态等,是扩展的「大脑」。

  • 运行在独立的后台进程中,生命周期与浏览器一致(浏览器启动时加载,关闭时终止),即使弹出窗口关闭也能持续运行。
  • 无界面(不可直接操作 DOM),主要通过监听 Chrome 事件(如标签页切换、网络请求、扩展安装等)触发逻辑。
  • 可存储全局数据(如用户配置、长期状态),并作为中间层协调 popup.js、内容脚本、其他扩展组件之间的通信。

交互流程

Chrome 插件的各模块并非孤立运行,而是通过固定流程协同工作,以 "用户点击按钮→提取 Cookie" 为例:

  1. 用户点击插件图标 → 浏览器加载popup.htmlpopup.js,弹出交互窗口;
  2. 用户点击 "提取 Cookie" 按钮 → popup.js触发点击事件,通过chrome.runtime.sendMessagebackground.js发送请求;
  3. background.js监听消息,调用chrome.cookies.getAllAPI 提取当前页 Cookie,将结果返回给popup.js
  4. popup.js接收结果,更新弹窗 DOM(如显示 Cookie 列表),完成交互;
  5. 用户关闭弹窗 → popup.htmlpopup.js销毁,但background.js仍在后台运行,等待下一次请求。

通过这种分工,popup 负责前端交互,background 负责后台逻辑和状态管理,形成了 Chrome 扩展的核心架构。

一个用于cookie跨页面提取和插入的chrome插件

了解基础知识后,我们开始开发核心功能。插件最终实现以下能力:

  • 提取当前页面的所有 Cookie;
  • 向目标页面注入指定 Cookie;
  • 本地保存多个 Cookie 集(支持加载、删除);
  • 支持 JSON / 表格两种视图查看 Cookie。

项目结构:

bash 复制代码
cookie-maker/
├─ images/          # 插件图标文件夹
│  ├─ icon16.png
│  ├─ icon48.png
│  └─ icon128.png
├─ manifest.json    # 核心配置文件
├─ popup.html       # 弹窗界面
├─ popup.js         # 弹窗交互逻辑
└─ background.js    # 后台服务逻辑

1. 配置manifest.json

根据插件功能,声明所需权限和资源路径,这里我们的扩展需要以下权限:

  • cookies:用于读取和修改Cookie
  • activeTab:用于获取当前活动标签页信息
  • storage:用于在本地存储保存的Cookie集
  • scripting:用于在页面中执行脚本
  • <all_urls>:允许扩展操作所有网站的Cookie
json 复制代码
{
  "manifest_version": 3,
  "name": "Cookie Maker",
  "version": "1.0",
  "description": "跨标签页提取和注入Cookie的Chrome扩展",
  "permissions": [
    "cookies",
    "activeTab",
    "storage",
    "scripting"
  ],
  "host_permissions": [
    "<all_urls>"
  ],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "images/icon16.png",
      "48": "images/icon48.png",
      "128": "images/icon128.png"
    }
  },
  "background": {
    "service_worker": "background.js"
  },
  "icons": {
    "16": "images/icon16.png",
    "48": "images/icon48.png",
    "128": "images/icon128.png"
  }
}

2. 开发弹窗界面(popup.html)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Cookie Maker</title>
    <style>
      <!-- 省略 -->
    </style>
  </head>
  <body>
    <h1>Cookie Maker</h1>

    <!-- 当前URL和Cookie操作 -->
    <div class="input-group">
      <label for="current-url">当前页面URL</label>
      <input type="text" id="current-url" placeholder="https://example.com">
    </div>

    <div class="button-group">
      <button id="extract-cookies">提取当前页面Cookie</button>
      <button id="load-url">加载当前标签URL</button>
    </div>

    <!-- 目标URL输入 -->
    <div class="input-group">
      <label for="target-url">目标URL</label>
      <input type="text" id="target-url" placeholder="https://example.com">
    </div>

    <!-- Cookie展示区域 -->
    <div class="input-group">
      <label>Cookie数据</label>
      <div class="view-switcher">
        <button id="json-view-btn" class="secondary active">JSON视图</button>
        <button id="table-view-btn" class="secondary">表格视图</button>
      </div>
      <textarea id="cookies-json" placeholder="{}"></textarea>
      <div id="cookies-table-container" class="table-container">
        <table id="cookies-table" class="cookie-table">
          <thead>
            <tr>
              <th><input type="checkbox" id="select-all-cookies" title="全选/取消全选" /></th>
              <th>名称</th>
              <th>值</th>
              <th>域名</th>
              <th>路径</th>
              <th>过期时间</th>
              <th>安全</th>
              <th>HttpOnly</th>
            </tr>
          </thead>
          <tbody id="cookies-table-body">
            <!-- 表格内容将通过JavaScript动态生成 -->
          </tbody>
        </table>
      </div>
    </div>

    <div class="button-group">
      <button id="inject-cookies">向目标URL注入Cookie</button>
      <button id="format-json" class="secondary">格式化JSON</button>
    </div>

    <!-- 保存和加载Cookie -->
    <div class="input-group">
      <label for="cookie-set-name">Cookie集名称</label>
      <input type="text" id="cookie-set-name" placeholder="输入名称保存当前Cookie集">
    </div>

    <div class="button-group">
      <button id="save-cookies">保存Cookie集</button>
      <button id="load-saved-cookies" class="secondary">加载Cookie集</button>
    </div>

    <!-- 已保存的Cookie集 -->
    <div class="saved-cookies">
      <div class="saved-cookies-header">
        <label>已保存的Cookie集</label>
        <button id="refresh-saved-cookies" class="secondary">刷新</button>
      </div>
      <div id="saved-cookies-list" class="saved-cookies-list">
        <!-- 已保存的Cookie集将在这里显示 -->
      </div>
    </div>

    <!-- 消息提示区域 -->
    <div id="message" class="message"></div>

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

3. 开发弹窗交互逻辑(popup.js)

popup.js 负责处理用户操作(如点击按钮、视图切换),并与 background.js 通信获取 / 提交数据。

部分代码如下:

js 复制代码
/**
 * Chrome扩展的popup脚本
 * 负责处理用户界面交互和与后台脚本的通信
 */

// 等待DOM加载完成
document.addEventListener('DOMContentLoaded', async () => {
  // 绑定事件监听器
  document.getElementById('extract-cookies').addEventListener('click', extractCookies);
  // 。。。

  // 监听JSON文本变化,自动更新表格视图和缓存
  document.getElementById('cookies-json').addEventListener('input', function() {
    updateTableFromJson();
    cacheCookieData();
  });

  // 初始化页面
  await loadCurrentTabUrl();
  await refreshSavedCookiesList();

  // 加载缓存的Cookie数据
  await loadCachedCookieData();

  switchView('table')
});

/**
 * 缓存Cookie数据到background.js
 */
async function cacheCookieData() {
  const cookiesJson = document.getElementById('cookies-json').value.trim();
  try {
    await chrome.runtime.sendMessage({
      action: 'cacheCookieData',
      cookiesJson
    });
  } catch (error) {
    console.warn('缓存Cookie数据失败:', error);
  }
}

/**
 * 从background.js加载缓存的Cookie数据
 */
async function loadCachedCookieData() {
  try {
    const response = await chrome.runtime.sendMessage({
      action: 'loadCachedCookieData'
    });
    if (response && response.success && response.cachedData) {
      document.getElementById('cookies-json').value = response.cachedData;
      updateTableFromJson();
    }
  } catch (error) {
    console.error('加载缓存的Cookie数据失败:', error);
  }
}

/**
 * 从当前页面提取Cookie
 */
async function extractCookies() {
  const url = document.getElementById('current-url').value.trim();
  
  if (!url) {
    showMessage('请输入URL', false);
    return;
  }
  
  try {
    const response = await chrome.runtime.sendMessage({
      action: 'extractCookies',
      url
    });
    
    if (response.success) {
      document.getElementById('cookies-json').value = JSON.stringify(response.cookies, null, 2);
      updateTableFromJson(); // 更新表格视图
      await cacheCookieData(); // 缓存Cookie数据
      showMessage(`成功提取 ${response.cookies.length} 个Cookie`);
    } else {
      showMessage(`提取Cookie失败: ${response.error}`, false);
    }
  } catch (error) {
    showMessage(`提取Cookie失败: ${error.message}`, false);
  }
}

/**
 * 向目标URL注入Cookie
 */
async function injectCookies() {
  const url = document.getElementById('target-url').value.trim();
  const cookiesJson = document.getElementById('cookies-json').value.trim();
  
  if (!url) {
    showMessage('请输入目标URL', false);
    return;
  }
  
  if (!cookiesJson) {
    showMessage('请输入Cookie数据', false);
    return;
  }
  
  try {
    const cookies = JSON.parse(cookiesJson);
    
    // 如果当前是表格视图,检查勾选状态
    let cookiesToInject = cookies;
    const tableViewBtn = document.getElementById('table-view-btn');
    
    if (tableViewBtn.classList.contains('active')) {
      const checkedCheckboxes = document.querySelectorAll('#cookies-table-body input[type="checkbox"]:checked');
      
      if (checkedCheckboxes.length > 0) {
        // 只注入勾选的Cookie
        const checkedIndices = Array.from(checkedCheckboxes).map(checkbox => 
          parseInt(checkbox.id.replace('cookie-', ''))
        );
        
        cookiesToInject = cookies.filter((_, index) => checkedIndices.includes(index));
      }
    }
    
    const response = await chrome.runtime.sendMessage({
      action: 'injectCookies',
      url,
      cookies: cookiesToInject
    });
    
    if (response.success) {
      showMessage('成功注入Cookie');
    } else {
      showMessage(`注入Cookie失败: ${response.error}`, false);
    }
  } catch (error) {
    showMessage(`解析Cookie失败: ${error.message}`, false);
  }
}

4. 开发后台服务逻辑(background.js)

核心功能包括:

  • 提取 Cookie:调用chrome.cookies.getAll获取指定 URL 的 Cookie;
  • 注入 Cookie:调用chrome.cookies.set向目标 URL 写入 Cookie;
  • 本地存储:通过chrome.storage.local保存 / 加载 Cookie 集;
  • 消息监听:接收并处理popup.js发送的各类请求。

部分代码如下:

js 复制代码
/**
 * Chrome扩展的后台脚本
 * 负责处理Cookie的提取和注入逻辑
 */

// 监听来自popup的消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  switch (message.action) {
    case 'extractCookies':
      extractCookies(message.url).then(cookies => {
        sendResponse({ success: true, cookies });
      }).catch(error => {
        sendResponse({ success: false, error: error.message });
      });
      return true; // 保持消息通道开放,以便异步响应
      
    case 'injectCookies':
      injectCookies(message.url, message.cookies).then(() => {
        sendResponse({ success: true });
      }).catch(error => {
        sendResponse({ success: false, error: error.message });
      });
      return true; // 保持消息通道开放,以便异步响应
      // 。。。
});

/**
 * 缓存Cookie数据到chrome.storage.local
 * @param {string} cookiesJson - Cookie数据的JSON字符串
 * @returns {Promise<void>}
 */
async function cacheCookieData(cookiesJson) {
  if (cookiesJson) {
    try {
      // 验证JSON格式
      JSON.parse(cookiesJson);
      await chrome.storage.local.set({
        cachedCookieData: {
          data: cookiesJson,
          timestamp: new Date().getTime()
        }
      });
    } catch (error) {
      // JSON格式不正确,不缓存
      console.warn('缓存Cookie数据失败,JSON格式不正确:', error);
      throw new Error('JSON格式不正确');
    }
  }
}

/**
 * 从chrome.storage.local加载缓存的Cookie数据
 * @returns {Promise<string|null>} - 缓存的Cookie数据JSON字符串或null
 */
async function loadCachedCookieData() {
  try {
    const result = await chrome.storage.local.get('cachedCookieData');
    if (result.cachedCookieData && result.cachedCookieData.data) {
      return result.cachedCookieData.data;
    }
    return null;
  } catch (error) {
      console.error('加载缓存的Cookie数据失败:', error);
      throw error;
    }
  }

/**
 * 从指定URL提取所有Cookie
 * @param {string} url - 要提取Cookie的URL
 * @returns {Promise<Array>} - Cookie数组
 */
async function extractCookies(url) {
  try {
    console.log('extractCookies', url);
    const cookies = await chrome.cookies.getAll({ url });
    if (cookies.length === 0) {
      return 'No cookies found';
    }
    return cookies;
  } catch (error) {
    console.error('提取Cookie失败:', error);
    throw error;
  }
}

/**
 * 向指定URL注入Cookie
 * @param {string} url - 要注入Cookie的URL
 * @param {Array} cookies - 要注入的Cookie数组
 * @returns {Promise<void>}
 */
async function injectCookies(url, cookies) {
  try {
    const domain = new URL(url).hostname;
    let failedCookies = [];
    
    // 遍历所有Cookie并注入,对每个Cookie单独处理错误
    for (const cookie of cookies) {
      try {
        // 构建Cookie参数
        const cookieParams = {
          url: url,
          name: cookie.name,
          value: cookie.value,
          domain: domain,
          path: cookie.path || '/',
          secure: cookie.secure || false,
          httpOnly: cookie.httpOnly || false,
          sameSite: cookie.sameSite || 'unspecified'
        };
        
        // 如果Cookie有过期时间,添加到参数中
        if (cookie.expirationDate) {
          cookieParams.expirationDate = cookie.expirationDate;
        }
        
        // 单独处理每个Cookie的注入
        await chrome.cookies.set(cookieParams);
      } catch (error) {
        // 记录失败的Cookie,但继续处理其他Cookie
        console.warn(`注入Cookie ${cookie.name || '未知名称'} 失败:`, error);
        failedCookies.push({
          name: cookie.name || '未知名称',
          error: error.message
        });
      }
    }
    
    // 如果有Cookie注入失败,抛出错误信息
    if (failedCookies.length > 0) {
      const totalSuccess = cookies.length - failedCookies.length;
      const failedNames = failedCookies.map(c => c.name).join(', ');
      throw new Error(`成功注入 ${totalSuccess} 个Cookie,但有 ${failedCookies.length} 个注入失败: ${failedNames}`);
    }
  } catch (error) {
    console.error('注入Cookie过程中出现错误:', error);
    throw error;
  }
}

注意事项: 因为我们需要跨页面提取和注入cookie,点击另一个页面的时候popup会关闭,因此需要将数据存储在chrome.storage.local中,chrome.storage.local与插件绑定,除非主动删除否则永久存在

5. 安装使用

5.1. 安装

  1. 打开Chrome浏览器,访问 chrome://extensions/
  2. 开启右上角的 "开发者模式"
  3. 点击 "加载已解压的扩展程序"
  4. 选择本项目的根目录
  5. 扩展将被添加到Chrome浏览器中

5.2. 使用

  1. 点击Chrome浏览器工具栏中的Cookie Maker图标打开扩展面板
  2. 默认会显示当前标签页的URL
  3. 提取Cookie:点击 "提取当前页面Cookie" 按钮,扩展会自动提取当前页面的所有Cookie
  4. 注入Cookie:在 "目标URL" 输入框中输入目标网站地址,确保Cookie数据已正确填写,然后点击 "向目标URL注入Cookie" 按钮
  5. 保存Cookie集:输入Cookie集名称,点击 "保存Cookie集" 按钮
  6. 加载Cookie集:输入要加载的Cookie集名称,点击 "加载Cookie集" 按钮
  7. 已保存的Cookie集:扩展面板下方会显示所有已保存的Cookie集,可以直接点击 "加载" 或 "使用" 按钮进行操作

代码地址:github.com/sunqqw/cook...

如果觉得有用,欢迎 Star 支持,也欢迎提交 PR 一起完善功能!

相关推荐
gplitems1235 小时前
Download:Blaxcut - Barbershop & Hair Salon WordPress Theme
前端
拜无忧5 小时前
【DEMO】互动信息墙 - 无限流动版-点击放大
前端
冰糖雪梨dd5 小时前
JS中new的过程发生了什么
开发语言·javascript·原型模式
AliPaPa5 小时前
你可能忽略了useSyncExternalStore + useOptimistic + useTransition
前端·react.js
parade岁月5 小时前
nuxt和vite使用环境比变量对比
前端·vite·nuxt.js
小帆聊前端5 小时前
Lodash 深度解读:前端数据处理的效率利器,从用法到原理全拆解
前端·javascript
Harriet嘉6 小时前
解决Chrome 140以上版本“此扩展程序不再受支持,因此已停用”问题 axure插件安装问题
前端·chrome
FuckPatience6 小时前
前端Vue 后端ASP.NET Core WebApi 本地调试交互过程
前端·vue.js·asp.net
Kingsdesigner6 小时前
从平面到“货架”:Illustrator与Substance Stager的包装设计可视化工作流
前端·平面·illustrator·设计师·substance 3d·平面设计·产品渲染