Function#bind の罠にはめられた
Firefox拡張開発のお話。
とあるメニューがポップアップするとき、popupshowingというイベントが発行されるわけだが。
Firefox側で以下の様な感じでメニューが表示されるときに動的にメニューを追加する処理が走るコードが記述されている。
const o = { init: function () { let elm = document.getElementId("hogeMenu"); elm.addEventListener("popupshowing", this.popupshowing.bind(this), true); }, popupshowing: function (aEvent) { let menuitem = document.createElement("menuitem"); // .... elm.appendChild(menuitem); }, };
で、問題は3点。
- addEventListenerの第3引数が
true
でキャプチャリングフェーズをlistenしている - Function#bind を使用している
- 関数側で
aEvent.target
等からどのメニューでイベントが発生したか検査していない。
Firefox側は上記の様になっていて、こちらの拡張機能側ではそのメニューに子メニューを追加している。するとどうなるか。子メニュー側から発生したpopupshowing
イベントをFierfox側が拾ってさらにメニューを追加してしまうんだよね。
というのが https://github.com/teramako/Pano/issues/18 で挙げられた問題。
非常に困った。かなり詰んだ状況。
上記問題3点のうち一つでも修正されればOKなんだが...。
1.キャプチャリングフェーズ
もし、バブリングフェーズであれば、既に実装しているのだが、子要素となる子メニューのリスナからevent.stopPorpagation()
してあげれば、伝播をとめられる。
2.Function#bind
Firefox側のハンドラ関数にはアクセス可能なのでremoveEventListener
してあげればOK*1そうだが、あいにくbindされている。bindは新たな関数オブジェクトを返すので登録された関数とアクセス可能な関数は別オブジェクトであり、remove不可
3.event.targetをチェックしていない
チェックしてくれていれば、何の問題もなかった。
*1:removeEventListenerしてあげて、問題修正済みの関数を登録してあげればOK