ScriptCat架构深度解析:5大执行上下文与消息通信机制揭秘
【免费下载链接】scriptcatScriptCat, a browser extension that can execute userscript; 脚本猫,一个可以执行用户脚本的浏览器扩展项目地址: https://gitcode.com/gh_mirrors/sc/scriptcat
ScriptCat作为一款现代化的浏览器用户脚本管理器,不仅完全兼容Tampermonkey生态,更通过创新的多进程架构和消息通信机制,实现了脚本执行的安全隔离与高性能通信。本文将从技术架构角度深入剖析ScriptCat的核心设计原理,揭示其如何通过五大执行上下文和统一消息层构建出一个分布式脚本执行系统。
传统脚本管理器瓶颈→多进程隔离架构
传统用户脚本管理器面临的核心挑战在于脚本执行的安全性与性能平衡。单一执行环境下的脚本隔离不足容易导致脚本间的相互干扰,而过度隔离又会带来通信效率问题。ScriptCat采用Manifest V3规范下的多进程架构,将扩展功能拆分为五个独立的执行上下文,每个上下文运行在隔离的JavaScript环境中,通过精心设计的消息通道进行通信。
五大执行上下文的技术分工
| 上下文 | 入口文件 | 执行环境能力 | 核心职责 |
|---|---|---|---|
| Service Worker | src/service_worker.ts | 无DOM环境,拥有chrome.*特权API | 中央枢纽:脚本CRUD、权限验证、资源缓存、路由分发 |
| Content Script | src/content.ts | 隔离的内容脚本世界 | 桥接Service Worker与页面脚本,提供安全隔离层 |
| Inject Script | src/inject.ts | 页面主世界,可访问unsafeWindow | 执行用户脚本,直接与页面DOM交互 |
| Offscreen Document | src/offscreen.ts | 具备DOM能力的后台页面 | 处理DOM相关操作:Blobs、剪贴板、DOM解析、本地存储 |
| Sandbox (iframe) | src/sandbox.ts | 沙盒化的iframe环境 | 安全执行后台/定时脚本,支持cron调度 |
跨进程通信难题→统一消息层抽象
在多进程架构中,高效的跨上下文通信是系统设计的核心挑战。ScriptCat通过packages/message模块抽象了浏览器原生通信API,提供了两种通信模式:请求/回复RPC和发布/订阅广播。
传输层抽象设计
ScriptCat定义了多种传输实现,每种针对特定的通信场景进行优化:
// Service Worker端引导示例 const message = new ExtensionMessage(true); // backgroundPrimary = true const server = new Server("serviceWorker", message); // RPC监听器,action前缀"serviceWorker/" const messageQueue = new MessageQueue(); // pub/sub广播总线传输层实现矩阵:
| 类名 | 连接场景 | 底层API |
|---|---|---|
ExtensionMessage | SW ↔ Content/Inject/Offscreen | chrome.runtime.sendMessage/onConnect |
CustomEventMessage | Content ↔ Inject | DOMCustomEvent分发 |
WindowMessage | Offscreen ↔ Sandbox | window.postMessage |
ServiceWorkerMessageSend | SW → Offscreen (Chrome) | clients.matchAll()+postMessage |
MessageQueue | 广播状态变更 | chrome.runtime.sendMessage+EventEmitter3 |
RPC与Pub/Sub的双重通信模式
请求/回复RPC模式适用于需要明确响应的操作,如获取脚本值、执行特权API调用。每个RPC调用通过唯一的action字符串标识(如"script/install"),Server实例根据action前缀路由到相应的处理程序。
// SW端注册 class ValueService { init(/* … */) { this.group.on("getScriptValue", this.getScriptValue.bind(this)); this.group.on("setScriptValues", this.setScriptValues.bind(this)); } } // 调用端 const value = await client.do("value/getScriptValue", { uuid });发布/订阅广播模式用于状态变更通知,任何关心特定主题的上下文都可以订阅变更事件。这种方式避免了频繁的请求/回复开销,特别适合数据一致性维护。
// 订阅脚本删除事件 this.mq.subscribe<TDeleteScript[]>("deleteScripts", async (data) => { for (const { storageName } of data) { const stillUsed = await this.scriptDAO.find((_, s) => getStorageName(s) === storageName); if (stillUsed.length === 0) await this.valueDAO.delete(storageName); } });GM API系统设计→权限验证与跨域通信
ScriptCat的GM_*API系统是其兼容性的核心,每个API调用实际上是一个小型客户端,通过跨上下文转发到特权处理程序,然后流式返回结果。
API注册与权限验证机制
ScriptCat使用装饰器模式注册API,通过@GMContext.API装饰器将方法映射到对应的@grant权限声明:
// API注册装饰器实现 class GMContext { static API(param: ApiParam = {}) { return (target, propertyName, descriptor) => { const follow = param.follow ?? propertyName; // 真实的@grant权限 GMContextApiSet(follow, propertyName, descriptor.value, param); if (param.alias) GMContextApiSet(param.alias, param.alias, descriptor.value, param); // GM_x ↔ GM.x }; } }跨浏览器平台兼容性处理
ScriptCat在Chrome和Firefox上的实现存在重要差异,主要体现在Offscreen API的处理上:
- Chrome:使用Offscreen API创建隐藏文档,通过
ServiceWorkerMessageSend进行通信 - Firefox:MV3事件页面本身就具备DOM能力,直接扮演Offscreen角色
// 跨平台兼容性处理 const hasOffscreenDocument = typeof chrome.offscreen?.createDocument === "function"; if (hasOffscreenDocument) { const offscreen = new ServiceWorkerMessageSend(); // Chrome: 与真实offscreen文档通信 new ServiceWorkerManager(server, messageQueue, offscreen).initManager(); setupOffscreenDocument(); // chrome.offscreen.createDocument(...) } else { const offscreen = new EventPageOffscreenManager(message); // Firefox MV3: 事件页面即DOM环境 new ServiceWorkerManager(server, messageQueue, offscreen).initManager(); }数据持久化策略→分层存储架构
ScriptCat采用分层数据存储架构,针对不同数据特性和访问模式设计了多种存储后端:
存储后端选择矩阵
| 存储类型 | 适用场景 | 技术实现 |
|---|---|---|
Repo<T> | 复杂实体关系管理 | 基于IndexedDB的对象存储 |
DAO<T> | 简单键值对存储 | 基于chrome.storage的键值存储 |
OPFSRepo | 大文件/二进制数据 | 基于Origin Private File System的文件存储 |
数据一致性保障机制
通过MessageQueue的发布/订阅机制,ScriptCat确保了跨上下文的数据一致性。当脚本数据发生变化时,Service Worker会广播变更事件,所有相关上下文同步更新本地缓存:
// 数据变更广播示例 this.mq.publish("scriptUpdated", { scriptId: "uuid", changes: { enabled: false, lastModified: Date.now() } }); // 其他上下文订阅更新 this.mq.subscribe<ScriptUpdate>("scriptUpdated", (update) => { this.localCache.update(update.scriptId, update.changes); });测试策略→模拟消息总线的单元测试
ScriptCat的架构设计支持全面的单元测试,通过MockMessage实现内存中的消息总线模拟,无需浏览器环境即可测试跨上下文通信逻辑:
// 使用MockMessage进行单元测试 const mockMessage = new MockMessage(); const server = new Server("test", mockMessage); const client = new Client(mockMessage); // 注册处理程序 server.group("test").on("echo", (params) => ({ echo: params.message })); // 测试RPC调用 const result = await client.do("test/echo", { message: "Hello" }); expect(result.echo).toBe("Hello");测试分层策略
| 测试类型 | 工具栈 | 测试重点 |
|---|---|---|
| 单元测试 | Vitest + happy-dom | 服务逻辑、消息处理、数据操作 |
| 集成测试 | 真实MessageQueue + Mock DAO | 跨服务协作、状态同步 |
| E2E测试 | Playwright + 真实Chromium | 完整用户流程、浏览器API交互 |
扩展开发模式→基于现有架构点的增量演进
ScriptCat的架构设计支持模块化扩展,开发者可以基于现有的扩展点进行功能增强,无需重新发明轮子:
扩展模式决策树
- 新增跨上下文消息:选择现有服务的Group,添加
this.group.on("newAction", handler) - 新增广播事件:使用
this.mq.publish("newTopic", payload)发布状态变更 - 新增持久化实体:根据数据特性选择匹配的存储后端(Repo/DAO/OPFSRepo)
- 新增GM API:使用
@GMContext.API装饰器注册,遵循权限验证流程 - 新增服务:根据服务类型选择对应的上下文和依赖注入模式
架构演进原则
ScriptCat遵循"修复根本原因"的工程原则,避免使用as any类型断言和错误吞没。系统倾向于直接替换而非适配器三明治模式,保持每个模块的职责单一和接口清晰。这种设计使得系统在保持向后兼容性的同时,能够持续演进和优化。
通过深入理解ScriptCat的五大执行上下文架构、统一消息层抽象、GM API系统设计和分层存储策略,开发者可以更好地利用这一强大的脚本管理平台,构建安全、高效、可扩展的用户脚本解决方案。该架构不仅解决了传统脚本管理器的性能和安全瓶颈,还为未来的功能扩展奠定了坚实的技术基础。
【免费下载链接】scriptcatScriptCat, a browser extension that can execute userscript; 脚本猫,一个可以执行用户脚本的浏览器扩展项目地址: https://gitcode.com/gh_mirrors/sc/scriptcat
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考