かつて、新しいブラウザタブを開くことが、まるで野生の獣を解き放つような感覚だった時代を覚えているだろうか? それぞれが注意をひこうと鳴き叫び、隣のタブのことなどお構いなし。私たちは、localStorageを通じた囁きや、BroadcastChannelを介した必死の信号といった、複雑怪奇なシステムをでっち上げ、なんとか秩序を保とう、一度に一つのタブだけがスポットライトを浴びるようにしようと躍起になっていた。それは開発者の通過儀礼であり、本来あるべき姿よりもはるかに複雑に感じられるパズルだった。
そして、そんな状況が長らく続いていた。それが当たり前、必要悪。私たちは、古いデータや忌まわしい競合状態といったエッジケースの重みに耐えきれなくなることを祈りながら、ぎこちなく手動の調整システムを構築することを期待していた。アクティブなタブが、うまくいけば、閉じられたことを示すために、信頼性の低い幽霊のようなイベントであるbeforeunloadを巧みに利用したことに、自画自賛していたものだ。
しかし、もしブラウザ自体が、私たちが利用するのを静かに待っている、洗練されたロックとキューのシステムを備えていたとしたらどうだろう? 私たちが一生懸命行ってきた、あの雑で信頼性の低いJavaScriptのダンスが、本質的には、車輪の再発明を、しかも下手なやり方でやっていたとしたら?
実のところ、Web Locks APIは、単なる開発者のベルトの新たなツールではない。これは根本的なプラットフォームのシフトだ。それは、手で井戸を掘っていたのに、完璧な帯水層がすぐ足元にあり、すでに加圧されてすぐにでも利用できる状態にあったことを発見するようなものだ。このAPIは、マルチタブ間のやり取りにようやく正気をもたらし、悪夢になりかねない状況を、ブラウザが管理するエレガントなバレエへと変貌させる、静かな天才なのだ。
タブのタブーは終わるのか?
長年、開発者は「シングルアクティブタブ」問題に苦しんできた。目標は、同じアプリケーションの数十個のタブの中から、一度にリクエストを処理したり、動的なコンテンツを表示したりできるのが一つだけであることを保証することだった。残りは、丁重にロックされた状態で、順番を待つべきとされていた。これは見た目の問題だけでなく、データ破損を防いだり、一貫したユーザー体験を保証したりするために、しばしば要件だった。
私たちは丹念にロジックを組み立てた。まず、各タブに一意の識別子――デジタル指紋。次に、通常はlocalStorageである中央の信頼できる情報源で、『このタブがボスだ!』と宣言する。新しいタブが到着し、localStorageをチェックし、すでにボスが宣言されている場合は、丁重に脇にそれてロックされた。ボスを閉じる? 胃が痛む瞬間、イベントがトリガーされ、他のタブが正しくバトンを引き継ぐことを願う。それは、制御された混沌の練習だった。
私たちは、
localStorageを通じた囁きや、BroadcastChannelを介した必死の信号といった、複雑怪奇なシステムをでっち上げ、なんとか秩序を保とうと躍起になっていた。
元のコンテンツが指摘するように、localStorageソリューションは、基本的なロックには機能したが、現実世界に直面すると崩壊した。ブラウザの再起動後も古いデータが残り続け、競合状態――2つのタブが同時に開き、どちらも自分が選ばれたと思い込む――は、私たちの悩みの種として頑固に残った。BroadcastChannelはタブ間のより直接的な通信手段を提供したが、誰がアクティブになれるかを確実に管理するという中心的な問題は解決しなかった。
ブラウザの秘密兵器:Web Locksの登場
Web Locks APIの美しさは、そのシンプルさと堅牢性にある。これは、共有リソースへの同時アクセスを処理するために、ブラウザの内部メカニズムを使用する。このシナリオでは、アプリケーション自体が共有リソースとなる。
タブが開かれると、単に特定の名前(例:'dev:app')を使ってロックを要求する。他のタブが現在そのロックを保持していない場合、要求したタブがロックを取得し、処理を進めることができる、つまり「アクティブ」なタブになる。他のタブがすでにロックを保持している場合、新しいタブは自動的にキューに入れられ、辛抱強く待機する。それは、排他的なクラブのベルベットロープのようなもので、ドアマン(ブラウザ)によって管理されているかのようだ。
これは、これらすべての厄介なエッジケースをエレガントに回避する。
- 次のアクティブタブ: 現在アクティブなタブが閉じられると、ブラウザは自動的にロックを解放する。キューの次のタブが自動的にロックを取得し、アクティブになる。手動での選挙プロセスも、複雑な順序付け配列も不要。ブラウザが引き継ぎを処理する。
- 古いアクティブタブ: ロックはブラウザによって管理され、
localStorageのようにユーザーがアクセスできる形で状態を永続化しないため、古いデータを気にする必要がない。ブラウザが予期せず閉じられた場合、ブラウザプロセスが終了したときにロックは解放される。再度開いたとき、最初にロックを要求したタブがアクティブになる。クリーンだ。 - 競合状態: ここでWeb Locks APIは真価を発揮する。ブラウザの内部キューイングシステムは、本質的に競合状態を防ぐ。一度に一つのタブしかロックを取得できない。2つのタブが同時に要求した場合、一方がロックを取得し、もう一方は順番を待つことになる。これは決定論的だ。
これは、驚異的な簡略化だ。状態を管理し、メッセージをブロードキャストし、信頼性のないイベントを処理するために何ページものJavaScriptを書く代わりに、開発者は今や、まさにこの種の調整のために設計されたブラウザネイティブAPIに依存できるようになった。提供されたコードスニペットは、ReactのuseEffectフック内の数行で、このパラダイムシフト全体をカプセル化している。
const [active, setActive] = useState(false);
useEffect(() => {
navigator.locks.request("dev:app", () => {
setActive(true);
});
}, []);
if (!active) {
return <div>This tab is not available</div>;
}
return <div>This tab is available</div>;
そして、あっという間に、ブラウザが重労働を引き受けてくれる。かつて苦労していた複雑さは、今や私たちのアプリケーションが実行される環境そのものによって管理されるようになった。
Webアプリケーションのパラダイムシフト
これは、単に一つのタブをアクティブにするだけではない。その意味を考えてみてほしい。アプリケーションのインスタンスが一度に一つのクリティカルな操作を実行すべきであるような、あらゆるシナリオが、このAPIで処理できるようになる。クリティカルなデータ更新の同期、タブをまたいだ重複フォーム送信の防止、バックグラウンドタスクの管理など、Web Locks APIは、堅牢なマルチタブアプリケーションのための基盤を提供する。
長年、私たちは複雑なミドルウェアを構築し、混沌とした環境に秩序を課そうとしてきた。今、ブラウザは私たちに配管を提供してくれる。時には、最もエレガントな解決策は、より多くのコードではなく、基盤となるプラットフォームをより効果的に理解し、利用することにあるということを思い出させてくれる。これが真のプラットフォームシフトの感覚だ:突然、不可能なことが些細なことになり、Webアプリケーションの未来が少しだけ…まともになる。