Open7

[更新あり] Chrome 120で dialog要素の onCancel イベントがトリガーしない

Yuki HattoriYuki Hattori

https://crbug.com/1510389


  • 2023-12-06: CloseWatcher API を含む Chrome 120 リリース、同時に onCancel がトリガーしない問題発生
  • 2024-01-08: CloseWatcher API が無効化されたことで一時的に問題が解消
  • 2024-06-12: Chrome 126 リリース、 onCancel 問題が対処された CloseWatcher API が再度有効化される予定
Yuki HattoriYuki Hattori

Chrome 120 で新たに追加された Close Watcher API によって、<dialog> 要素にアタッチされた onCancel イベントがトリガーしなくなる模様。

https://developer.chrome.com/blog/new-in-chrome-120?hl=ja#close-watcher

https://github.com/WICG/close-watcher

this is an intentional change as part of https://chromestatus.com/feature/4722261258928128, to prevent excessive events firing when the user does not interact with the <dialog>. It is now specified and required by the HTML Standard.

https://bugs.chromium.org/p/chromium/issues/detail?id=1510389#c8

<dialog> で過剰にイベントを発火しないようにと、HTML の仕様で決められたとのこと。

Hidden comment
Yuki HattoriYuki Hattori

Demo

https://jsfiddle.net/kmdno3he/

Esc でモーダルを閉じると、コンソールに、onCancel イベントのログが出力される。Chrome 120 で見ると、ログが出ていないことがわかる。

何らかの理由で、Esc キーでダイアログを閉じないようにしたい場合、以下の実装は Chrome 120 以降では動作しないことになる。

// Chrome 120 以降では動作しない
dialog.addEventListener('cancel', (e) => {
  e.preventDefault();
})

→ Escキーを抑制するデモ (Chrome 120 以降で動かない版)

Yuki HattoriYuki Hattori

Chrome 120 でも cancel イベントが呼ばれる時もある模様。一貫してないのがちょっとややこしい。

例: モーダル表示後、背景の透過部分 (::backdrop) をクリックした後に Esc キーを押すと、 onCancel が呼ばれる。e.preventDefault() をしていれば、閉じる処理も抑制される。

Yuki HattoriYuki Hattori

CloseWatcher で Esc を抑制する (close イベントを使うパターン)

Esc の他、Android の戻るジェスチャーも抑制可能になる。

<button type="button" id="open">
  Open modal
</button>

<dialog id="modal">
  <p>
    Esc key is disabled!
  </p>
  <button type="button" id="close">
    Close modal
  </button>
</dialog>
const open = document.querySelector('#open');
const close = document.querySelector('#close');
const dialog = document.querySelector('#modal');

open.addEventListener('click', () => {
  dialog.showModal();
  
  // Esc キーを抑制 (Chrome 120 以降)
  if ('CloseWatcher' in window) {
    let closeWatcher;

    const initCloseWatcher = () => {
      closeWatcher = new CloseWatcher();

      // CloseWatcher は消費すると2回目はトリガーしないので、消費の度に新しい CloseWatcher を作る
      closeWatcher.addEventListener('close', initCloseWatcher);
    }
    initCloseWatcher();

    // ダイアログが実際に閉じる際に既存の CloseWatcher を破棄
    dialog.addEventListener('close', () => closeWatcher?.destroy(), { once: true });
  }
});

close.addEventListener('click', () => dialog.close());

→ Escキーを抑制するデモ (CloseWatcher 使用版)