Mozilla 24 行動記録2007年09月25日 19時21分

さる 9 月 15 ~ 16 日に Mozilla 24 へ行ってきました。大体の様子は他のブログなどで知れ渡っているかと思いますが、とりあえず私の行動記録を少々。

  • 9 月 15 日 4:30 電車内で参加賞を印刷し忘れたことに気づき焦るも、Twitter にて os0x さんyoshiori さんに携帯での確認方法を教えてもらい一安心。
  • 5:05 東京駅着。
  • 6:55 仮眠と時間つぶしをかねて山手線を 1 周半、池袋駅着。
  • 7:30 バーガーキングにて朝食。ブレックファストメニューのクロワッサンウィッチ・ハムを頼むも、通常メニューも頼めるということを後から知り、ワッパーにしておけばよかったかと少し後悔。
  • 8:40 上野駅着。鶯谷から歩いてこようかとも思ったが、時間と土地勘のなさ、そして暑さにより断念。
  • 9:10 不忍池、めがねの碑、正岡子規記念球場を巡る。
  • 9:20 国立科学博物館へ。シアター 360 に圧倒される。
  • 11:20 科博より撤退。お昼を食べてから会場入りのつもりだったがその時間がなくなる。
  • 11:55 ベルサール九段に到着。入り口にて Kuruma さんSaito さんと一緒に。
  • 12:00 DocFest M24 開始、と思いきや設営などの関係で開始は 12:30 からとのこと。
  • 13:00 DocFest M24 開始。実はこれが OmegaT の使い初め。
  • 14:30 対訳ユーザー辞書仕様 UTX に関するプレゼンテーションおよびディスカッション。1 時間の予定が話題も広がり最終的に 2 時間近くに。
    • シリアライズ形式として XML を採用。
    • 訳語の分野を明確化することにより、文脈に合った訳が可能に。
    • 作成者・作成日時といったメタ情報を付加して、管理・共有しやすく。
  • お昼のお弁当が余ったとかで分けてもらう。
  • 18:30 出張 Shibuya.js 24 に参加。開始直前にホール入りし、終了直後に DocFest へ戻ったため、特に誰とも交流することなし。もったいないことをした。
  • 20:30 DocFest 会場に戻るとカツサンドが山のように積まれていた。
  • Taken さんMDC 支援プロジェクト翻訳文書新規作成支援ボットの存在を教えてもらう。
  • Shimono さんに、title-override をつけるのは breadcrumbs を正しく表示させるためのおまじないでもあると教えてもらう。
  • 新規翻訳文書には英語版へのリンクさえつけておけば、後はボットが英語版を含む他言語版のページに言語間リンクをつけてくれると教えてもらう。
  • 16 日 0:30 事務局にて仮眠。
  • 3:00 ビンゴに参加。リーチは一番乗りだったものの、結局ビンゴならず。
  • トイレで小飼弾さんに合うも、挨拶だけで終わってしまった。
  • 6:00 賞味期限切れの弁当を食べませんかとお誘いがかかるものの、代表理事じきじきにストップがかかったとかで食べられず。
  • 7:00 朝の弁当が到着したとのことでいただく。
  • 9:00 事務局にて仮眠。
  • 10:30 DocFest 成果発表。Core JavaScript 1.5 ReferenceStatements の項をすべて訳せるかと思っていたが、結局 3 分の 2 程度しか訳せず。
  • 11:30 ベルサール九段から退出。お土産に Firefox カステラ、カロリーメイトポテト味、余った駄菓子類をいただく。カツサンドの最後の一包みももらったので駅弁代わりの昼食とした。

食料は豊富でしたが、夜のお弁当を食べ逃したのが心残りです。翻訳も Twitter やら何やらで脱線していなければもっと進められたかも。

それにしても翻訳支援ソフトというものは、一度使い出すともう手放せませんね。E4X 邦訳なんて、よくテキストエディタだけで訳せたものだと今になって思います。

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();

Firefox Developers Conference Summer 20072007年06月19日 20時50分

Firefox Developers Conference Summer 2007 の「拡張機能作者による大ライトニングトーク」に発表者として参加させていただきました。以下が発表資料です。中身は JavaScript による XPCOM コンポーネント作成方法のダイジェストということで、あまり「拡張機能作者」とは関係ないものになってしまいました。また、補足として、XPCOM コンポーネントの置換を行う例を書きました。

プレゼンテーション機能は Firefox 2 以上でないと使えません (現在のTrunkでもエラーになる?)。その他のブラウザではデフォルトスタイルシートによる表示となります。また、Atom 版でプレゼンテーション機能を利用するには、Enhanced Feed Preview などを導入する必要があります。なお、プレゼンテーション機能は、解像度 1024 × 768、フルスクリーンでの利用を前提としているので、それ以外の環境では適宜文字サイズを拡大縮小してください。

