アドオン(拡張機能)にショートカットキーを追加する

追記@2008/02/14
Mac OS XFirefoxだと、動的に扱う部分で一部挙動が違う場合があるようです。
まとまり次第、追記したいと思っています。

                                  • -

アドオンにキーボードショートカットを追加したくなったので、調べたことをメモしていきます。(Windows XPFirefox 2.0.0.11で確認)
まだ書きかけです。すいません。一通り書きました。動的に扱う部分は最初に書いたものから大幅に変わってしまいました。。(2008/02/07)


キーボードショートカットの追加方法は、下記のドキュメントが参考になりました。

メニューと関連したキーボードショートカットを定義する

とりあえず上記ドキュメントを参考に、メニューを追加し、それに対してキーボードショートカットを割り当てる、、といったよくあるパターンを書いてみました。
browser.xulに下記をoverlayします。

<?xml version="1.0" encoding="UTF-8"?>
<overlay id="testOverlay"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <commandset id="mainCommandSet">
    <command id="testCommand" oncommand="alert('test command');"/>
  </commandset>
  <menupopup id="menu_ToolsPopup">
    <menuitem id="testMenuitem"
      label="test"
      insertbefore="sanitizeSeparator"
      accesskey="T"
      key="testKey"
      command="testCommand" />
  </menupopup>
  <keyset id="mainKeyset">
    <key id="testKey" modifiers="accel shift" key="B" command="testCommand" />
  </keyset>
</overlay>

キーボードショートカットは、key要素で指定します。
key要素のmodifiers属性が修飾キー(controlやaltなど)で、key属性は表示可能文字(Aとか1とか)でkeycode属性が表示不可能文字(F1やESCなど)を指定するときに使います。
modifiers、key、keycode属性で指定可能な文字は、先ほどのXUL Tutorial:Keyboard Shortcuts - MDC に記載されているので参考になります。
key要素に指定可能な属性は、下記で確認できます。

menuitem要素のkey属性に、key要素のidを指定することにより、メニューにキーボードショートカットが表示されます。

キーボードショートカットだけ追加する

キーボードショートカットはメニューと紐付ける必要もないので、単にkeyset、key要素を書くだけでも問題ありません。

<?xml version="1.0" encoding="UTF-8"?>
<overlay id="testOverlay"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <keyset id="mainKeyset">
    <key id="testKey" modifiers="accel shift" key="B" oncommand="alert('test command');" />
  </keyset>
</overlay>

ショートカットを動的に扱う

キーボードショートカットをXMLとして書かず、JavaScriptによるDOM操作で追加したり、既に定義しているキーボードショートカットを変更、削除することも出来ます。
が、タイミングによって反映され方が違ったりするので、とてもわかりずらかったです。。
動作としては、言葉だけで説明すると、下記のような感じでした。

  • ブラウザ起動後、何かキーが押されるまでは、key要素の追加、属性の上書きが自由。そのままキーボードショートカットとして反映される。
  • キーが押された時点で、キーボードショートカットの一覧?が作られるようで、これ以降は下記のような感じになる。
    • key要素のmodifiers、key属性を変えても反映されない
    • 既に存在するkeyset要素に、新たなkey要素を追加してもそのkey要素の内容は反映されない。
      • 新たにkeyset要素を作って、そこにkey要素を設定してあげれば反映できる。
    • key要素のoncommand属性は特殊で、なにかキーを押された後でも上書き(setAttribute)できる。
      • しかし、そのショートカットキーが押下され、oncommandの内容が一度実行されると、上書きできなくなる。
      • ただし、removeAttributeしてからsetAttributeすれば変更可能。
  • disabled属性はいつでも変えられる。
  • removeChildなどで、keyset要素からkey要素を外すと、そのキーボードショートカットは削除される。

上記での反映されないというのは、キーボードショートカットに反映されないということであって、DOM的には追加されています。アドオンのkeyconfigはDOMをたどって一覧作っていて、そっちには変更した内容が見えてきます。


キー押下前と押下後で動作が違ってくる現象を、下記のようなコード確認できます。

