XPCOM サービスへの頻繁なアクセスを効率化するテクニック

拡張機能や XUL アプリにて、 JavaScript から特定の XPCOM サービスを頻繁に使用するケースがよくあります。そのような場合に処理やソースコードを効率化するためのテクニックをいくつか紹介します。ここでは、例として nsIObserverService を頻繁に利用するケースを想定します。なお、CcComponents.classes, CiComponents.interfaces への参照です。

方式1: 毎回 XPCOM サービスを呼び出す

特に工夫をしない場合、以下のように XPCOM サービスを利用するたびに毎回そのスコープ内で呼び出し手続きを行うことになります。

var MyExtension = {
    init: function() {
        var observerSvc = Cc["@mozilla.org/observer-service;1"].
                          getService(Ci.nsIObserverService);
        observerSvc.addObserver(...);
    },
    uninit: function() {
        var observerSvc = Cc["@mozilla.org/observer-service;1"].
                          getService(Ci.nsIObserverService);
        observerSvc.removeObserver(...);
    },
};

方式2: グローバル変数として定義

最初に XPCOM サービスの呼び出しを行い、グローバル変数 gObserverSvc として参照を保持します。
この方式ですと、スクリプトロード直後(実際に XPCOM サービスを使うタイミングよりも前)に XPCOM サービスの呼び出しが行われます。したがって、その XPCOM サービスを必ずしも使うとは限らないケースには向いていません。

var gObserverSvc = Cc["@mozilla.org/observer-service;1"].
                   getService(Ci.nsIObserverService);

var MyExtension = {
    init: function() {
        gObserverSvc.addObserver(...);
    },
    uninit: function() {
        gObserverSvc.removeObserver(...);
    },
};

方式3: 拡張機能専用オブジェクトのプロパティとして定義

グローバル変数として定義せずに、拡張機能専用オブジェクト MyExtensionobserverSvc プロパティとして定義します。
この方式ですと、実際に XPCOM サービスを使うタイミング(observerSvc プロパティを参照した時)に XPCOM サービスの呼び出しが行われます。逆に、その XPCOM サービスを使わない場合は無駄に XPCOM サービスの呼び出しが行われることがない、という利点があります。

var MyExtension = {
    get observerSvc() {
        return Cc["@mozilla.org/observer-service;1"].
               getService(Ci.nsIObserverService);
    },
    init: function() {
        this.observerSvc.addObserver(...);
    },
    uninit: function() {
        this.observerSvc.removeObserver(...);
    },
};

方式4: 一度取得した参照をキャッシュ

方式3は、XPCOM サービスを使う(observerSvc プロパティを参照する)たびに毎回 XPCOM サービスの呼び出しが行われるという欠点があります。一方、こちらの方式4では、 observerSvc プロパティが初めて参照された際に、取得した XPCOMサービスへの参照を _observerSvc プロパティ(先頭にアンダーバー付きのプライベート的なプロパティ)として保持し、二回目以降のサービス使用時は保持した参照を返します。

var MyExtension = {
    _observerSvc: null,
    get observerSvc() {
        if (!this._observerSvc) {
            this._observerSvc = Cc["@mozilla.org/observer-service;1"].
                                getService(Ci.nsIObserverService);
        }
        return this._observerSvc;
    },
    init: function() {
        this.observerSvc.addObserver(...);
    },
    uninit: function() {
        this.observerSvc.removeObserver(...);
    },
};

方式5: 一度取得した参照をキャッシュ(改良版)

方式4をさらに改良し、プロパティの数を節約したバージョンです。
observerSvc プロパティへの初回参照時に、プロパティ自身を XPCOM サービスへの参照へと置き換えます。
return a = b; という書き方により、 ab を代入して、さらに a の値を返します。

var MyExtension = {
    get observerSvc() {
        delete this.observerSvc;
        return this.observerSvc = Cc["@mozilla.org/observer-service;1"].
                                  getService(Ci.nsIObserverService);
    },
    init: function() {
        this.observerSvc.addObserver(...);
    },
    uninit: function() {
        this.observerSvc.removeObserver(...);
    },
};

方式6: 一度取得した参照をキャッシュ(クラス対応)

方式5のような単独のオブジェクトではなく、クラスのプロパティとする場合、理由はよくわかりませんが以下のように書く必要があるようです。

function MyExtensionClass() { ... }

MyExtensionClass.prototype = {
    get observerSvc() {
        var svc = Cc["@mozilla.org/observer-service;1"].
                  getService(Ci.nsIObserverService);
        this.__defineGetter__("observerSvc", function() svc);
        return svc;
    },
    init: function() {
        this.observerSvc.addObserver(...);
    },
    uninit: function() {
        this.observerSvc.removeObserver(...);
    },
};

方式7: XPCOMUtils.defineLazyServiceGetter を使う

XPCOMUtils.jsm という標準の JavaScript モジュールをインポートすると、方式5は以下のように defineLazyServiceGetter を使って書くことができます。ただし方式7は Firefox 3.6 (Gecko 1.9.2) 以降限定です。また、 browser.xul 内であれば Firefox 本体側ですでにモジュールがインポート済みなので、拡張機能側で改めてインポートする必要はありません。

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

var MyExtension = {
    init: function() {
        this.observerSvc.addObserver(...);
    },
    uninit: function() {
        this.observerSvc.removeObserver(...);
    },
};

XPCOMUtils.defineLazyServiceGetter(
    MyExtension, "observerSvc", "@mozilla.org/observer-service;1", "nsIObserverService"
);

方式8: Services.jsm を使う

Services.jsm という標準の JavaScript モジュールをインポートすると、ごく一部の XPCOM サービスへの参照を手軽に取得できます。 nsIObserverService であれば Services.obs で参照できます。実は Services.jsm モジュール内部では XPCOMUtils.defineLazyServiceGetter が使用されており、また、すべての拡張機能および Firefox 本体でシングルトンの参照を保持できるという JavaScript モジュールの特性を考えると、最も効率的な方式といえるでしょう。ただし方式8は Firefox 3.7 (Gecko 1.9.3) 以降限定です。また、 browser.xul 内であれば Firefox 本体側ですでにモジュールがインポート済みなので、拡張機能側で改めてインポートする必要はありません。

Components.utils.import("resource://gre/modules/Services.jsm");

var MyExtension = {
    init: function() {
        Services.obs.addObserver(...);
    },
    uninit: function() {
        Services.obs.removeObserver(...);
    },
};

方式9: 独自モジュール内で定義する

方式8に近いですが、拡張機能独自の JavaScript モジュールを作り、頻繁にアクセスする XPCOM サービスへの参照はそのモジュールのプロパティとして定義(その際に方式5や方式7を利用)してしまう手もあります。たくさんの XUL ウィンドウから XPCOM サービスへアクセスするような規模の大きい拡張機能に向いています。
また、そもそも拡張機能のメインプログラムがモジュール化されている場合は、そのモジュール内で方式5や方式7を使ってキャッシュを行うことで効率化が可能です。

私見

個人的には多くの場合は方式5で、グローバル変数を増やしても他への影響が少ない拡張機能独自のウィンドウ内で、それほど効率化を意識する必要の無い場面や単に面倒な場合は方式2、という感じです。最近の Firefox 本体のソースコードを見ると方式7,8が主流となりつつあるようですが、古いバージョンの Firefox にも対応しなければならない拡張機能としては、今のところは対応する Firefox のバージョンが限られてしまうのが難点です。

TOP

TOP