皆さん興味深い話題が目白押しでしたが、それはほかの方のレポートに譲るとして、以下個人的に Gomita さんへしゃべったことを載せておきます。

  • UCJS Loader に、Overlay XUL を読み込む機能がないのは、私自身がその機能を使っていないから。
  • userChrome.js 0.8 への移行手段として示された、borwser.xul 以外なら例外を投げるという手段は、UCJS Loader のアプローチでは対応できない。
  • mozIJSSubScriptLoader や JavaScript コンポーネントが UTF-8 として読み込まれない (Latin-1 として読み込まれる?) のは困るが、まだ Bugzilla に登録などはしていない。(mozIJSSubScriptLoaderに関してはバグがたっていました。)
  • (userChrome.js 用スクリプトも、Greasemonkey 用スクリプトのように、メタ情報を解釈してほしいという要望が挙がっていることに関して) そういう機能を userChrome.js 本体に取り込んでしまうと、userChrome.js の利点である「軽さ」が犠牲になってしまうのではないか。

E4X in Firefox 発表資料2007年04月22日 20時11分

Mozilla Party JP 8.0 に講師として参加させていただきました。以下はプレゼンテーション用のスライドです。

OnpenOffice.org 2.0 の Impress を使って作ったのですが、私のノート PC のディスプレイ接続がうまくいかず、お借りしたマシンに入っていた OpenOffice.org 2.1/2.2 ではなぜか実行途中にフリーズするので、PDF にエクスポートしたものを用いました。

また、発表は 25 分の枠だったのですが、最初に練習したときは 1 時間かかってしまい、話す内容をいろいろと削ったものの、結局枠ぎりぎりまで使い切ってしまって、質疑応答の時間をとることができませんでした。もし何か聞きたいことがあったという方がいらっしゃれば、このコメント欄にお願いします。

それから、発表中に話したこともそうでないことも含め、上記のスライドに関する補足をいくつか挙げておきます。

XML 型と XMLList 型の相互変換に関して、1 項目の XMLList が XML として扱えるだけでなく、XML が 1 項目のみを含む XMLList として扱われる場合もあります。
XML 名前空間
:: の前に指定した変数は、その文字列化した値が使われます。ですから、名前空間 URI を扱うだけなら、ns = "http://example.org/" のように、Namespace オブジェクトを使わなくともかまいません。ただし、名前空間接頭辞の情報も保持したいといったときは、Namespace オブジェクトを使う必要があります。
SpiderMonkey による拡張
XML/XMLList 型は [[Get]]/[[Put]] といった内部メソッドが上書きされています。これにより、存在しないプロパティ名が指定されたとき、プロトタイプチェーンをたどるのではなく、空 XMLList を返すといった動作が実現されます。しかし、function 名前空間を使うことで、通常のオブジェクトと同じく、プロパティの検索にプロトタイプチェーンを使うといった動作にすることができます。
スコープチェーン上に XML オブジェクト x がある状態で式 method() を評価したとき、ECMAScript ではスコープチェーンを順にたどり、method という名前のプロパティを持つオブジェクトを探します。しかし、x が method という名前の子要素を持っていなければ、x は method という名前のプロパティを持たないことになってしまいます。そのため、list.(method()) では期待通りに動かない場合があります。
AttributeName コンストラクタに指定可能な引数は、QName コンストラクタに指定可能なものと同じです。
SpiderMonkey のバグ (1)

関数内でデフォルト名前空間を指定した場合、非修飾名を解決するのにデフォルト名前空間が使われない場合があります。たとえば、以下のコードでは "Default Namespace" と表示されるはずですが、"No Namespace" と表示されてしまいます。

var ns = new Namespace("http://example.org/");
(function () {
  default xml namespace = ns;
  var x = <a>
    <b>Default Namespace</b>
    <b xmlns="">No Namespace</b>
  </a>;
  print(x.b); // No Namespace
})();

この問題は、スコープチェーンを深くすることにより解決できます。

(function () {
  ...
  with ({}) print(x.b); // Default Namespace
})();
(function () {
  ...
  (function () { print(x.b); })(); // Default Namespace
})();

また、スコープチェーンを深くする操作があれば、それが非修飾名によるプロパティアクセスのあとであっても、解決することがあります。

