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
}
]
}| 参数 | 取值 | 说明 |
|---|---|---|
| matches | string | 匹配的URL(支持通配符),<all_urls>表示所有url |
| exclude_matches | string | 排除的URL |
| include_globs | string | 更灵活的匹配(通配符更强) |
| exclude_globs | string | 排除的glob规则 |
| js | string | 注入的JS文件(按顺序执行) |
| css | string | 注入的CSS文件(按顺序执行、先于JS加载) |
| run_at | document_start、document_end、document_idle(默认) | 注入时机 |
| all_frames | boolean | 是否注入到网页的iframe中(默认false,仅主框架) |
| match_about_blank | boolean | 是否注入到about:blank页面(需matches匹配) |
| world | ISOLATED、MAIN | 运行上下文 ISOLATED(隔离,默认) MAIN(网页主上下文,可直接访问网页全局变量,但风险高) |
| match_as_incognito | boolean | 是否在无痕模式中注入(需扩展开启无痕权限) |
核心能力
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里面获取localStorage和httpOnly=false的cookie
样式操作
- 静态注入:通过
manifest.json的css字段注入(优先级高于网页样式) - 动态注入:在
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有三钟,sync,local,session。只要用 chrome.storage,必须先在 manifest.json 中声明 storage 权限
json
{
"permissions": [
"storage"
]
}区别
| 特性维度 | chrome.storage.sync | chrome.storage.local | chrome.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();