一個 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 與資料落點可能不同;涉及具體產品的內部架構,以該產品文件為準。