一个 Chrome 窗口跑多个隔离工作台,技术上是怎么做到的
单窗口隔离靠三层叠加:会话与存储层、网络路由层、生命周期管理层,而不是内核级虚拟化。它不改内核指纹。
为什么“一个浏览器开多个窗口”不等于多个账号同时登录?
先拆掉一个直觉:窗口不是隔离单位。同一个 Profile 里开十个窗口,它们共用同一份 Cookie 库、同一份 localStorage 和同一个 HTTP 缓存,站点看到的仍是同一个会话;隐身窗口确实有独立存储,但只有一个,关窗即弃,没法给十个账号各配一个。真正的隔离单位是“会话容器”:一份独立的 Cookie 集合与存储命名空间,加一条稳定的归属映射。Chrome 没有现成的容器 API(Firefox 的 contextualIdentities 才有),所以扩展只能自己在会话层造一个——先记住“哪个标签页属于哪个容器”,再在请求与页面两个面上按这张映射分流。CrispLogin 这类扩展就是按这个思路做的:容器是扩展自己维护的归属,不是 Chrome 的原生概念。所以隔离的粒度是“会话”,不是“进程”。
第一层:Cookie 为什么不串?
不串号靠的是“虚拟 Cookie 罐 + 按标签页改写请求头”。每个容器在扩展里有一份自己的 Cookie 记录;请求发出前,declarativeNetRequest 的 session rule 用 condition.tabIds 匹配标签页,把 cookie 请求头整体 set 成这个容器的值;响应回来时,再用 webRequest 观察 Set-Cookie 写回对应的罐。罐里的 Cookie 得按 path 长度与创建时间自行排序序列化,拼出来的头才和浏览器原生的一致。
容易漏的是生命周期:tabIds 只对 session rule 有效,而 session rule 在浏览器退出、扩展更新时会被清空,所以每次启动都要重装规则,漏了就表现为“重启后所有工作台变成一个号”。页面存储是另一个面——document.cookie 读的是真实 Cookie 库,localStorage 也只有一个命名空间,得靠 MAIN world 脚本按容器代理读写。这层是应用层隔离:分得开同站点的两个账号,但不改内核指纹。
第二层:代理怎么跟着工作台走?
Chrome 没有“按标签页设代理”的 API。chrome.proxy.settings 的作用域是整个 Profile,PAC 的 FindProxyForURL(url, host) 拿不到 tabId;declarativeNetRequest 能改请求头、能重定向,却没有“选上游代理”这个动作。所以扩展能做的是维护一张“工作台 → 出口”的映射,并在前台标签页变化时改写这份全局配置。
PAC 可以按域名分流,但依据是 URL 不是标签页——同一个站点在两个工作台里,PAC 给不出两个答案。因此“十个工作台各跑一条隧道”在扩展里不成立,能保证的是“此刻前台是谁,出口就是谁的”。这也是切换工作台时会看到一次重连的原因:代理配置一改,已建立的连接要重建。边界:这层换的是出口 IP 与请求路径,TCP/TLS 指纹(JA3/JA4)不变;WebRTC 可以用 chrome.privacy.network.webRTCIPHandlingPolicy 关掉非代理 UDP,但那是浏览器级策略,不按工作台生效。
第三层:关掉标签页、重启浏览器,绑定还在吗?
MV3 的后台是 Service Worker:30 秒没有事件就被终止,模块级变量和闭包里的 Map 一起消失。所以“哪个标签页属于哪个工作台”不能存内存,必须落到 chrome.storage.session——它在扩展加载期间驻留内存,SW 被回收再唤醒后仍在,浏览器重启即清空。注意它默认不对内容脚本开放,要让页面侧读到归属,得显式调一次 setAccessLevel。
这就分出两种数据:运行时绑定放 session,工作台列表、代理配置、快照索引这些跨重启要用的放 storage.local。因果链很直接:因为绑定落在 session 而不是变量里,唤醒后扩展仍知道每个标签页的归属,不会把两个工作台的 Cookie 灌到同一页面;只存内存的话,SW 一睡,下一次请求就没有归属可查,表现是分组重复、快照灌错容器。
第四层:站点快照存的是什么,恢复的又是什么?
快照解决的是另一个问题:登录态的可搬运,不是运行时的隔离。它排在第四层,但不属于隔离本身。它把某个站点在一个容器里的 Cookie 与该站点的存储序列化存下来,恢复时灌回这个容器的命名空间。两者正交:隔离保证两个工作台同时存在时互不干扰,快照保证不用重新登录就能回到某个状态。快照的粒度是站点而不是整个浏览器:同一容器里两个站点,是两条互不相干的记录。
所以快照必须从属于容器——把 A 的登录态灌进 B,等于把两个号混成一个。也要写明它不做什么:恢复的是应用层会话状态,不恢复内核级指纹,也不承诺绕过平台的风控与设备校验。用法见【内链:站点快照的实际用法】。
和“开多个 Profile”比,单窗口多工作台差在哪?
Chrome 的多 Profile 是存储根目录级的物理切割:每个 Profile 一套独立的 Cookie 库、存储、扩展与缓存,隔离彻底。代价落在四个维度上——内存,每个 Profile 有自己的网络上下文与渲染进程,开三个就近似三份常驻开销;进程,进程树各自独立;窗口,一个 Profile 一个窗口,切换等于换窗口;指纹,Profile 之间共享同一份内核与硬件,UA、Canvas、字体默认完全一致。另一笔隐性成本是数据分裂:书签、扩展、历史默认不共享,三个以上就开始各自为政。
扩展方案反过来:同一个 Profile、同一个窗口,共享进程与内核,换来单窗口并行和近乎为零的增量开销,代价是碰不到内核指纹层。所以两者不是同一件事的两个档位,而是隔离粒度的两种取法。展开见【内链:Chrome 多 Profile 与扩展隔离的对比】。
能力边界:哪些层扩展能覆盖,哪些覆盖不到?
把上面的边界收成一张表。读法是“看平台在查什么”:查会话与出口,看中间那一列;查指纹一致性,看右边那一列。需要强调:扩展做的是会话层隔离,和指纹浏览器不是同等级别的隔离,把它当成“更轻的指纹浏览器”会误判风险。
| 层 | 扩展能覆盖 | 覆盖不到,或代价在哪 |
|---|---|---|
| Cookie 与登录态 | 按容器分开,可序列化成快照 | 内核不认容器,靠请求头改写;规则丢了就串号 |
| 页面存储 | localStorage、IndexedDB 可按容器加前缀或代理 | Service Worker、SharedWorker 等独立上下文要单独覆盖 |
| 出口 IP | 按工作台绑定,切前台即切出口 | 同一时刻只有一个前台出口,不能多隧道并发 |
| WebRTC | 可关闭非代理 UDP(浏览器级策略) | 不按工作台生效,媒体路径仍可能绕开代理 |
| TLS 指纹 | 无 | JA3/JA4 由内核与网络栈决定 |
| Canvas / WebGL / Audio | 无 | 渲染发生在内核,扩展没有改写接口 |
| 字体枚举 | 无 | 只能在页面 JS 层打补丁,可被检测 |
| HTTP 缓存 | 无 | 缓存是 Profile 级,容器之间共享 |
这张表也说明了能力上限在哪:扩展能替代的是“为了分开登录而买客户端”的动机,不是客户端本身。两条路线怎么选,是另一个问题,逐项对照见【内链:扩展与指纹浏览器的路线对比】。
常见问题
扩展做的 Cookie 隔离和 Chrome 原生的 Partitioned Cookie 是一回事吗?
不是。Partitioned Cookie 按顶层站点分键,同一站点在不同顶层页面下各有一份;cookies API 里的 partitionKey 只是读写这类分区 Cookie 用的键,键只有 topLevelSite,不能拿它另开分区。工作台的罐按容器分,同一站点可以有任意多份,且由扩展自己记账。前者是浏览器的隐私分区,后者是扩展的会话抽象。
关掉标签页后工作台状态还在吗?
分三种。标签页本身没了,绑定关系随之失效;工作台的定义(名称、代理、快照索引)这类跨重启要用的数据一般落在 storage.local;登录态只有在存过快照时才可恢复,否则要重新登录。storage.session 里的东西浏览器一关就清空,别把它当持久层。
单窗口方案会影响浏览器性能吗?
增量主要来自标签页本身,而不是隔离机制。扩展不启第二个浏览器、不复制内核,开销是规则匹配与请求头改写,属于每请求一次的常数级工作。真正的成本在标签页数量与页面本身:开二十个工作台,卡的是渲染,不是隔离层。
两个工作台能同时走不同的出口 IP 吗?
不能。代理配置是 Profile 级的,扩展只能在标签页切换时改写它,所以同一时刻只有前台工作台走自己的出口;后台工作台要么被规则挡住,要么直连。要真正的多隧道并发,得用带独立网络栈的客户端方案。
资料来源与最后更新
本文涉及 API 行为的部分以官方文档为准;扩展能力边界随 Chrome 版本变化,落地前请按当期文档复核。
- Chrome for Developers,《chrome.declarativeNetRequest》——session rules 的生命周期、condition.tabIds 的适用范围、modifyHeaders 对 cookie 请求头的改写能力: developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest
- Chrome for Developers,《chrome.storage》——session 区驻留内存、浏览器重启即清除、默认不对内容脚本开放: developer.chrome.com/docs/extensions/reference/api/storage
- Chrome for Developers,《chrome.cookies》——partitionKey 与 Partitioned Cookie(CHIPS)的键值语义: developer.chrome.com/docs/extensions/reference/api/cookies
- Chrome for Developers,《chrome.proxy》——代理设置作用于整个浏览器配置,PAC 只拿到 URL 与 host: developer.chrome.com/docs/extensions/reference/api/proxy
- Chrome for Developers,《The extension service worker lifecycle》——30 秒空闲终止、全局变量随 SW 一起丢失: developer.chrome.com/…/service-workers/lifecycle
最后更新:2026 年 11 月 11 日。文中写的是这类扩展的通用实现路径,不同产品选用的 API 与数据落点可能不同;涉及具体产品的内部架构,以该产品文档为准。