開発者「すいません、window.close()
が動かないんですけど…」
ワイ「コンソールになんか書いてないですか?」
Scripts may close only the windows that were opened by them.
開発者「ってでてます。」
ワイ「そこに書いてある通りですね。」
開発者「すみません、英語わかんなくて…」
ワイ「」
なぜ閉じないのかわからない人も、どうすればいいかと相談される人も、window.close()
で消耗するのはおしまいにしましょう。
もう聞かれた時にURLを投げればいいだけにしておきたいので、この記事を残しておきます。
さて、まずはなぜ解決できないのかを知っていくために、先ほどの会話の中で出てきた英文の日本語訳を確認します。
直訳なのでわかりづらいですが、要は__JavaScriptで閉じることができるウィンドウは、JavaScriptで開かれたウィンドウのみ__であると書いてあります。
しかしながら、時々JavaScriptで開いてないウィンドウもwindow.close()
で閉じることができる気がします。それはなぜでしょうか?
一旦冷静になって仕様を確認してみましょう。
What is window.close()
A browsing context is script-closable if it is an auxiliary browsing context that was created by a script (as opposed to by an action of the user), or if it is a top-level browsing context whose session history contains only one Document.
参考文献:https://html.spec.whatwg.org/multipage/window-object.html#dom-window-close
ざっくり要約すると、window.close()
で閉じることができるのは次のパターンです。
- JavaScriptによって開かれた場合
- 履歴が1つしかないトップレベルブラウジングコンテキストの場合
それぞれ1つずつ見ていきます。
JavaScriptで開かれた場合
window.open()
1によって開かれた場合がこれに該当します。
ちなみに、window.open()
でも、features
にnoopener
が指定されていると「JavaScriptで開かれた場合」に__該当しない__振る舞いになります。
逆に、a[target="_blank"]
でも[rel="opener"]
を付与すれば、「JavaScriptで開かれた場合」に__該当する振る舞い__になります2。
こうした実際の動きをみると、「JavaScriptによって開かれた」というよりも、opener
3が存在する場合、と言った方が正しいかもしれませんね。
履歴が1つしかないトップレベルブラウジングコンテキストの場合
2つの要素が関係しているので、ここも1つずつ確認していきましょう。
履歴が1つしかない
window.history.length
が1
のときを指します。
これは、タブ(ブラウジングコンテキスト)が開かれてから、どこページにも遷移していないということを表します。つまり、そのブラウジングコンテキストで「戻る」「進む」がどちらか1方向でもできる状態だと、履歴の長さは2以上になります。
if (window.history.length === 1) {
// It is a browsing context whose session history contains only one Document.
}
トップレベルブラウジングコンテキスト
-
window
- 現在のブラウジングコンテキストのWindowProxy
-
window.parent
- 親ブラウジングコンテキストのWindowProxy
-
window.top
- トップレベルブラウジングコンテキストのWindowProxy
iframe
などでブラウジングコンテキストがネストしているとき、自身のブラウジングコンテキストがトップレベルかを確認するには、次のような方法で確認できます4。
if (window === window.top) {
// It is a top-level browsing context
}
逆に特別なことをしていなければ、常にトップレベルブラウジングコンテキストのはずです。
どちらの状況も満たせない時にウィンドウを閉じるにはどうしたらいいの?
残念ですが閉じることができません5。
そういう場合は、そもそも閉じるボタンの実装を見送って「このタブを閉じてください」など、ユーザ自身にページを閉じるよう促す文言を掲載したりするしかないでしょう。
もし、多くのケースで閉じるボタンが有効ではあるものの、特定のケースでのみ前述の条件を満たせないような場合には、代替テキストを表示する方法もあります。
<button type="button" id="close">このボタンを押すとページが閉じます</button>
<script>
const closeBtn = document.getElementById('close');
closeBtn.addEventListener('click', function () {
window.close();
// `window.closed`を参照することでページが閉じられているかを確認できます。
if (!window.closed) {
this.textContent = '閉じるのに失敗しました';
}
});
</script>
あるいは、もともと閉じることができるかを判定して、「閉じるボタン」と「ブラウザのUIで閉じるよう促す文言」を出し分けるのもいいかもしれません。
<p id="close">ブラウザの×ボタンを押して、このページを閉じてください。</p>
<script>
const placeholder = document.getElementById('close');
if (
window.opener ||
(
window === window.top &&
history.length <= 1
)
) {
const closeBtn = document.createElement('button');
closeBtn.type = 'button';
closeBtn.textContent = 'このボタンを押してページを閉じます';
closeBtn.addEventListener('click', function () {
window.close();
});
placeholder.replaceWith(closeBtn);
}
</script>
テスト用スペース
動いているのが確認したいという方向けにwindow.close()
の動きをテストするためのページを用意してみました。
このページでは次の状態の違い、確認できます。
- トップレベルブラウジングコンテキストかどうか
- openerの存在
- 履歴の長さ
- Script closable(
window.close()
が利用可能か)
終わりに
わたしたちがwindow.close()
閉じない問題を本当の意味で解決する方法は、そもそももっと上流工程を見直してwindow.close()
が不要な導線設計になるようにするほかありません。
可能であれば、導線設計の話の部分にも食い込んで提案するようにしていきたいですね😔
-
https://developer.mozilla.org/ja/docs/Web/API/Window/open
https://html.spec.whatwg.org/multipage/window-object.html#dom-open ↩ -
Google Chrome 89、Firefox 86、Edge 89 で動作確認済み。Safari(14)ではhistoryの挙動が異なります。 ↩
-
https://developer.mozilla.org/ja/docs/Web/API/Window/opener ↩
-
https://html.spec.whatwg.org/multipage/browsers.html#navigating-nested-browsing-contexts-in-the-dom ↩
-
通常のJavaScriptの範疇を超えない場合に限る(ブラウザ拡張を利用するなどはその限りではない) ↩