1回の DOM イベントでは、外側の祖先にある capture listener が target に向かって順に実行されます。target に到達すると、そのノードに登録された capture listener が bubble listener より先に実行されますが、どちらの観測も target フェーズに属します。イベントが bubble する場合は、続いて target から祖先へ向かって bubble listener が実行されます。この順序で観測点を読むと、経路を変更または中断した可能性がある最初の listener を特定できます。
target は listener から見えるイベントの発生元を示し、currentTarget はその観測時点で listener が実行されているノードを示します。composedPath() は現在のコンテキストから見える順序付きのノード経路で、この trace では target からルートへ向かう順に表示されます。capture は逆向き、bubble は表示どおりに読みます。Shadow DOM では、見える target が再ターゲットされたり closed shadow の内部が経路から省かれたりするため、通常の DOM の parent 連鎖を想定せず、記録された経路を基準にしてください。
stopPropagation はイベントが後続ノードへ進むのを止めます。同じ currentTarget 上で、同じ capture または bubble の呼び出し中にあるほかの listener は引き続き実行されます。ただし target の capture listener から呼ぶと、後続の target の bubble 呼び出しはスキップされます。stopImmediatePropagation は、そのノードで同じ呼び出し中に残っている listener も停止します。一度だけ動く probe は、停止メソッドが呼ばれた phase と currentTarget を記録するため、登録コードだけから推測せず、停止地点と最後の観測を照合できます。handler を修正したら同じイベントを再取得し、想定した祖先まで経路が届くことを確認してください。
このテーマに関するよくある疑問と回答をまとめました。
capture listener は外側の祖先から target に向かって実行されます。target では capture listener が bubble listener より先に実行され、どちらも target フェーズとして報告されます。bubbles が true なら、その後 target から祖先へ向かって bubble listener が実行されます。同じノードにある同種の listener 同士は登録順に実行されます。
まず event.bubbles を確認してください。イベントの種類によっては、もともと bubble しません。true の場合は、親要素へ届く前に stopPropagation または stopImmediatePropagation が呼ばれていないか、親要素が実際に composedPath() 上にあるか、Shadow DOM やフレームワークの境界によって見える経路が変わっていないかを確認します。trace には経路、最後の観測、記録された停止呼び出しが表示されるため、これらを切り分けられます。
event.target は listener から見えるイベントの発生元で、通常は伝播中も同じです。event.currentTarget は現在 listener が実行されているノードなので、観測点ごとに変わります。イベント委譲では、target でクリックされた子孫を識別し、currentTarget で handler を持つ要素を識別します。
常にブロックするわけではありません。同じ currentTarget 上で、同じ capture または bubble の呼び出し中にあるほかの listener は引き続き実行されます。ただし target の capture listener が stopPropagation を呼ぶと、後続の target の bubble 呼び出しはスキップされます。stopImmediatePropagation は後続の伝播に加えて、同じ呼び出し中に残っている listener も停止します。probe は実際に呼ばれたメソッドを記録するため、これらを混同せずに済みます。
イベント種別を選び、一度だけ動く probe をコピーして、問題が起きるページの DevTools で実行します。イベントを1回再現したら、生成された JSON trace をこのツールに貼り付けます。probe は取得後に listener を削除して一時的に変更したメソッドを元に戻し、その1イベント以外は記録せず、trace をネットワーク送信しません。
イベント伝播は、1つの DOM イベントがどのノードを進み、その経路上の capture、target、bubble listener がどのように実行されるかを表します。イベントループは、コールスタック、microtask、macrotask を調整して JavaScript の処理がいつ実行されるかを決めます。click はタスクとしてイベントループに入り、そのイベントの dispatch が始まると、イベント伝播が内部の listener の順序を決めます。