content script 是运行在网页上下文(DOM)中的 JavaScript 脚本,由浏览器扩展注入,能访问网页的 DOM 结构,但和网页自身的脚本、扩展的后台脚本(background)有严格的隔离。
核心特点:
- 能读写当前网页的 DOM,修改样式、文本、节点等;
- 不能访问网页自身的全局变量 / 函数(如网页里定义的
window.xxx); - 不能直接访问扩展的
chrome.api全部方法(仅能访问chrome.runtime、chrome.storage等少数 API); - 可通过
chrome.runtime.sendMessage与扩展的后台脚本通信。
先跑通最简单的一个示例
先在manifest.json里面加入
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"css": [],
"run_at": "document_end"
}
]
然后在content.js里面写上
// 核心:操作网页DOM(这是content script最基础的能力)
console.log("Content Script注入成功!");
// 修改网页标题
document.title = "被Content Script修改后的标题";
// 向网页插入一个元素
const div = document.createElement("div");
div.style.cssText = "position:fixed;top:0;left:0;background:red;color:white;padding:10px;z-index:9999;";
div.innerText = "这是Content Script插入的内容";
document.body.appendChild(div);
访问任意网站,就可以看到插入的效果了

manifest.json 配置
{
"manifest_version": 3,
"content_scripts": [
{
// 1. 匹配规则(核心,必选)
"matches": ["https://*.example.com/*"], // 匹配的URL(支持通配符),<all_urls>表示所有url
"exclude_matches": ["https://example.com/admin/*"], // 排除的URL
"include_globs": ["*example.com/*/test*"], // 更灵活的匹配(通配符更强)
"exclude_globs": ["*example.com/*/test/old*"], // 排除的glob规则
// 2. 注入的脚本/样式(核心)
"js": ["content.js", "utils.js"], // 注入的JS文件(按顺序执行)
"css": ["content.css"], // 注入的CSS文件(先于JS加载)
// 3. 注入时机
"run_at": "document_end", // 可选:document_start/document_end/document_idle(默认)
// 4. 注入上下文(关键)
"all_frames": false, // 是否注入到网页的iframe中(默认false,仅主框架)
"match_about_blank": false, // 是否注入到about:blank页面(需matches匹配)
"world": "ISOLATED", // 运行上下文:ISOLATED(隔离,默认)/MAIN(网页主上下文)
// 5. 权限相关(V3新增)
"match_as_incognito": false // 是否在无痕模式中注入(需扩展开启无痕权限)
}
]
}
- world:
ISOLATED(隔离上下文,安全)/MAIN(网页主上下文,可直接访问网页全局变量,但风险高); - all_frames:设为
true时,网页内所有 iframe 都会注入脚本(需注意 iframe 跨域问题);
核心能力(DOM / 样式 / 事件)
dom操作
content script 可完全操控网页 DOM,包括增删改查、事件监听
// 1. 查询/修改DOM
const title = document.querySelector("h1");
title.textContent = "被Content Script修改";
// 2. 新增DOM
const btn = document.createElement("button");
btn.innerText = "点击触发";
document.body.appendChild(btn);
// 3. 监听DOM事件(两种方式)
// 方式1:直接监听(推荐)
btn.addEventListener("click", () => alert("Content Script 触发点击"));
// 方式2:监听网页全局事件(如监听网页的点击)
document.addEventListener("click", (e) => {
console.log("点击的元素:", e.target);
});
// 4. 监听DOM变化(MutationObserver)
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
console.log("DOM变化:", mutation.addedNodes); // 监听新增节点(如广告)
});
});
// 监听body下的所有DOM变化
observer.observe(document.body, { childList: true, subtree: true });
如果是vue、react这种动态渲染的页面,不能直接在content.js里面使用document去选择元素,是取不到的,需要监听body的dom变化
console.log("Content Script注入成功!");
// 目标元素选择器
const targetSelector = ".my_site";
// 监听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_site) => {
console.log("找到目标元素:", my_site);
my_site.addEventListener("click", function (ev) {
ev.preventDefault();
console.log("我被点了");
});
});
可以在content script里面获取localStorage和httpOnly=false的cookie
console.log(localStorage)
console.log(document.cookie)
样式操作
- 静态注入:通过
manifest.json的css字段注入(优先级高于网页样式);
"content_scripts": [
{
"matches": ["https://www.fengfengzhidao.com/*"],
"js": ["content.js"],
"css": ["content.css"],
"run_at": "document_end"
}
]
一定要注意样式的优先级,多打开控制台调试