<?xml version="1.0" encoding="UTF-8"?>
<overlay id="testOverlay"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <keyset id="mainKeyset">
    <key id="key1" modifiers="alt" key="0" oncommand="alert('変更前のコマンドです。');" />
  </keyset>
  <script type="application/x-javascript">
  <![CDATA[
    // 何かキーを押していると、keyとmodifiersの変更は反映されない
    // oncommandは、そのショートカットが実行されていなければ上書き可能
    window.addEventListener('load', function() {
      setTimeout(function() { // 10秒後にkey要素を変更
        var key = document.getElementById('key1');
        key.setAttribute('modifiers', 'control');
        key.setAttribute('key', 'Q');
        key.setAttribute('oncommand', "alert('変更後のcommandです。')");
        alert("key要素を変更しました。");}, 10000);
    }, false);
  ]]>
  </script>
</overlay>

上記は、browser.xulが描画後10秒たったらkey要素を変更するようにしており、key要素を変更する前の操作でショートカットの内容が変わってきます。

  1. なにもキー操作を行わなかった場合
    • key属性に対する変更は全て反映される。
      • Ctrl+Q で 「変更後のcommandです。」とalert表示される。
  2. ショートカット以外のキー(例えば"1"や"a"など)を押下した場合
    • modifiersとkey属性の変更はキーボードショートカットに反映されない。
      • キーボードショートカットは、Alt+0のまま
    • command属性に対する変更は反映される。
      • 「変更後のcommandです。」とalert表示される。
  3. ショートカットキー(Alt+0)を押下した場合
    • key属性に対する変更はまったく反映されない。
      • Alt+0で 「変更前のcommandです。」とalert表示される。


以降、動的に扱う例を記載します。

キーボードショートカットを追加する(キー押下前)

追加はキー押下前ならば、既に存在するkeyset要素に追加しても、新たなkeyset要素に追加しても反映されます。

<?xml version="1.0" encoding="UTF-8"?>
<overlay id="testOverlay"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <keyset id="mainKeyset">
    <key id="key1" modifiers="alt" key="1" oncommand="alert('key1です。');" />
  </keyset>
  <script type="application/x-javascript">
  <![CDATA[
    window.addEventListener('load', function() {

      // 既にあるkeysetに追加する場合
      var keyset = document.getElementById('mainKeyset');
      // 新しいkeysetを作って追加する場合
      //var keyset = document.documentElement.appendChild(document.createElement('keyset'));

      var key = keyset.appendChild(document.createElement('key'));
      key.setAttribute('modifiers', 'alt');
      key.setAttribute('key', '2');
      key.setAttribute('oncommand', "alert('key2です。')");

    }, false);
  ]]>
  </script>
</overlay>

onload時にキーボードショートカットを追加するというのは、結構使い道あるんじゃないかなと思ってます。
例えば、preferencesで設定されている内容に応じてキーボードショートカットの内容を変えるとか。

キーボードショートカットを追加する(キー押下後)

キー押下後の場合は、既にあるkeyset要素に対して追加しても反映されないため、新たなkeyset要素に対して追加する必要があります。
下記の例では、Alt+1で、Alt+2のキーボードショートカットを追加しています。

<?xml version="1.0" encoding="UTF-8"?>
<overlay id="testOverlay"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <keyset id="mainKeyset">
    <key id="key1" modifiers="alt" key="1" oncommand="addKey2();" />
  </keyset>
  <script type="application/x-javascript">
  <![CDATA[
    function addKey2() {

      // 新しいkeysetを作って追加しないと、key要素の内容は登録されない
      var keyset = document.documentElement.appendChild(document.createElement('keyset'));

      var key = keyset.appendChild(document.createElement('key'));
      key.setAttribute('modifiers', 'alt');
      key.setAttribute('key', '2');
      key.setAttribute('oncommand', "alert('key2です。')");

      alert('key2を追加しました。');
    }
  ]]>
  </script>
</overlay>

キーボードショートカットを変更する(キー押下前)

キー押下前にキーボードショートカットを変更する場合なんてないような気がしますが、サンプルとして、、
キー押下前ならば、key要素に対する変更は全てキーボードショートカットとして反映されます。

