XTF インターフェース変更2006年10月09日 12時12分

気づいたら XTF のインターフェースが大幅に変更されていた。nsIXTFVisual インターフェースとそれに関連する部分が削除されている。全体的な構成はかなりすっきりしたが、前に書いた XTF の実装はもう通用しない。

削除されたうちで、onCreated メソッドは nsIXTFElement インターフェースに取り込まれたが、visualContent 、insertionPoint 、applyDocumentStyleSheets プロパティなどに関しては削除されたままだ。こういった機能を使いたいときは XBL を使えということか。XBL だと動的に要素を作成した際、文書ツリーに挿入するまではその要素特有の機能が使えないのが嫌なのだが。それとも onCreated 時に nsIDOMDocumentXBL.addBinding() を使うことで解決せよというのか。

しかし自分たちが使わなくなったからってあっさり削除するあたり、XTF なんて誰も使っていないと思っているんだろうな。

Firefox 2 RC3 の予感2006年10月09日 12時58分

Firefox 2 RC3 が出そうだ。もうちょっと早くバグが報告されていれば / 催促していれば出さずにすんだかもしれないとは思うけど。このままだと IE 7 に遅れをとりそうだな。まあ分割代入のデコンパイルやら何やらにかかりきりでうっかり忘れていた /be の人が悪いって事で :P

ということで Bugzilla を見ているうちに「ぶれんたんはドジっ子属性つきのマッドサイエンティスト」という電波を受信した。そういえばもえじら組はなつかしの W3C 子本を出したりはしないのだろうか?

IE 7 を判別する JavaScript コード2006年10月14日 07時57分

Abe Fettig’s Weblog » Detecting IE7+ in Javascript (日本語紹介記事) にて style.maxHeight プロパティを用いた IE 6 と 7 の判別方法が紹介されている。ただ、この方法だと IE とそれ以外を判別するためにもう一手間かけなくてはいけないので、IE 7 以上だけを判別できるようにしたのが以下。

if (typeof document.documentElement.style.msInterpolationMode != "undefined") {
  // IE 7 or newer
} else {
  // IE 6 or older, Gecko, Opera, etc.
}

IE 7 から実装された独自 CSS プロパティ、-ms-interpolation-mode を利用。画像を拡大するときの補完方法を指定するものらしい。それにしてもまさか IE が独自プロパティに接頭辞をつける日がこようとは! DOM オブジェクトのプロパティ名にしたときに "MsIntepolationMode" ではなく "msInterpolationMode" となるのは微妙なところだが。あと、document.body は body 要素の開始タグが現れるまで参照できないので document.documentElement を使用した。

なお、元記事中で条件付きコメントJScript の条件コンパイルについても話題に挙がっていたが、条件付きコメントは Multiple Explorers のような環境で期待通り動作しないし、スクリプトエンジンはレイアウトエンジンと関係なく更新可能なはずなので、確実性にやや劣ると思う。User-Agent 文字列はこの件に関しては役に立たない。Microsoft が直々に User-Agent 文字列を変更するツールを公開していたりするので (しかしこのツール、IE 7 用なのになぜか対応 OS に Windows XP SP1 が含まれている)。

それと、IE 7 のスクリプトエンジンのバージョンはどうなっているのだろうか? 元記事のコメントでは 5.7 という情報が出ているが、Browsershots で使われている IE 7 では 5.6.8833 となっている (IE 6 SP2 では 5.6.8831)。Beta 3 か RC1 かで更新されたのか? IE 7 RC1 で確認したところ 5.7.5700 だった。一応確認用に IE のレイアウトエンジン (が提供する DOM オブジェクト) とスクリプトエンジンのバージョンチェックページを置いておく。レイアウトエンジンは IE 5.01 相当なのにスクリプトエンジンは 5.5、条件付きコメントでは IE 6 として扱われるなんてことも起こりうるが。

inject、畳み込み2006年10月16日 22時06分

IT戦記 - Perl で再帰呼出し時のスタック間データ共有を読んだ時点では気づきもせず、Kazuho@Cybozu Labs: JavaScript で Generic Programming にいたってようやく思い当たったのだが、Ruby の Enumerable#injectPython の reduce というのはこういうときに使うものなのかもしれない。

// requires Prototype.js
function count_tags(node) {
  return $A(node.childNodes).inject({}, function (tag, kid) {
    if (kid.nodeType != 1) return tag;
    tag[kid.nodeName] = (tag[kid.nodeName] || 0) + 1;
    return $A(kid.childNodes).inject(tag, arguments.callee);
  });
}

