Firefox 3ではじめる拡張機能開発

第4回機能を実装する(後編)

中編から引き続き、選択範囲の文字列からブックマークへタグ付けする処理などをJavaScriptで実装していきます。

その前に、処理を実装するために必要となるPlaces APIについて簡単な紹介をします。

Places API

ここからは、選択範囲の文字列からブックマークへタグ付けする処理などを実装していきますが、その前に、処理を実装するために必要となるPlaces APIについて簡単な紹介をします。

ご存知の通りFirefox 3ではブックマークと履歴の管理機能が一新されましたが、この新しい管理システムを「Places」と呼びます。Placesでは、ブックマークや履歴を操作するための多数のAPIがXPCOMサービスとして提供されいます。そのうち、今回使用するAPIの概要を表1に示します。

表1 使用するPlaces API(XPCOMサービス)の概要
XPCOMサービス概要
nsINavBookmarksServiceブックマークの追加、編集、削除といった一般的な操作を行う。
Manipulating bookmarks using Places - MDC
nsINavBookmarksService - MDC
nsITaggingServiceブックマークへのタグ付け、タグの削除などを行う。
Using the Places tagging service - MDC
nsITaggingService - MDC

表1に挙げた以外にも様々なAPIが存在します。詳しくは下記URLをご覧ください。

ブックマーク済みかどうかのチェック

ここからは、選択範囲を取得する処理の前に、現在表示しているWebページのURLがブックマーク済みかどうかをチェックする処理を追加します。また、ブックマーク済みでない場合は警告メッセージを表示して処理を終了するようにします。⁠overlay.js」リスト1の内容を追加してください。

リスト1 ⁠overlay.js」への記述内容4(ブックマーク済みかどうかのチェック処理を追加)
var TagHelper = {
  generateTags: function() {
    var url = window.content.location.href;
    var ioSvc = Cc["@mozilla.org/network/io-service;1"].
                getService(Ci.nsIIOService);
    url = ioSvc.newURI(url, null, null);
    var bookmarksSvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
                       getService(Ci.nsINavBookmarksService);
    if (!bookmarksSvc.isBookmarked(url)) {
      alert('"' + window.content.document.title + '" is not bookmarked.');
      return;
    }
    var keywords = [];
    var sel = window.content.getSelection();
    for (var i = 0; i < sel.rangeCount; i++) {
      var keyword = sel.getRangeAt(i).toString();
      keyword = keyword.replace(/^\s+|\s+$/g, "");
      keywords.push(keyword);
    }
    Application.console.log("keywords = " + keywords);
    sel.removeAllRanges();
  },
};

