Skip to content

Chrome Plugin 浏览器插件开发(拓展程序)

项目地址:BrowserExtension By Github

Hello World

初始化一个项目:添加manifest.json文件,再添加一个popup.html文件,这个是插件的入口文件,也就是点击插件图标的时候,就会展示到这个文件

json
{
  "manifest_version": 3,
  "name": "modify-plugin",
  "version": "1.0.0",
  "description": "modify chrome plugin",
  "author": "modify",
  "action": {
    "default_icon": {
      "16": "plugin.png",
      "32": "plugin.png",
      "48": "plugin.png",
      "64": "plugin.png",
      "128": "plugin.png"
    },
    "default_popup": "popup.html"
  },
  "icons": {
    "16": "plugin.png",
    "32": "plugin.png",
    "48": "plugin.png",
    "64": "plugin.png",
    "128": "plugin.png"
  }
}

在chrome当中,打开chrome://extensions/,打开开发者模式,加载未打包的扩展程序,选择项目目录,就会自动加载

Content Scripts

content script 是运行在网页上下文(DOM)中的 js 脚本,由浏览器扩展注入,能访问网页的 DOM 结构,但和网页自身的脚本、扩展的后台脚本(background)有严格的隔离

特点:

  • 能读写当前网页的 DOM,修改样式、文本、节点等
  • 不能访问网页自身的全局变量 / 函数(如网页里定义的 window.xxx)
  • 不能直接访问扩展的chrome.api全部方法(仅能访问 chrome.runtime、chrome.storage 等少数 API)
  • 可通过chrome.runtime.sendMessage与扩展的后台脚本通信

manifest.json 配置

json
{
  "content_scripts": [
    {
      "matches": [
        "https://*.example.com/*"
      ],
      "exclude_matches": [
        "https://example.com/admin/*"
      ],
      "include_globs": [
        "*example.com/*/test*"
      ],
      "exclude_globs": [
        "*example.com/*/test/old*"
      ],
      "js": [
        "content.js",
        "utils.js"
      ],
      "css": [
        "content.css"
      ],
      "run_at": "document_end",
      "all_frames": false,
      "match_about_blank": false,
      "world": "ISOLATED",
      "match_as_incognito": false
    }
  ]
}
参数取值说明
matchesstring匹配的URL(支持通配符),<all_urls>表示所有url
exclude_matchesstring排除的URL
include_globsstring更灵活的匹配(通配符更强)
exclude_globsstring排除的glob规则
jsstring注入的JS文件(按顺序执行)
cssstring注入的CSS文件(按顺序执行、先于JS加载)
run_atdocument_start、document_end、document_idle(默认)注入时机
all_framesboolean是否注入到网页的iframe中(默认false,仅主框架)
match_about_blankboolean是否注入到about:blank页面(需matches匹配)
worldISOLATED、MAIN运行上下文
ISOLATED(隔离,默认)
MAIN(网页主上下文,可直接访问网页全局变量,但风险高)
match_as_incognitoboolean是否在无痕模式中注入(需扩展开启无痕权限)

核心能力

DOM操作

可完全操控网页DOM,包括添加、删除、修改节点、属性、文本内容,其中监听DOM变化可以使用MutationObserver

js
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    console.log("DOM变化:", mutation.addedNodes); // 监听新增节点(如广告)
  });
});
observer.observe(document.body, {childList: true, subtree: true});

如果是vue、react这种动态渲染的页面,不能直接在content.js里面使用document去选择元素,是取不到的,需要监听body的dom变化

js
const leftTerminal = document.querySelector('#leftTerminal');
console.log('在vue框架,直接取leftTerminal:', leftTerminal);

// 目标元素选择器
const targetSelector = '#leftTerminal';

// 监听DOM变化的函数
function waitForElement(selector, callback) {
  // 先检查元素是否已存在
  const element = document.querySelector(selector);
  if (element) {
    callback(element);
    return;
  }

  // 创建DOM观察者
  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      // 检查新增的节点中是否包含目标元素
      const addedNodes = Array.from(mutation.addedNodes);
      for (const node of addedNodes) {
        // 节点是元素,且自身/子节点包含目标元素
        if (node.nodeType === 1) {
          const target = node.matches(selector) ? node : node.querySelector(selector);
          if (target) {
            observer.disconnect(); // 找到元素后停止监听
            callback(target);
            return;
          }
        }
      }
    });
  });

  // 配置监听规则:监听body下所有子节点变化
  observer.observe(document.body, {
    childList: true, // 监听子节点添加/删除
    subtree: true,   // 监听所有后代节点
    attributes: false,
    characterData: false
  });
}

