クリックされたタブやボタンを確実に取得する方法

タブの上での特殊な操作をトリガーとして発動する機能を作りたい時は、イベントが発生した要素を取得する方法にも気を配りましょう。

例えば「ダブルクリックされたタブを閉じる」機能を実現する場合、単純に考えるとこのようになるでしょう。

gBrowser.mTabContainer.addEventListener(
  'dblclick',
  function(aEvent) {
    var tab = aEvent.originalTarget;
    if (tab && tab.localName == "tab")
      gBrowser.removeTab(tab);
  },
  false
);

しかし、これは実際には期待通りに動きません。例えばタブの中の label 要素や image 要素でイベントが発生した場合、aEvent.originalTarget は tab 要素ではなくなってしまうので、この条件ではマッチしないことになります。なのでもう少し工夫が必要です。

gBrowser.mTabContainer.addEventListener(
  'dblclick',
  function(aEvent) {
    var target = aEvent.originalTarget;
    var parent = target.parentNode;
    if (target && target.localName == "tab")
      gBrowser.removeTab(target);
    else if (parent && parent.localName == "tab")
      gBrowser.removeTab(parent);
  },
  false
);

素の Firefox での利用のみを前提とするなら、これでも十分かもしれません。

しかし実際には、ユーザはそのアドオンを他のアドオンと組み合わせて使うかもしれません。タブの中の label 要素にさらに子要素を追加するアドオンや、tab 要素のバインディングを変更するアドオンと組み合わせた時、このコードでは期待通りに動かなくなります。また、-moz-border-image がサポートされていなかった頃からあるテーマの中にも、バインディングを使ってタブの外観を変えている物はたくさんあります。イベントが発生したノードとその直上の親ノードだけを調べてタブかどうかを判別するというやり方は、この手の変化に対し非常に脆弱です。(直上の親に限らず、「N 階層上の祖先」や「N 番目の子ノード」という調べ方でハードコーディングしてしまうやり方自体がそもそも変化に弱いということです。)

このような場面では、DOM3 XPathSelectors API のように抽象的な指定でノードを取得する仕組みを使うことをお勧めします。

例えば上記の例では、特定のノードを起点として祖先方向に検索を行える DOM3 XPath を使うと問題を解決できます。

gBrowser.mTabContainer.addEventListener(
  'dblclick',
  function(aEvent) {
    var tab = document.evaluate(
                'ancestor-or-self::*[local-name()="tab"][1]',
                aEvent.originalTarget,
                null,
                XPathResult.FIRST_ORDERED_NODE_TYPE,
                null
              ).singleNodeValue;
    if (tab) gBrowser.removeTab(tab);
  },
  false
);

DOM3 XPath では、XPath 式で検索対象のノードを指定します。ここでは ancestor-or-self という指定で「起点ノードもしくはその祖先」の要素ノードを検索して、その中でローカル名が「tab」である1番目の要素(※ XPath 式では N 番目の項目を指定する時、添字は 0 ではなく 1 から始まります)を取得しています。

アドオンを開発する時、特にそれがタブやツールバーなど他のアドオンによって色々と変更される可能性が高い箇所に対して機能を追加するのであれば、Firefox の素の状態からある程度の変化が加わっている可能性を見越して、柔軟に対応できるようにしておくとよいでしょう。また、自分のアドオンが他のアドオンに対して悪影響を及ぼさないよう、加える変化はなるべく少なくする事も大切です。どちらにしても、Firefox のセールスポイントが豊富なアドオンである以上、アドオンの作者は、自分のアドオンが他のアドオンと組み合わせて使われることを前提にして、なるべく衝突の可能性が低くなるように、また、衝突しても容易に対応できるような設計にしておくように気を遣う必要があります(←自戒を込めて)。