【JavaScript】俺の書いた離脱防止用beforeunloadイベントがなんかおかしい件

## ある日

依頼者：「コンバージョン率向上の施策として、離脱防止用のJavaScriptを書いて」

わたし：「ほーん。とりあえず調べるは」

わたし：『JavaScript 離脱防止』で検索っと・・・カタカタカタ・・・beforeunloadイベントで実装したら手軽そうだな・・・

わたし：「これこれこんな感じでどーすか？」

依頼者：「それでおっけー、よろしく！」

わたし：都合のいい要件で落とし込めたな・・・チョロすぎｗｗｗ　はやく終わらせて飲みに行こっと

```html:
<!DOCTYPE html>
<html>
<head>
<script>
(() => {
    'use strict';
    let isSubmit = false;
    addEventListener('beforeunload', e => {
        if (!isSubmit) {
            e.preventDefault();
            e.returnValue = 'Are you sure you want to exit?';
        }
    });
    document.addEventListener('DOMContentLoaded', () => {
        document.getElementsByTagName('button')[0].addEventListener('click', () => { isSubmit = true; });
    });
})();
</script>
</head>
<body>
<form style="background:#ccc;padding:50px">
    Name:<input><button>Send</button>
</form>
<a href="https://example.com/">Go to example.com</a>
</body>
</html>
```
![sample-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1519519/00f8bde5-6d12-c5bd-5f84-664033835774.png)
わたし：ふぅ・・・こんなんでええやろ。余裕すぎて自分の才能が怖いｗｗｗ

わたし：あーいちおテストしとくか。あーだりー。

わたし：まずは[Send]ボタン押してっと・・・離脱防止確認ダイアログ出ないね。よしよし。

わたし：次はリンクをクリックしてっと・・・これは確認ダイアログ出るね。ハイハイっと。

わたし：ブラウザの「リロード」ボタンをクリックしたら・・・これも確認ダイアログ出るのね、なるほどー、俺最高↑↑

わたし：（リロード後）・・・いちお「リロード」ボタン連打もしとくか・・・ポチポチポチ・・・ん？　確認ダイアログ出ない？　さっきは出たのに・・・

わたし：なんか適当にブックマークしてるサイトに飛んでみるか・・・はい？　なんで確認ダイアログ出ないの？？

わたし：ブラウザの「戻る」ボタンは・・・これも出ない！！　もうやだ！！！　おうちかえりたい！！！

## Chromeデベロッパーツールを確認

:::note alert
Blocked attempt to show a 'beforeunload' confirmation panel for a frame that never had a user gesture since its load.
:::

ユーザージェスチャーが発生しなかったから確認ダイアログの表示をブロックしたの？　何言ってんだこいつ・・・。

1回目のリロードでは確認ダイアログ出たのに2回目以降は確認ダイアログ出なかった件について、どう説明するんだよ。

その後、適当にいろんなパターンで離脱を試みては確認ダイアログが出たり出なかったりを繰り返してわけわからなくなりながらも、ようやくひとつの答えにたどり着きました。

:::note info
ユーザージェスチャーが1度も発生してなければ、beforeunloadイベントでの確認ダイアログの表示はブロックされる。
:::

はい。。。エラーメッセージの通りでございました。

いろいろ試した感じ、ユーザージェスチャーとは表示領域内でのクリックとかキーボード押下のことです。
※マウス動かしたりスクロールしたりはユーザージェスチャーには含まれないです。

1回目と2回目以降のリロード時の確認ダイアログの挙動が違った理由は以下でした。
1回目＝リロード前にリンクをクリックしてる＝確認ダイアログ出る
2回目＝ユーザージェスチャーなしで「リロード」ボタンをクリック＝確認ダイアログ出ない
※「リロード」ボタンは表示領域外でのクリックなのでユーザージェスチャーにならない。

ブックマークのクリックも「戻る」ボタンクリックも同様に表示領域外のクリックだったため確認ダイアログが出なかった次第です。

## 結論

**beforeunloadイベントを使用したいなら、ユーザージェスチャーがない場合の確認ダイアログ表示は諦める**

とはいえエラーは解消しておきたいので以下のように修正しました。

```html:
<!DOCTYPE html>
<html>
<head>
<script>
(() => {
    'use strict';
    let isSubmit = false;
    let userGestureExists = false;
    for (const v of ['click', 'keydown']) {
        addEventListener(v, () => { userGestureExists = true; }, { once: true });
    }
    addEventListener('beforeunload', e => {
        if (!isSubmit && userGestureExists) {
            e.preventDefault();
            e.returnValue = 'Are you sure you want to exit?';
        }
    });
    document.addEventListener('DOMContentLoaded', () => {
        document.getElementsByTagName('button')[0].addEventListener('click', () => { isSubmit = true; });
    });
})();
</script>
</head>
<body>
<form style="background:#ccc;padding:50px">
    Name:<input><button>Send</button>
</form>
<a href="https://example.com/">Go to example.com</a>
</body>
</html>
```

onclickかonkeydownのイベント発火をもって「ユーザージェスチャーあり」と判断してます。