一個 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 與擴充功能隔離的對比】。

能力邊界:哪些層擴充功能能覆蓋,哪些覆蓋不到?

把上面的邊界收成一張表。讀法是「看平台在查什麼」:查工作階段與出口,看中間那一欄;查指紋一致性,看右邊那一欄。需要強調:擴充功能做的是工作階段層隔離,和指紋瀏覽器不是同等級的隔離,把它當成「更輕的指紋瀏覽器」會誤判風險。

擴充功能級工作階段隔離的能力邊界(2026-11 版本)
層擴充功能能覆蓋覆蓋不到,或代價在哪
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 版本變化,上線前請依當期文件複核。

最後更新:2026 年 11 月 11 日。文中寫的是這類擴充功能的通用實作路徑,不同產品選用的 API 與資料落點可能不同;涉及具體產品的內部架構,以該產品文件為準。