(function () {
  ...
  print(x.b); // Default Namespace
  with ({});
})();
SpiderMonkey のバグ (2)
for-in 文、for-each-in 文を XML オブジェクトに対して使用すると、その XML オブジェクトは XMLList に変換されます。ですから、変換された XMLList が唯一持つプロパティ、すなわち元の XML オブジェクトそのもののみが列挙されるはずです。しかし、SpiderMonkey は誤って、XML オブジェクトが持つプロパティ、すなわちその XML の子ノードを列挙してしまいます。
DOM との変換
XMLSerializer はコンストラクタ名こそ XMLSerializer ですが、実装するインターフェース名は nsIDOMSerializer です。XPCOM コンポーネントとして呼び出すときなどには注意してください。
DOM ノードから E4X XML オブジェクトに変換するコードに関して、実際には node が文書ノードなら、そのルート要素に対して XMLSerializer を適用するなどの工夫が必要です。また、XMLSerializer は DocumentFragment にも対応しているので、XMLList コンストラクタを使うことで、DocumentFragment から XMLList への変換もできるようになります。
使用例 (2)
実際に Google カレンダーのフィードをパースするソースコードHTML エクスポートを行うソースコードをご覧ください。
ヒアドキュメントとしての使用に関して、XMLList リテラルを使えば式の埋め込みが簡単にできます。また、CDATA セクションリテラルを使うと、小なり記号 (<) やアンパサンド (&) 、開き波括弧 ({) などをエスケープする必要がなくなります。
資料
cho45 さんの「えへへうふふ E4X」が参考になります。また、ActionScript に関する Web ページで E4X について触れているものもあります。

「使用例 (1)」で述べた、Greasemonkey 用スクリプトと userChrome.js 用スクリプトの例を以下に置きます。

Greasemonkey 用スクリプト (スクリーンショットソース)
閲覧中のページに対するはてなブックマークでのコメントを表示します。さらに、ブックマーク数におけるコメントつきブックマーク数の割合を、SVG を用いてグラフ描画します。
E4X による要素の作成、XML オブジェクトから DOM ノードに変換する xml2dom 関数の実装など。
worris さんの「はてなブックマークコメントビューワ」を参考にしました。
userChrome.js 用スクリプト (スクリーンショットソース)
Web 検索バーに入力した文字列を用いて、ページ内検索もできるようにします。
E4X を用いた Overlay ファイルの動的生成、CDATA セクションのリテラル表記を用いたヒアドキュメントの実現など。
Georges-Etienne Legendre さんの SearchWP を参考にしました。

さらに、あとで Piro さんから質問があったので、その概要と回答も載せておきます。(回答はそのとき答えたことに少し付け加えています。)

E4X を使ったときのパフォーマンスはどうなのか?
今回の発表に当たってはパフォーマンスに関して調べていませんが、要素のネストが深くなると文字列化が非常に遅くなるというバグがあります。また、今回示した DOM ノードとの相互変換の手法は文字列を介したもので、DOM ノードと文字列との変換、E4X と文字列との変換がそれぞれ必要になるので、パフォーマンスはあまり良くないと思われます。なお、ヒアドキュメントとしての使用に関しては「えへへうふふ E4X」でも触れられていますが、通常の文字列連結に比べパフォーマンスの点で大きく劣るという結果が出ています。
DOM ノードとの相互変換は Firefox 1.5 でも可能か?
今回示した手法なら Firefox 1.5 から使用可能です。
ヒアドキュメントとして CDATA セクションのリテラル表記を使う際の注意点は?
後方互換性の関係から、XML オプションが有効になっていないと、CDATA セクションリテラルを直に書くことはできません。HTML 中で XML オプションを有効にするためには、<script type="text/javascript; e4x=1"> のように e4x パラメータを 1 にセットするか、<script type="text/javascript; version=1.6"> のように 1.6 以上のバージョンを指定しないといけません。XUL 中または XPCOM コンポーネント中では、最初から XML オプションが有効になっているため、このような指定を明示的に行う必要はありません。また、XML/XMLList リテラル内ならば、XML オプションの状態に関わらず、CDATA セクションを記述することができます。なお、CDATA セクションリテラルの評価結果は、テキストノードを表現する XML オブジェクトとなります。

E4X 話 in Mozilla Party2007年04月20日 00時31分

ここでの告知がぎりぎりになりましたが、明日 4 月 21 日の Mozilla Party JP 8.0 に、講師として参加します。話す内容は E4X について。……ええ、かぶってますね、思いっきり……「えへへうふふ E4X」と……。いや、Shibuya.es の告知を見たときに、これはまずいと思ったんですけどね……、それ以外に話すネタも思い浮かばずに……。まあ復習にはぴったりということで。Firefox に特化した話もちょっと出てきますし。

そんな程度の話ですが、既に私の頭は「みんなジャガイモ」(© 広末涼子) 祭りの結果、逆にパニックを起こしそうなので、生暖かい目で見守ってくださると幸いです。それから、質問意見文句は大歓迎! むしろ 25 分間それで埋めてもいいくらいです。