- 动态注入:在 JS 中创建
<style>标签注入:
// 动态注入样式
const style = document.createElement("style");
style.id = "custom-style";
style.textContent = `
body { background: #f5f5f5 !important; }
.ad { display: none !important; } /* 屏蔽广告 */
`;
document.head.appendChild(style);
// 动态修改样式
document.body.style.fontSize = "16px";
访问网页全局变量(突破隔离)
如果需要在content script里面获取网页的window,需要 "world": "MAIN"
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["content.js"],
"css": [],
"run_at": "document_end",
"world": "MAIN"
}
]
通信机制
content script 无法直接访问扩展后台 / 弹窗 / 侧边栏,需通过消息通信
与后台通信
content script发送消息
chrome.runtime.sendMessage({
type: "FROM_CONTENT", // 消息类型(方便 service 区分不同消息)
data: {
pageUrl: window.location.href, // 当前网页URL
message: "你好,我是content script!"
}
}).then(res => {
console.log("后台回复的消息", res)
}).catch(err => {
console.log("出错了", err)
})
后台接收消息并回复
// 监听来自 content script 的消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// 打印收到的消息和发送者信息
console.log("收到content script的消息:", message);
console.log("发送者信息:", sender); // sender包含标签页、frame等信息
// 验证消息类型(可选,用于区分不同业务的消息)
if (message.type === "FROM_CONTENT") {
// 构造回复数据
const reply = {
status: "success",
message: "你好,我是service worker!已收到你的消息",
receivedData: message.data // 回显收到的数据
};
// 发送回复(注意:如果是异步操作,需要返回true保持端口开放)
sendResponse(reply);
}
// 🌟 重要:如果 sendResponse 在异步操作中调用,必须返回 true
// 示例(异步场景):
// setTimeout(() => {
// sendResponse({ status: "success", message: "异步回复" });
// }, 1000);
// return true;
});
没有代码提示的问题
因为编辑器不认识chrome这个全局对象
如果是webstrorm的话,直接下载chrome的ts声明
npm i -D @types/chrome
与popup通信
和给service worker发消息类似,但是popup必须是点开状态
chrome.runtime.sendMessage({
type: "xxx", // 消息类型(方便SW区分不同消息)
data: {
pageUrl: window.location.href, // 当前网页URL
message: "你好,我是content script!"
}
}).then(res => {
console.log("popup 回复的消息", res)
}).catch(err => {
console.log("出错了", err)
})
popup接收消息并回复
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log('popup 收到消息:', message);
// 判断消息类型,处理不同逻辑
if (message.type === 'xxx') {
// 模拟处理逻辑(比如读取插件存储、调用API等)
const replyContent = `已收到你的消息「${message.data.message}」,时间:${message.data.time}`;
// 向Content script发送响应(同步回复)
sendResponse({
success: true,
reply: replyContent
});
}
})
chorme storage
storage有三钟,sync,local,session
只要用 chrome.storage,必须先在 manifest.json 中声明 storage 权限
"permissions":["storage"]
他们三种的区别
| 特性维度 | chrome.storage.sync |
chrome.storage.local |
chrome.storage.session |
|---|---|---|---|
| 存储位置 | 云端 + 本地(同步到 Chrome 账号) | 仅本地设备 | 内存(临时),插件进程结束即销毁 |
| 数据持久化 | 永久(除非手动删除 / 同步失败) | 永久(除非手动删除 / 清除浏览器数据) | 临时(插件关闭 / 浏览器重启后丢失) |
| 存储大小限制 | 约 100KB(总数据),单条 8KB | 约 5MB(可通过扩展权限提升) | 约 10MB(内存级,无严格硬限制) |
| 同步特性 | 多设备同步(登录同一 Chrome 账号) | 仅当前设备,不同步 | 仅当前浏览器会话,不同步 |
| 适用场景 | 跨设备共享的轻量配置(如主题偏好、账号设置) | 单设备的大量数据(如本地缓存、历史记录) | 临时状态(如弹窗临时数据、会话级缓存) |
| 读写性能 | 较慢(需云端同步) | 较快(纯本地) | 极快(内存操作) |
storage读、写、清空、删除
// 写入
await chrome.storage.sync.set({ theme: 'dark', autoSave: true });
// 读取
const { theme } = await chrome.storage.sync.get('theme');
// 清空
await chrome.storage.sync.clear()
// 单个键删除
await chrome.storage.[sync/local/session].remove('键名');
// 多个键批量删除
await chrome.storage.[sync/local/session].remove(['键名1', '键名2']);
监听storage变化
// 监听任意Storage的变化
chrome.storage.onChanged.addListener((changes, areaName) => {
console.log(`存储区域${areaName}发生变化:`, changes);
});