さて、このような畳み込み関数の名前は inject、reduce、foldl など言語によりばらばらで、逆に言えばこれぞというべき名前がないようで、Ruby のフォーラムでも inject というメソッド名はわかりづらいというトピックがあった。代替案として出ていたのは combine、each_with_state、accumulate など。個人的には、reduce は何を「減らす」のよくわからず、foldl は JavaScript の命名習慣から外れているようで、「リストの要素間に二項演算を『注入』する」という解釈も含めて inject が好みなのだが、もし JavaScript に採用されることがあっても名前は reduce になるんだろうなという気がする。

ところで、畳み込み関数について検索していたら、foldl.com というサイトを見つけた。JavaScript を有効にする必要があるがなかなか面白いサイトだ。ここで、ふと思いついて foldr.com とアドレスバーに入力してみたら……。いやはや、盛大に笑わせてもらった。

プリミティブ値でもプロトタイプ的継承2006年10月18日 23時50分

書き上げた後に元記事の続きが出ているのに気づいたが、方向性が違うようなのでそのまま掲載。

404 Blog Not Found:javascript - プロトタイプ的継承 (元記事: Prototypal Inheritance) より。継承という言葉は意味が広いので、この操作に対してはチャイルドの作成といったほうが個人的にはわかりやすい。

さて、元記事で紹介されているコードではプリミティブ値からのチャイルドの作成 (継承) ができなかった。これはなぜかといえば、オブジェクト作成の際、プリミティブ値をプロトタイプ ([[Prototype]] 内部プロパティ、__proto__ プロパティ) に設定することはできないからである。

そこで、プリミティブ値が渡された場合は、それをラッパオブジェクトに変換することにする。といっても場合分けの必要はない。Object 関数を使えば、プリミティブ値が渡されたときは対応するラッパオブジェクトを、オブジェクトが渡されたときはそれをそのまま返してくれる。

function object(o) {
  function F() {}
  F.prototype = Object(o);
  return new F();
}

var myStr = object("子");
myStr.x = function (n) {
  var s = "";
  while (n--) s += this;
  return s;
};
print(myStr.x(12)); // doesn't work as expected

ところが、このままでは期待通りには動かない。JScript では空文字列が出力されるし、JavaScript (SpiderMonkey) では例外 (TypeError: String.prototype.valueOf called on incompatible Object) が投げられる。

その理由は、Kazuho@Cybozu Labs: JavaScript の String 型を継承するでも触れられているとおり、String#toString および String#valueOf が String 型以外のオブジェクト上では (そのオブジェクトのプロトタイプが String 型のオブジェクトであったとしても) 呼び出せないからだ。ECMAScript 仕様ではこのことを not generic (汎用的ではない) と表現している。

これは何も String 型に限った話ではない。Array#toString も汎用的ではなく、実際 JScript では以下のように文字列に変換しようとすると例外が投げられる。

String(object([1, 2, 3]));
// Microsoft JScript 実行時エラー: Array オブジェクトがありません。

対応策として考えられるのが、汎用的でないメソッドが呼び出されたときには処理をプロトタイプに委譲してしまうことだ。一部の実装では __proto__ プロパティからプロトタイプにアクセスできるので以下のように書ける。

var myStr = object("子");
myStr.valueOf = function () {
  return this.__proto__.valueOf();
};
myStr.x = function (n) { ... };
print(myStr.x(12)); // 子子子子子子子子子子子子

しかし、一般に ECMAScript ではオブジェクトのプロトタイプにアクセスする手段が提供されていない。そこで、チャイルドを作成する際に汎用的でないメソッドを上書きすることにする。

function object(o) {
  o = Object(o);
  function F() {}
  F.prototype = o;
  var obj = new F();
  var methods = ["toString", "toLocaleString", "valueOf"];
  for (var i = 0; i < methods.length; i++)
    with ({ method: methods[i] })
      obj[method] = function () { return o[method].apply(o, arguments); };
  return obj;
}

var myStr = object("子");
myStr.x = function (n) { ... };
print(myStr.x(12)); // 子子子子子子子子子子子子

これでプリミティブ値からもチャイルドを作成できるようになった。とはいえ、このままでは Number#toFixedDate#getTime などは使えないが、それらにも対応したものを prototypal.js として置いておく。