1 つの Chrome ウィンドウで複数の分離ワークスペースを動かす仕組み
単一ウィンドウの分離は 3 層の積み重ねです。セッション/ストレージ層、ネットワーク経路層、ライフサイクル管理層であり、カーネル仮想化ではありません。指紋は変えません。
なぜ「ウィンドウを増やす」ことが複数アカウントの同時ログインにならないのか
まず直感を一つ外します。ウィンドウは分離の単位ではありません。同じプロファイルで 10 個のウィンドウを開いても、Cookie ストアも localStorage も HTTP キャッシュも 1 つを共有し、サイトから見れば同一セッションです。シークレットウィンドウには独立したストアがありますが 1 つしかなく、閉じれば消えるので 10 アカウントには足りません。本当の単位は「セッションコンテナ」——独立した Cookie の集合とストレージの名前空間、そして安定した帰属の対応表です。Chrome にコンテナ API はありません(Firefox の contextualIdentities が例外)。だから拡張機能はセッション層で自前で作ります。どのタブがどのコンテナに属するかを記録し、リクエストとページの 2 面でその対応表に沿って振り分ける。CrispLogin もこの方式で、コンテナは拡張機能が保持する帰属記録であって Chrome のプリミティブではありません。分離の粒度は「セッション」で、「プロセス」ではないのです。
第 1 層:Cookie はなぜ混ざらないのか
アカウントが混ざらないのは「仮想 Cookie ジャー + タブ単位のリクエストヘッダ書き換え」によるものです。各コンテナは拡張機能の中に独自の Cookie 記録を持ちます。リクエストが出る前に、declarativeNetRequest の session rule が condition.tabIds でタブを照合し、cookie リクエストヘッダをそのコンテナの値に丸ごと set します。応答が返ると、webRequest で Set-Cookie を観測して該当するジャーへ書き戻します。ジャー内の Cookie は path の長さと作成時刻の順に自前で並べて直列化しないと、ブラウザが本来送るヘッダと一致しません。
見落としやすいのがライフサイクルです。tabIds は session rule でしか使えず、session rule はブラウザ終了時と拡張機能の更新時に消えます。だから起動のたびにルールを入れ直す必要があり、これを飛ばすと「再起動したら全ワークスペースが同じアカウントになる」という症状になります。ページ側のストレージも別の面です。document.cookie は実際の Cookie ストアを読み、localStorage の名前空間も 1 つしかないので、MAIN world のスクリプトがコンテナ単位で読み書きを代理します。これはアプリケーション層の分離であり、同一サイトの 2 アカウントは分けられますが、カーネルの指紋には触れません。
第 2 層:プロキシはどうワークスペースに追従するのか
Chrome に「タブ単位でプロキシを設定する」API はありません。chrome.proxy.settings のスコープはプロファイル全体で、PAC の FindProxyForURL(url, host) に tabId は渡りません。declarativeNetRequest はヘッダの書き換えやリダイレクトはできても、「上流プロキシを選ぶ」アクションを持ちません。したがって拡張機能にできるのは「ワークスペース → 出口」の対応表を保持し、前面のタブが変わったときにこの全体設定を書き換えることです。
PAC はドメインで振り分けられますが、判断材料は URL であってタブではありません。同じサイトを 2 つのワークスペースで開いても、PAC は 1 つの答えしか返せません。つまり「10 のワークスペースがそれぞれ別トンネルを同時に走る」は拡張機能では成立せず、保証できるのは「いま前面にあるワークスペースの出口が生きている」ことだけです。ワークスペースを切り替えると一度切断が見えるのも同じ理由で、プロキシ設定を変えると確立済みの接続が作り直されます。境界:この層が変えるのは出口 IP と経路であり、TCP/TLS 指紋(JA3/JA4)は変わりません。WebRTC は chrome.privacy.network.webRTCIPHandlingPolicy で非プロキシ UDP を止められますが、あれはブラウザ全体のポリシーで、ワークスペース単位ではありません。
第 3 層:タブを閉じても、再起動しても紐付けは残るのか
MV3 のバックグラウンドはサービスワーカーです。イベントが 30 秒なければ終了し、モジュール変数もクロージャ内の Map も一緒に消えます。だから「どのタブがどのワークスペースか」をメモリに置くことはできず、chrome.storage.session に落とします。これは拡張機能が読み込まれている間メモリに常駐し、ワーカーが回収されて再起動しても残りますが、ブラウザの再起動で消えます。既定ではコンテンツスクリプトに公開されない点にも注意が必要で、ページ側から帰属を読むなら setAccessLevel を明示的に呼びます。
ここでデータが 2 種類に分かれます。実行時の紐付けは session、ワークスペース一覧・プロキシ設定・スナップショット索引など再起動をまたぐものは storage.local です。因果は単純です。紐付けが変数ではなく session にあるから、ワーカーが起こされても拡張機能は各タブの帰属を把握しており、あるワークスペースの Cookie を別のページに流し込みません。メモリだけに置けば、ワーカーが眠った瞬間に次のリクエストには持ち主がなく、グループの重複やスナップショットの誤注入として現れます。
第 4 層:サイトスナップショットは何を保存し、何を戻すのか
スナップショットが解くのは別の問題、ログイン状態の可搬性であって実行時の分離ではありません。4 層目に置いていますが、分離そのものの一部ではありません。あるサイトの Cookie とそのサイトのストレージを、1 つのコンテナの中に直列化して保存し、復元時にそのコンテナの名前空間へ戻します。両者は直交します。分離は 2 つのワークスペースが同時に存在しても干渉しないことを保証し、スナップショットは再ログインせずにある状態へ戻れることを保証します。粒度はブラウザ全体ではなくサイト単位で、同じコンテナの 2 つのサイトは無関係な 2 レコードです。
だからスナップショットはコンテナに従属します。A のログイン状態を B に流し込めば、2 つのアカウントを 1 つに混ぜたことになります。やらないことも明記します。戻すのはアプリケーション層のセッション状態であり、カーネル級の指紋ではなく、プラットフォームのリスク判定や端末検証を突破する約束もしていません。使い方は【内部リンク:サイトスナップショットの実用例】を参照してください。
「複数プロファイル」と何が違うのか
Chrome の複数プロファイルは、ストレージのルートディレクトリ単位での物理的な分割です。プロファイルごとに Cookie ストア、ストレージ、拡張機能、キャッシュが独立し、分離は徹底しています。代償は 4 つの次元に出ます。メモリは、各プロファイルが独自のネットワークコンテキストとレンダラプロセスを持つため、3 つ開けば常駐コストもおよそ 3 倍。プロセスは、プロセスツリーが別々。ウィンドウは、1 プロファイル 1 ウィンドウなので切り替えはウィンドウの切り替え。指紋は、プロファイル間でカーネルもハードウェアも共有するため、UA も Canvas もフォントも既定では同一です。もう一つ静かなコストがあり、ブックマーク・拡張機能・履歴は共有されないので、3 つを超えるとそれぞれが勝手に育ち始めます。
拡張機能の方式はその逆です。同じプロファイル、同じウィンドウ、プロセスとカーネルを共有し、単一ウィンドウでの並行とほぼゼロの増分コストを得る代わりに、カーネルの指紋層には届きません。同じものの 2 グレードではなく、分離粒度の取り方が 2 通りあるということです。比較は【内部リンク:Chrome の複数プロファイルと拡張機能分離の比較】で展開しています。
能力の境界:どの層まで拡張機能で覆えるのか
ここまでの境界を 1 枚の表にまとめます。読み方は「プラットフォームが何を見ているか」です。セッションと出口を見ているなら中央の列、指紋の一貫性を見ているなら右の列。強調しておくと、拡張機能がやるのはセッション層の分離であり、指紋ブラウザと同格の分離ではありません。「軽い指紋ブラウザ」と見なすとリスクを読み違えます。
| 層 | 拡張機能で覆える | 覆えない、または代償 |
|---|---|---|
| Cookie とログイン状態 | コンテナ単位で分離、スナップショット化できる | カーネルはコンテナを知らない。ヘッダ書き換え依存で、ルールを失うと混ざる |
| ページストレージ | localStorage と IndexedDB はコンテナ単位で接頭辞や代理が可能 | Service Worker や SharedWorker など別コンテキストは個別対応が必要 |
| 出口 IP | ワークスペースに紐付け、前面を切り替えれば出口も変わる | 同時に使える出口は 1 つ。トンネルの並行実行は不可 |
| WebRTC | 非プロキシ UDP を止められる(ブラウザ全体のポリシー) | ワークスペース単位ではなく、媒体経路がプロキシを迂回しうる |
| TLS 指紋 | 不可 | JA3/JA4 はカーネルとネットワークスタックが決める |
| Canvas / WebGL / Audio | 不可 | 描画はカーネル側。拡張機能に書き換え API はない |
| フォント列挙 | 不可 | ページ JS のパッチのみ。検出可能 |
| HTTP キャッシュ | 不可 | キャッシュはプロファイル単位で、コンテナ間で共有される |
この表は上限の位置も示しています。拡張機能が置き換えるのは「ログインを分けるためだけにクライアントを買う」動機であって、クライアントそのものではありません。どちらの路線を選ぶかは別の問いで、項目ごとの比較は【内部リンク:拡張機能と指紋ブラウザの路線比較】にあります。
よくある質問
拡張機能の Cookie 分離は Chrome の Partitioned Cookie と同じものですか
違います。Partitioned Cookie はトップレベルサイトでキー付けされ、1 つのサイトはトップレベルページごとに 1 つずつしか持てません。cookies API の partitionKey はそれを読み書きするためのキーで、構成要素は topLevelSite だけであり、新しい区画を作る道具ではありません。ワークスペースのジャーはコンテナ単位なので、同じサイトをいくつでも持て、帳簿は拡張機能が付けます。前者はブラウザのプライバシー分区、後者は拡張機能のセッション抽象です。
タブを閉じたらワークスペースの状態は残りますか
3 つに分かれます。タブが消えれば紐付けも消えます。ワークスペースの定義(名前、プロキシ、スナップショット索引)は再起動をまたぐ必要がある種類のデータで、通常は storage.local に置きます。ログイン状態はスナップショットを保存した場合だけ復元でき、なければ再ログインです。storage.session の中身はブラウザを閉じると消えるので、永続層として扱わないでください。
単一ウィンドウ方式はブラウザの性能に影響しますか
増分は分離機構ではなくタブそのものから来ます。2 つ目のブラウザを起動せず、カーネルも複製しません。コストはルール照合とヘッダ書き換えで、リクエスト 1 回あたりの定数です。本当のコストはタブ数とページ自体にあり、ワークスペースを 20 個開けば重くなるのは描画であって分離層ではありません。
2 つのワークスペースが同時に別の出口 IP を使えますか
使えません。プロキシ設定はプロファイル単位で、拡張機能はタブ切り替えのときにしか書き換えられないため、同時点で自分の出口を使えるのは前面のワークスペースだけです。背面のワークスペースはルールで遮断されるか直接接続になります。本当の並行トンネルには、独立したネットワークスタックを持つクライアントが必要です。
出典と最終更新
API の挙動に触れる箇所は公式ドキュメントに従います。拡張機能の能力境界は Chrome のバージョンで動くため、導入前にその時点のドキュメントで確認してください。
- Chrome for Developers, chrome.declarativeNetRequest — session rule の寿命、condition.tabIds の適用範囲、cookie リクエストヘッダへの modifyHeaders: 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 秒のアイドルで終了、グローバル変数はワーカーと共に失われる: developer.chrome.com/…/service-workers/lifecycle
最終更新:2026 年 11 月 11 日。ここに書いたのはこの種の拡張機能に共通する実装経路で、採用する API やデータの置き場所は製品ごとに異なります。個々の製品の内部構造は、その製品のドキュメントに従います。