// 调用函数,等待元素出现后绑定事件
waitForElement(targetSelector, (my_node) => {
  console.log('找到目标元素:', my_node);
  my_node.addEventListener('click', function (e) {
    e.preventDefault();
    console.log('我被点了');
  });
});

还可以在content script里面获取localStoragehttpOnly=falsecookie

样式操作

  • 静态注入:通过manifest.jsoncss字段注入(优先级高于网页样式)
  • 动态注入:在js中创建<style>标签注入

访问网页全局变量

通过配置"world": "MAIN",即可访问网页全局变量

通信机制

manifest.json中配置background字段,用来指定后台脚本

json
{
  "background": {
    "service_worker": "background/background.js"
  }
}

与后台通信

js
chrome.runtime.sendMessage({
  type: 'TO_BACKGROUND',
  data: {
    pageUrl: window.location.href,
    message: '你好,我是content script TO_BACKGROUND!'
  }
}).then(res => {
  console.log('TO_BACKGROUND后台回复的消息', res);
}).catch(err => {
  console.log('TO_BACKGROUND出错了', err);
});
js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  // 打印收到的消息和发送者信息
  console.log("收到content script的消息:", message);
  console.log("发送者信息:", sender); // sender包含标签页、frame等信息

  // 验证消息类型(可选,用于区分不同业务的消息)
  if (message.type === "TO_BACKGROUND") {
    // 构造回复数据
    const reply = {
      status: "success",
      message: "你好,我是service worker!已收到你的消息",
      receivedData: message.data // 回显收到的数据
    };

    // 发送回复(注意:如果是异步操作,需要返回true保持端口开放)
    sendResponse(reply);
  }

  // 🌟 重要:如果 sendResponse 在异步操作中调用,必须返回 true
  // setTimeout(() => {
  //   sendResponse({ status: "success", message: "异步回复" });
  // }, 1000);
  // return true;
});

注:代码提示,因为编辑器不认识chrome这个全局对象,安装npm i -D @types/chrome

与popup通信

和与background通信一样,但是popup必须是点开状态

js
const head = document.querySelector('.head_wrapper');
console.log('head:', head);

head.addEventListener('click', function (e) {
  e.preventDefault();
  console.log('我被点了');

  chrome.runtime.sendMessage({
    type: 'TO_POPUP',
    data: {
      pageUrl: window.location.href,
      message: '你好,我是content script TO_POPUP!',
      time: new Date().toLocaleString()
    }
  }).then(res => {
    console.log('TO_POPUP后台回复的消息', res);
  }).catch(err => {
    console.log('TO_POPUP出错了', err);
  });
});
js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  console.log('popup 收到消息:', message);

  // 判断消息类型,处理不同逻辑
  if (message.type === 'TO_POPUP') {
    // 模拟处理逻辑(比如读取插件存储、调用API等)
    const replyContent = `已收到你的消息「${message.data.message}」,时间:${message.data.time}`;

    // 向Content script发送响应(同步回复)
    sendResponse({
      success: true, reply: replyContent
    });
  }
});

chrome storage

storage有三钟,synclocalsession。只要用 chrome.storage,必须先在 manifest.json 中声明 storage 权限

json
{
  "permissions": [
    "storage"
  ]
}

区别

特性维度chrome.storage.syncchrome.storage.localchrome.storage.session
存储位置云端 + 本地(同步到 Chrome 账号)仅本地设备内存(临时),插件进程结束即销毁
数据持久化永久(除非手动删除 / 同步失败)永久(除非手动删除 / 清除浏览器数据)临时(插件关闭 / 浏览器重启后丢失)
存储大小限制约 100KB(总数据),单条 8KB约 5MB(可通过扩展权限提升)约 10MB(内存级,无严格硬限制)
同步特性多设备同步(登录同一 Chrome 账号)仅当前设备,不同步仅当前浏览器会话,不同步
适用场景跨设备共享的轻量配置(如主题偏好、账号设置)单设备的大量数据(如本地缓存、历史记录)临时状态(如弹窗临时数据、会话级缓存)
读写性能较慢(需云端同步)较快(纯本地)极快(内存操作)

读写操作【get、set、remove】

js
const storageChange = async () => {
  await chrome.storage.local.set({'name': 'modify', 'age': 18});
  const {name} = await chrome.storage.local.get('name');
  console.log('name:', name);

  // await chrome.storage.sync.clear();

  await chrome.storage.local.remove('name');

  await chrome.storage.local.remove(['name', 'age']);
};

chrome.storage.onChanged.addListener((changes, areaName) => {
  console.log(`存储区域${areaName}发生变化:`, changes);
});

storageChange();

By Modify.