あるURLがブックマーク済みかどうかを調べるには、表1に示したnsINavBookmarksServiceのisBookmarkedメソッドを使用します。nsINavBookmarksServiceのリファレンスに記載されている通り、isBookmarkedはnsIURIオブジェクトを引数とし、指定したURLがブックマーク済みかどうかを真偽値で返します。そこで、リスト1では、まずnsIIOServiceを使用してURLの文字列(変数「url⁠⁠)からnsIURIオブジェクトを生成します。次に、nsINavBookmarksServiceを呼び出し(変数「bookmarksSvc⁠⁠、isBookmarkedによって先ほどのnsIURIオブジェクトがブックマーク済みかどうかを調べ、戻り値がfalseなら警告を表示して終了します。

なお、リスト1では「Cc」「Ci」を使用していますが、これらは「browser.xul」から読み込まれるFirefox自体のJavaScriptソースファイル内で定義された定数で、それぞれ「Components.classes」⁠Components.interfaces」への参照です。

ブックマークへのタグ付け

ここからは、選択範囲を取得する処理の後に、現在表示しているWebページのURLに対するブックマークに対し、選択範囲の文字列からタグ付けする処理を追加します。⁠overlay.js」リスト2の内容を追加してください。

リスト2 ⁠overlay.js」への記述内容5(ブックマークへのタグ付け処理を追加)
var TagHelper = {
  generateTags: function() {
    var url = window.content.location.href;
    var ioSvc = Cc["@mozilla.org/network/io-service;1"].
                getService(Ci.nsIIOService);
    url = ioSvc.newURI(url, null, null);
    var bookmarksSvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
                       getService(Ci.nsINavBookmarksService);
    if (!bookmarksSvc.isBookmarked(url)) {
      alert('"' + window.content.document.title + '" is not bookmarked.');
      return;
    }
    var keywords = [];
    var sel = window.content.getSelection();
    for (var i = 0; i < sel.rangeCount; i++) {
      var keyword = sel.getRangeAt(i).toString();
      keyword = keyword.replace(/^\s+|\s+$/g, "");
      keywords.push(keyword);
    }
    Application.console.log("keywords = " + keywords);
    if (keywords.length > 0) {
      var taggingSvc = Cc["@mozilla.org/browser/tagging-service;1"].
                       getService(Ci.nsITaggingService);
      taggingSvc.tagURI(url, keywords);
    }
    sel.removeAllRanges();
  },
};

ブックマークに対してタグ付けするには、表1に示したnsITaggingServiceのtagURIメソッドを使用します。nsITaggingServiceのリファレンスに記載されている通り、tagURIはnsIURIオブジェクトとタグ付けする文字列の配列を引数とします。そこで、リスト2では、タグ付けする文字列の配列(変数「keywords⁠⁠)の要素が1個以上あるかを調べてから、nsITaggingServiceを呼び出し(変数「taggingSvc⁠⁠、tagURIによってタグ付けを行います。

JavaScriptコードモジュールを利用する

JavaScriptコードモジュールとは、Firefox 3で新たに導入された機能で、異なるJavaScriptのスコープ間で共有可能なシングルトンのオブジェクトを容易に実現することができます。例えば、複数のXULから利用される関数を大量に定義するような場合、それらをひとつのJavaScriptファイルにまとめて各XUL内で<script>タグで読み込むよりも、JavaScriptコードモジュール化して各XULへインポートしたほうがメモリの面などで効率が良いといった利点があります。

もちろん拡張機能が独自にJavaScriptコードモジュールを定義することもできますが、Firefox 3自体が定義している便利なJavaScriptコードモジュールがいくつかあり、これらを拡張機能から利用することもできます。そのうちのひとつである「PlacesUtils」は、Places APIがより利用しやすい形にまとめられています。あるJavaScriptのスコープで「PlacesUtils」をインポートして利用可能にするためには、リスト3のように記述します。

リスト3 PlacesUtilsのインポート
Components.utils.import("resource://gre/modules/utils.js");
 

ただし、今回色々と機能を実装している「TagHelper」オブジェクトのスコープでは、すでにFirefox自体によって「PlacesUtils」がインポートされていますので[1]⁠、リスト3の内容を記述しなくても「PlacesUtils」が利用可能です。

JavaScriptコードモジュールは、そのソースコード内で定義された配列「EXPORTED_SYMBOLS」の各要素と同一の名前を有するJavaScriptのオブジェクト(およびその全プロパティ)が外部から利用可能となります。⁠PlacesUtils」オブジェクトで利用可能な各プロパティについての説明は、下記URLにて解説されていますが、場合によっては「PlacesUtils」自体のソースコードを見たほうが早いかもしれません[2]⁠。

「PlacesUtils」で定義されたプロパティのうち、今回使用するものを表2に示します。これらのプロパティを利用することで、リスト2の内容はリスト4のように簡潔に書き直すことができます。

表2 使用するPlacesUtilsのプロパティ
プロパティ詳細
PlacesUtils.bookmarksnsINavBookmarksServiceへの参照。
PlacesUtils.taggingnsITaggingServiceへの参照。
PlacesUtils._urinsIIOServiceでURL文字列からnsIURIオブジェクトを生成する[3]⁠。
リスト4 ⁠overlay.js」への記述内容6(PlacesUtilsを利用して書き換え)
var TagHelper = {
  generateTags: function() {
    var url = window.content.location.href;
    url = PlacesUtils._uri(url);
    if (!PlacesUtils.bookmarks.isBookmarked(url)) {
      alert('"' + window.content.document.title + '" is not bookmarked.');
      return;
    }
    var keywords = [];
    var sel = window.content.getSelection();
    for (var i = 0; i < sel.rangeCount; i++) {
      var keyword = sel.getRangeAt(i).toString();
      keyword = keyword.replace(/^\s+|\s+$/g, "");
      keywords.push(keyword);
    }
    Application.console.log("keywords = " + keywords);
    if (keywords.length > 0) {
      PlacesUtils.tagging.tagURI(url, keywords);
    }
    sel.removeAllRanges();
  },
};

右クリックメニュー項目の表示制御

ここからは、DOMイベントリスナの仕組みによって、右クリックメニュー表示時に選択範囲の有無を調べてタグヘルパーのメニュー項目を表示/非表示する処理を追加します。⁠overlay.js」リスト5の内容を追加してください。

リスト5 ⁠overlay.js」への記述内容7(右クリックメニュー項目の表示制御処理を追加)
var TagHelper = {
  init: function() {
    var contextMenu = document.getElementById("contentAreaContextMenu");
    contextMenu.addEventListener("popupshowing", this, false);
  },
  destroy: function() {
    var contextMenu = document.getElementById("contentAreaContextMenu");
    contextMenu.removeEventListener("popupshowing", this, false);
  },
  generateTags: function() {
    var url = window.content.location.href;
    url = PlacesUtils._uri(url);
    if (!PlacesUtils.bookmarks.isBookmarked(url)) {
      alert('"' + window.content.document.title + '" is not bookmarked.');
      return;
    }
    var keywords = [];
    var sel = window.content.getSelection();
    for (var i = 0; i  0) {
      PlacesUtils.tagging.tagURI(url, keywords);
    }
    sel.removeAllRanges();
  },
  handleEvent: function(event) {
    if (event.type == "popupshowing") {
      var selected = getBrowserSelection() != "";
      document.getElementById("taghelper-menu").hidden = !selected;
    }
  }
};

window.addEventListener("load", function() { TagHelper.init(); }, false);
window.addEventListener("unload", function() { TagHelper.destroy(); }, false);

リスト5の最後の2行は、⁠overlay.js」読み込み直後に実行されます。DOM:windowに対してイベントリスナを登録し、イベント発生時に以下の2処理を実行します。

  • ブラウザウィンドウを開いた直後(⁠⁠browser.xul」ロード完了に伴う「load」イベント発生時)「TagHelper.init」を実行する。
  • ブラウザウィンドウを閉じた直後(⁠⁠browser.xul」アンロード完了に伴う「unload」イベント発生時)「TagHelper.destroy」を実行する。

「TagHelper.init」は、右クリックメニューへ自分自身(⁠⁠TagHelper」オブジェクト)をイベントリスナとして登録し、メニューを開いた直後(⁠⁠popupshowing」イベント発生時)「handleEvent」が呼び出されるようにします[4]⁠。⁠TagHelper.destroy」「TagHelper.init」の逆で、登録したイベントリスナを解除します。⁠TagHelper.handleEvent」では、念のため発生したイベントが「popupshowing」であることを確認し、⁠chrome://browser/content/browser.js」で定義された関数「getBrowserSelection」を使って選択範囲の文字列を取得し、空であればメニュー項目を非表示にします。

動作確認

ここまでのソースコード記述が完了したら、動作確認を行います。Firefoxの新しいウィンドウを開き、以下の3点を確認してください。

  • ブックマークされていないWebページ上で範囲を選択し、右クリックから「Generate Tags from Selection」メニュー項目を選択すると、警告メッセージが表示される。
  • ブックマーク済みのWebページ上で範囲を選択し、右クリックから「Generate Tags from Selection」メニュー項目を選択すると、選択範囲の文字列からタグ付けされる。
  • 範囲を選択していない状態で右クリックすると、メニュー項目「Generate Tags from Selection」が非表示となる。

まとめと次回の予告

中編・後編とひたすらJavaScriptのコードを書いてタグヘルパーの機能を実装しました。機能のオブジェクト化、XPCOMサービス呼び出しよる高度な処理、DOMイベントリスナによるユーザの操作に応じた処理などは、拡張機能を開発する上では基本といえるでしょう。また、Firefox 3ならではのトピックとして、以下の3点についても触れました。

  • FUEL
  • Places API
  • JavaScriptコードモジュール

ブックマークや履歴に関する拡張機能を開発するのであれば、Places APIの利用は欠かせないでしょう。また、JavaScriptコードモジュールはお手軽かつ有用であり、規模の大きな拡張機能を開発するのであれば、ぜひ導入を検討してみてはいかがでしょうか。

次回はタグヘルパーへlocaleパッケージを追加して拡張機能のUIをローカライズ可能にします。

おすすめ記事

記事・ニュース一覧