XPCOM コンポーネントの置換2007年06月19日 20時51分

Firefox Developers Conference Summer 2007 での発表の補足も兼ねて、実際に既存の XPCOM コンポーネントを自作のものに置き換える手順を説明していきたいと思います。ちょうど Mozilla でプログラミング (XUL) スレッドで about:blank を置き換える話が出ていたので、それを題材にとってみましょう。

Firefox で about:foo を表示するとき、内部的にはコントラクト ID が "@mozilla.org/network/protocol/about;1?what=foo" である XPCOM コンポーネントが作成されます。このコンポーネントは nsIAboutModule インターフェースを実装している必要があり、その newChannel メソッドで得られたチャンネルの内容がブラウザに表示されます。チャンネルというのはデータストリームの源であり、ある URI のページを Firefox で表示するときは、URI からチャンネルを作成、そのチャンネルからストリームを取得、ストリームの内容を解析して表示、という流れになります。

それでは早速コンポーネントの実装に入りましょう。実装するインターフェースは nsIAboutModule だけでいいので、新しくインターフェースを定義する (IDL を書く) 必要はありません。

function MyAboutBlank() {
}
MyAboutBlank.prototype = {
  newChannel:
  function MAB_newChannel(aURI) {
    var content =
      <html xmlns="http://www.w3.org/1999/xhtml">
        <head>
          <title>My About Blank</title>
        </head>
        <body>
          <p>Hello, about:blank world!</p>
        </body>
      </html>;
    var uriSpec = "data:application/xhtml+xml;charset=utf-8," +
                  encodeURI(content.toXMLString());
    var ios = Cc["@mozilla.org/network/io-service;1"].
              getService(Ci.nsIIOService);
    var channel = ios.newChannel(uriSpec, null, null);
    channel.originalURI = aURI;
    return channel;
  },

  getURIFlags:
  function MAB_getURIFlags(aURI) {
    return Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT;
  },

  QueryInterface:
  function MAB_QueryInterface(aIID) {
    if (aIID.equals(Ci.nsIAboutModule) ||
        aIID.equals(Ci.nsISupports))
      return this;
    throw Cr.NS_ERROR_NO_INTERFACE;
  }
};

newChannel メソッドの役割は上で述べたとおりです。ここでは data URI からチャネルを作成していますが、http URI や chrome URI からチャンネルを作成することもできます。

getURIFlags メソッドでは、その about URI に適用されるフラグを返します。URI_SAFE_FOR_UNTRUSTED_CONTENT を返すことで、一般の Web ページからの読み込み (たとえば、<iframe src="about:blank"> といった使い方) が可能になります。

コンポーネントの登録は通常と代わりありません。クラス ID には新しく生成した UUID を指定します。ただし、コントラクト ID は既存のものを指定します。こうすることで、既存のコントラクト ID に関連付けられたコンポーネントを、自作のものに置換することができます。

const MAB_CLASSID = Components.ID("{80b51603-d8b8-4bcf-aa0b-644f6a196ffe}");
const MAB_CLASSNAME = "My About Blank";
const MAB_CONTRACTID = "@mozilla.org/network/protocol/about;1?what=blank";

var MyAboutBlankFactory = {
  createInstance:
  function MABF_createInstance(aOuter, aIID) {
    if (aOuter)
      throw Cr.NS_ERROR_NO_AGGREGATION;
    return new MyAboutBlank().QueryInterface(aIID);
  }
};

var MyAboutBlankModule = {
  registerSelf:
  function MABM_registerSelf(aCompMgr, aFile, aLocation, aType) {
    var cr = aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
    cr.registerFactoryLocation(MAB_CLASSID, MAB_CLASSNAME, MAB_CONTRACTID,
                               aFile, aLocation, aType);
  },
  
  getClassObject:
  function MABM_getClassObject(aCompMgr, aCID, aIID) {
    if (!aIID.equals(Ci.nsIFactory))
      throw Cr.NS_ERROR_NOT_IMPLEMENTED;
    if (!aCID.equals(MAB_CLASSID))
      throw Cr.NS_ERROR_NO_INTERFACE;
    return MyAboutBlankFactory;
  },

  ...
};

以上を my-about-blank-0.0.1.xpi としてパッケージ化しました。導入することで about:blank が空白ページではなく、「Hello, about:blank world!」というメッセージを表示するものになります。ただし、このままではエラーコンソールでの評価がおかしくなるといった副作用もあります。

なお、Enhanced Feed Preview の場合は、なぜか起動のたびに関連付けが元に戻されてしまうので、browser.xul へのオーバーレイ内で以下のような処理をし、毎回登録を行っています。

var gEnhancedFeedPreview = {
  EXTENSION_ID: "{347b1bed-0fe7-4cdc-921a-51c49e690864}",
  FC_CLASSID: Components.ID("{81adca60-0b9a-405b-a514-6f2cf548fc4b}"),
  FC_CLASSNAME: "Enhanced Feed Stream Converter",
  FC_CONTRACTID: "@mozilla.org/streamconv;1" +
                 "?from=application/vnd.mozilla.maybe.feed&to=*/*",

  init: function EFP_init() {
    ...

    // XXX On trunk, we have to register the component at every statup
    if (!Cc[this.FC_CONTRACTID].equals(this.FC_CLASSID))
      this.registerFeedConverter();
  },

  ...

  registerFeedConverter: function EFP_registerFeedConverter() {
    var cr = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
    var em = Cc["@mozilla.org/extensions/manager;1"].
             getService(Ci.nsIExtensionManager);
    var file = em.getInstallLocation(this.EXTENSION_ID)
                 .getItemFile(this.EXTENSION_ID, "components/FeedConverter.js");

    cr.registerFactoryLocation(this.FC_CLASSID, this.FC_CLASSNAME,
                               this.FC_CONTRACTID, file,
                               "abs:" + file.path, "text/javascript");
  }
};

gEnhancedFeedPreview.init();

コメント

_ foo ― 2008年06月10日 10時25分

上記サンプルコードを見て気になったのですが、

function MAB_newChannel(aURI) {
var content =
<html xmlns="http://www.w3.org/1999/xhtml">
     ~

の部分の代入文って文法的に許されるんでしょうか?
(確かに動作はするようですが・・・)
まるでヒアドキュメントみたいで不思議に感じます。
XPCOMだとこういう書き方ができるんですか?
良ければ教えてください。

_ foo ― 2008年06月10日 16時16分

すいません自己解決しました。
E4Xってやつだったんですね。

_ とろとき ― 2010年10月19日 22時46分

アドオン開発初心者です。
日本語の資料が少なくて困っていたら偶然このサイトを見つけて、参考にさせていただきました。
ありがとうございます!

コメントをどうぞ

※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。

※投稿には管理者が設定した質問に答える必要があります。

名前:
メールアドレス:
URL:
次の質問に答えてください:
「ハイパーテキストマークアップ言語」をアルファベット4文字でいうと?

コメント:

トラックバック

このエントリのトラックバックURL: http://nanto.asablo.jp/blog/2007/06/19/1590545/tb

_ SCRAPBLOG - 2007年06月21日 01時57分

昨年開催された Firefox Developers Conference にて、 Firefox 2 で新たに搭載された Feed Content Access API を利用した独自フィードビューアの実装例を示したのだが、 Firefox 標準のフィードプレビュー (...