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点。

  1. addEventListenerの第3引数がtrueでキャプチャリングフェーズをlistenしている
  2. Function#bind を使用している
  3. 関数側で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