Chrome Extension Script World(ISOLATED / MAIN)原理与适用场景
适用于 Chrome Manifest V3(MV3)扩展开发
一、概述
Chrome 为了保证网页与扩展之间的安全隔离,引入了 Execution World(执行世界) 的概念。
Content Script 并不是直接运行在网页 JavaScript 环境中,而是运行在不同的 World(世界)。
目前主要有:
| World | 含义 | 是否默认 |
|---|---|---|
| ISOLATED | 隔离环境(Extension World) | ✅ 默认 |
| MAIN | 页面环境(Page World) | ❌ 需要主动指定 |
Manifest V3 中可以通过:
json
{
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"],
"world": "MAIN"
}]
}
或者
javascript
chrome.scripting.executeScript({
target: { tabId },
files: ["inject.js"],
world: "MAIN"
});
指定执行环境。
二、Chrome 的执行环境
浏览器打开一个网页以后,大致存在如下环境:
Chrome Browser
│
├── Web Page(网页)
│ │
│ ├── Window
│ ├── Document
│ ├── 页面 JS
│ └── 第三方 JS
│
├── Extension World(ISOLATED)
│ │
│ ├── Content Script
│ ├── chrome.runtime
│ └── chrome.storage
│
└── Service Worker
可以看到:
网页 JS
和
Content Script
其实并不在同一个 JavaScript 环境。
三、ISOLATED(默认)
什么是 ISOLATED
ISOLATED 就是:
Chrome 给每个扩展创建一个独立 JavaScript 运行环境。
它共享:
- DOM
- Document
- 页面元素
但是:
不共享 JavaScript 变量。
举个例子
网页:
javascript
window.user = {
name: "Tom"
};
Content Script:
javascript
console.log(window.user);
输出:
undefined
很多新人第一次都会觉得奇怪。
原因:
你的 window 并不是网页那个 window。
它只是:
DOM 相同
JS Context 不同
为什么还能操作 DOM?
例如:
javascript
document.body.style.background = "red";
可以成功。
因为:
DOM 是共享的。
网页
│
├───────────── DOM ─────────────┐
│ │
ISOLATED World MAIN World
两个 JS Context
操作的是同一棵 DOM Tree。
JS Context 隔离
举例:
网页:
javascript
let count = 100;
Content Script:
javascript
console.log(count);
结果:
ReferenceError
再比如:
网页:
javascript
window.fetch = function(){
console.log("page");
}
Content Script:
javascript
fetch("...");
调用的是:
浏览器原始 fetch。
不是页面修改后的 fetch。
为什么这么设计?
为了防止网页:
修改扩展。
例如:
恶意网页:
javascript
chrome.runtime.sendMessage = function(){
stealData();
}
如果没有隔离:
扩展就危险了。
Chrome 所以:
把 JS Context 完全隔离。
ISOLATED 可以访问哪些 API?
可以。
例如:
javascript
chrome.runtime.sendMessage(...)
可以。
chrome.storage
chrome.runtime
chrome.i18n
chrome.tabs(部分)
都是扩展能力。
这是 MAIN 没有的。
ISOLATED 优点
✅ 安全
不会污染网页。
不会被网页污染。
可以直接使用:
chrome.runtime
chrome.storage
chrome.cookies
message
适合:
90% 插件开发。
ISOLATED 缺点
不能:
window.React
window.Vue
window.jQuery
webpackChunk
__NEXT_DATA__
这些页面变量。
也不能 Hook:
fetch
XMLHttpRequest
history.pushState
因为不是一个 JS Context。
四、MAIN
什么是 MAIN
MAIN:
就是:
让 Content Script
真正运行到:
网页 JS 环境
也就是:
和网页自己的 JS
属于同一个 Context。
执行结构:
网页 JS
│
├──── inject.js(MAIN)
此时:
window
this
globalThis
完全一致。
举例
网页:
javascript
window.user = {
name:"Tom"
}
MAIN:
javascript
console.log(window.user);
输出:
{name:"Tom"}
可以访问。
Hook fetch
MAIN:
javascript
const oldFetch = window.fetch;
window.fetch = function(...args){
console.log(args);
return oldFetch.apply(this,args);
}
以后:
页面:
javascript
fetch(...)
都会经过:
你的 Hook。
ISOLATED 做不到。
Hook XHR
javascript
const open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(){
console.log(arguments);
return open.apply(this,arguments);
}
页面所有 Ajax
都会经过。
Hook History
例如:
React:
Vue:
SPA:
javascript
history.pushState = ...
只能:
MAIN。
修改页面变量
例如:
javascript
window.token = "abc";
MAIN:
可以。
ISOLATED:
不可以。
调用 React
例如:
javascript
window.React
MAIN:
可以。
调用 Vue
例如:
javascript
window.__VUE__
MAIN:
可以。
MAIN 的限制
MAIN:
没有:
chrome.runtime
例如:
javascript
chrome.runtime.sendMessage()
报错:
chrome is undefined
因为:
MAIN
已经变成:
网页 JS。
不是扩展环境。
因此:
如果:
MAIN
想调用:
Extension API。
需要:
postMessage
或者
CustomEvent
通知:
ISOLATED。
五、MAIN 与 ISOLATED 通信
最常见:
MAIN
│
postMessage
│
ISOLATED
│
chrome.runtime.sendMessage
│
Service Worker
例如:
MAIN:
javascript
window.postMessage({
type: "GET_TOKEN"
});
ISOLATED:
javascript
window.addEventListener("message", (e) => {
if (e.data.type === "GET_TOKEN") {
chrome.runtime.sendMessage({
type: "GET_TOKEN"
});
}
});
六、两者对比
| 对比项 | ISOLATED | MAIN |
|---|---|---|
| 默认环境 | ✅ 是 | ❌ 否 |
| JS Context | 独立 | 与页面相同 |
| DOM | 共享 | 共享 |
| 页面变量 | ❌ | ✅ |
| 修改页面 JS | ❌ | ✅ |
| Hook fetch | ❌ | ✅ |
| Hook XHR | ❌ | ✅ |
| Hook History | ❌ | ✅ |
| React/Vue 全局变量 | ❌ | ✅ |
| chrome.runtime | ✅ | ❌ |
| chrome.storage | ✅ | ❌ |
| 安全性 | 高 | 较低 |
| 推荐程度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
七、典型应用场景
场景一:页面美化(推荐 ISOLATED)
例如:
- 修改 CSS
- 添加按钮
- 添加侧边栏
- 阅读模式
- 广告屏蔽
原因:
只需要操作 DOM,不需要访问页面 JS。
场景二:网页自动化(推荐 ISOLATED)
例如:
javascript
document.querySelector(...)
button.click()
适用于:
- 自动签到
- 自动填写表单
- 自动点击按钮
场景三:抓取页面数据(推荐 ISOLATED)
例如:
javascript
document.querySelectorAll(...)
读取:
- 文本
- 图片
- 表格
- DOM 信息
场景四:Hook 网络请求(推荐 MAIN)
例如:
监控:
fetch()
XMLHttpRequest
WebSocket
适用于:
- 网络调试
- 数据采集
- API 分析
场景五:读取前端框架状态(推荐 MAIN)
例如:
读取:
javascript
window.React
window.__NEXT_DATA__
window.__INITIAL_STATE__
window.__NUXT__
适用于:
- React DevTools 类工具
- Vue 调试工具
- 数据分析插件
场景六:修改网页运行逻辑(推荐 MAIN)
例如:
重写:
javascript
window.fetch
history.pushState
console.log
alert
适用于:
- APM
- 性能监控
- 调试工具
- 自动测试
八、最佳实践
推荐原则
能用 ISOLATED,就不要用 MAIN。
原因:
- 更安全
- 能直接访问扩展 API
- 不容易受到网页脚本影响
- 更符合 Chrome 扩展的设计理念
推荐架构
对于复杂插件,推荐采用双 World 架构:
Chrome Extension
Service Worker
▲
│ chrome.runtime
│
┌─────────┴─────────┐
│ │
ISOLATED World MAIN World
│ │
│ postMessage │
└─────────┬─────────┘
│
Web Page
职责划分如下:
MAIN
负责:
- Hook fetch
- Hook XHR
- Hook WebSocket
- 读取页面变量
- 修改页面 JavaScript 行为
ISOLATED
负责:
- DOM 操作
- 与 Service Worker 通信
- 使用
chrome.*API - 数据存储
- 权限管理
- 消息转发
Service Worker
负责:
- 网络请求
- 插件后台逻辑
- 持久化存储
- 标签页管理
- 生命周期管理
这种分层方式是目前多数大型 Chrome 扩展(如开发者工具、网络抓包、自动化工具等)常见的实现模式,兼顾了安全性、可维护性和扩展能力。