<?xml version="1.0" encoding="UTF-8"?>
<overlay id="testOverlay"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <keyset id="mainKeyset">
    <key id="key1" modifiers="alt" key="1" oncommand="alert('変更前のcommandです。');" />
  </keyset>
  <script type="application/x-javascript">
  <![CDATA[
    window.addEventListener('load', function() {

      var key = document.getElementById('key1');
      key.setAttribute('modifiers', 'control');
      key.setAttribute('key', 'Q');
      key.setAttribute('oncommand', "alert('変更後のcommandです。')");

    }, false);
  ]]>
  </script>
</overlay>

この例だと、Ctrl+Qで「変更後のcommandです。」と表示されるキーボードショートカットのみ登録されます。

キーボードショートカットを変更する(キー押下後)

キー押下後は、key要素に対して属性を変更しても反映されません。
そこで、新しくkeyset要素を作成し、そこにkey要素を移し変えて、属性を変更するようにします。
この際、oncommand属性は上書きでは反映されない(一度実行されると、張り付いたままとなる)ので、一旦属性を削除し設定します。

<?xml version="1.0" encoding="UTF-8"?>
<overlay id="testOverlay"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <keyset id="mainKeyset">
    <key id="key1" modifiers="alt" key="1" oncommand="changeKey1();" />
  </keyset>
  <script type="application/x-javascript">
  <![CDATA[
    function changeKey1() {

      // 新しいkeysetを作成し、そこに移す
      var keyset = document.documentElement.appendChild(document.createElement('keyset'));
      var key1 = keyset.appendChild(document.getElementById('key1'));

      key1.setAttribute('modifiers', 'control');
      key1.setAttribute('key', 'Q');

      // oncommand属性は上書きじゃ反映されないので一旦削除してから追加
      key1.removeAttribute('oncommand');
      key1.setAttribute('oncommand', "alert('変更後です。')");

      alert('変更しました。');
    }
  ]]>
  </script>
</overlay>

キーボードショートカットを無効にする

key要素のdisabled属性をtrueにすると、ショートカットは無効になります。

<?xml version="1.0" encoding="UTF-8"?>
<overlay id="testOverlay"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <keyset id="mainKeyset">
    <key id="key1" modifiers="alt" key="1" oncommand="changeKey2();" />
    <key id="key2" modifiers="alt" key="2" oncommand="alert('key2です。');" />
  </keyset>
  <script type="application/x-javascript">
  <![CDATA[
    function changeKey2() {

      var key2 = document.getElementById('key2');

      if (key2.getAttribute('disabled')) {
        key2.removeAttribute('disabled');
        alert('有効にしました');
      } else {
        key2.setAttribute('disabled', true);
        alert('無効にしました');
      }
    }
  ]]>
  </script>
</overlay>

Alt+1を押下するたびに、Alt+2の無効/有効を切り替えています。


再度有効にすることがないのならば、key要素自体を消してしまってキーボードショートカットを削除するのでも良いと思います。

<?xml version="1.0" encoding="UTF-8"?>
<overlay id="testOverlay"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <keyset id="mainKeyset">
    <key id="key1" modifiers="alt" key="0"
      oncommand="this.parentNode.removeChild(this); alert('削除しました');" />
  </keyset>
</overlay>

参考:キーボードショートカットの確認

キーボードショートカットを確認するのに、keyconfigというアドオンを使うと便利です。
keyconfigを使うことにより、どんなキーボードショートカットが定義されているかを一覧で確認できます。

アドオンをたくさんいれていると、キーボードショートカットが重複することがよくありますが、keyconfigで特定のキーボードショートカットを削除(元にも戻せます)、割り当ての変更をすることにより、重複を回避できます。
また、新たなキーボードショートカットを追加することも出来てとても便利です。


なお、途中でも書きましたが、keyconfigはDOMからkey要素を探し出して表示しているので、動的にkey要素の属性を変えてしまった場合には、実際のキーボードショートカットと異なる内容が表示される場合があるので注意が必要です。