別タブで開いた画面にopenerを渡すことによって引き起こされるパフォーマンス問題について
openerとブラウザプロセス
タイトルの通り、Webアプリケーションにおいて遷移先画面に opener
(遷移元画面 Window
への参照) を渡してしまうと、アプリケーションのパフォーマンス問題を招く可能性がある。JavaScriptの window.open
における windowFeatures
、あるいはHTMLのanchor要素における rel
属性の設定を考えたことのある人なら、おそらく一度はぶつかる話である。
今に始まった話ではないので、この件に関してWebで検索すれば日本語のものも含めいくつも情報を見つけることができる。その中の多くは以下の記事を元ネタにしている。
一部抄訳して引用すると、
ほとんどのブラウザは、Firefox を除いて(そして彼らはそれに取り組んでいます)、マルチプロセスです。各プロセスには、私たちがよく「メイン」スレッドと呼ぶものを含め、複数のスレッドがあります。ここでは、構文解析、スタイル計算、レイアウト、ペイント、および非ワーカー JavaScript が実行されます。つまり、あるドメインで動作するJavaScriptは、別のドメインで動作するウィンドウ/タブとは別のスレッドで実行されます。
しかし、DOM が window.opener を介して与える同期的なクロスウィンドウ アクセスにより、target="_blank" で起動したウィンドウは同じプロセスおよびスレッドで終了します。iframeやwindow.openで開かれたウィンドウも同様です。
最初、この記事が執筆されたのが2016年だったので、もしかすると今では状況も変わっているのではないかと期待したが、手元で検証してみたところ2022年の現在でも状況は変わっていなかった。
ということで、引き続きアプリケーションのパフォーマンスを考えるのであれば (セキュリティリスクを無視できるとしても) opener
は渡すなというのが結論になるのであるが、今後いつ状況が変わるかわからない(あるいは永遠に変わらないかもしれない)ので、いつでもサクッと状況を確認できるように検証アプリを用意した。
検証アプリ
検証アプリは以下にデプロイしている。
https://yukiyokotani.github.io/performance-impact-of-passing-window-opener/
アプリの中には3つのボタンがある。
Open in New Tab
新しいタブで画面を開く。このとき、新しいタブには遷移元からopener
が渡るようになっている。Exclusive thread for 10 seconds
ループで10秒間スレッドを専有するようにしている。実行すると処理の間、自タブは勿論opener
を通じてWindow
への参照を共有している他タブもレンダリングがストップする。+1
カウンターのインクリメントボタン。レンダリングがストップしていることを確認するためにある。ループがスレッドを専有している間はクリックしても反応しない。
ソースコードは以下のリポジトリに上げている。
補足:なぜopenerを渡したいか
opener
を渡さなければいけないケースというのは実際のところかなり少ないとは思うが、あえて渡すとしたら以下のようなケースが考えられる。
- 別タブで開いた遷移先windowから遷移元windowの内容を参照したい
- 別タブで開いた遷移先windowから遷移元windowへ
window.postMessage()
でメッセージを送りたい。
パフォーマンス問題が起きた場合、もし遷移元と遷移先が同一オリジンであれば BroadcastChannel API
を利用したメッセージのやり取りで問題は回避できると思われる。ただ、遷移元と遷移先でオリジンが異なる場合、 opener
を渡さずに (つまりパフォーマンス問題を回避して) 要件を達成する方法を自分は知らない。
補足:openerの渡し方
遷移先に opener
を渡す方法、あるいは渡さない方法について簡単にまとめておく。
window.open
window.open
で遷移する場合のopenerの指定は引数の windowFeature
によって行う。デフォルトでは opener
が渡るので、渡したい場合は特に何もしなくて良い。渡したくない場合は noopener
もしくは noreferrer
を設定する必要がある。この両者の違いは遷移先に referrer
を渡すかどうかなので、それをもとに判断すればよく、両方設定する必要はない。
動作確認する際は、遷移先のコンソールで document.referrer
を見てみればよい。ちなみに opener
についてもコンソールで window.opener
を見れば、opener
が渡っているかを簡単に確認することができる。
https://html.spec.whatwg.org/multipage/window-object.html#apis-for-creating-and-navigating-browsing-contexts-by-name
anchor要素
target='_blanck'
の場合が問題になる。Chromiumは target='_blanck'
の場合にデフォルトで opener
を渡さないようになっているので、渡したくない場合は何もしなくて良い。渡したい場合は rel=opener
で明示的に設定する必要がある。
https://html.spec.whatwg.org/multipage/links.html#link-type-opener