JavaScript で XTF の実装 ― 2005年09月02日 05時31分
この間は JavaScript で XPCOM コンポーネントを作ったので、今回はその発展として XTF の実装に挑戦。XTF (eXtensible Tag Framework) っていうのは XPCOM を用いて新しい XML 要素を実装できるというもので、IE でいうところの Element Behavior (日本語での要約記事) に似てる。Gecko 1.8 (Firefox 1.5) からのサポートなので現時点で試すためには nightly ビルドを使う必要あり。
といってもどんな要素を作ったものかうまく思い浮かばないのでとりあえずカレンダーを作ってみることにする。<calendar xmlns="http://www.ne.jp/asahi/nanto/moon/ns/calendar" />
とすることで今月のカレンダーが表示されるというもの。
ファクトリの作成
XTF ではまず特定の名前空間に対応する XPCOM コンポーネントをひとつ作り、要素の生成はそのコンポーネントが受け持つという形になる。デザインパターンでいうところの Factory Method ってところか。で、そのコンポーネントが実装しなくてはいけないのが nsIXTFElementFactory インターフェース。
interface nsIXTFElementFactory : nsISupports { nsIXTFElement createElement(in AString tagName); };
メンバは createElement
メソッドのみ。というわけでサクッと実装。
function nntCalendarFactory() {}
nntCalendarFactory.prototype = {
createElement: function (tagName)
{
switch (tagName) {
case "calendar":
return new nntCalendarElement();
default:
throw Components.results.NS_ERROR_FAILURE;
}
},
QueryInterface: function (iid)
{
if (iid.equals(Components.interfaces.nsIXTFElementFactory) ||
iid.equals(Components.interfaces.nsISupports))
return this;
Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
return null;
}
};
nntCalendarElement
っていうのが実際の要素に当たるオブジェクト。その解説は後回しにしてとりあえずこれを登録してみる。
const XMLNS_NNT_CALENDAR = "http://www.ne.jp/asahi/nanto/moon/ns/calendar";
var nntCalendarFactoryModule = {
_description: "XTF Calendar Factory",
_classId: Components.ID("{1abb1e29-c7a7-45c7-8670-ba8919f0ae6d}"),
_contractId: "@mozilla.org/xtf/element-factory;1?namespace="
+ XMLNS_NNT_CALENDAR,
...
registerSelf: function (compMgr, fileSpec, location, type)
{
...
compMgr.registerFactoryLocation(this._classId,
this._description,
this._contractId,
fileSpec,
location,
type);
},
...
};
function NSGetModule(compMgr, fileSpec)
{
return nntCalendarFactoryModule;
}
"@mozilla.org/xtf/element-factory;1?namespace=NamespaceURI"
という Contract ID で Factory
を登録することによって、<elementName xmlns="NamespaceURI">
という要素が現れたときに Factory.createElement("elementName")
が呼び出されるようになるらしい。CID (Class ID?) は適宜生成。
要素オブジェクトの作成
次は実際に要素を表すオブジェクトの作成。これは nsIXTFElement インターフェースを実装しなくてはいけない。
function nntCalendarElement() {}
nntCalendarElement.prototype = {
// nsIXTFElement の実装
elementType: Components.interfaces.nsIXTFElement.ELEMENT_TYPE_XML_VISUAL,
isAttributeHandler: false,
getScriptingInterfaces: function (count)
{
var interfaces = [Components.interfaces.nsIDOMElement,
Components.interfaces.nsIDOM3Node,
Components.interfaces.nsIDOMEventTarget];
count.value = interfaces.length;
return interfaces;
},
// nsIXTFVisual (nsIXTFXMLVisual の派生元) の実装
visualContent: null,
insertionPoint: null,
applyDocumentStyleSheets: true,
// nsIXTFXMLVisual の実装
onCreated: function (/* nsIXTFXMLVisualWrapper */ wrapper)
{
...
var builder = Components.classes["@mozilla.org/xtf/xml-contentbuilder;1"]
.createInstance(
Components.interfaces.nsIXMLContentBuilder);
builder.setElementNamespace("http://www.w3.org/1999/xhtml");
builder.beginElement("table");
...
builder.endElement(); // table
this.visualContent = builder.root;
},
// nsISupports の実装
QueryInterface: function (iid)
{
if (iid.equals(Components.interfaces.nsIXTFXMLVisual) ||
iid.equals(Components.interfaces.nsIXTFVisual) ||
iid.equals(Components.interfaces.nsIXTFElement) ||
iid.equals(Components.interfaces.nsISupports))
return this;
Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
return null;
}
};
まずは elementType
にそれがどういう種類の要素か指定する。指定できる値は以下のとおり。指定した値によって追加のインターフェースを実装する必要あり。
値 | 説明 | インターフェース |
---|---|---|
ELEMENT_TYPE_GENERIC_ELEMENT |
表示には反映されない要素を表すみたい。HTML での link 要素とか script 要素とかみたいなものか。 | nsIXTFGenericElement |
ELEMENT_TYPE_SVG_VISUAL |
SVG を使って表示する要素を表すみたい。 | nsIXTFSVGVisual |
ELEMENT_TYPE_XML_VISUAL |
何らかの内容を表示する一般的な要素。とりあえず SVG を使わないのならこれを指定しておけばいいのか? | nsIXTFXMLVisual |
ELEMENT_TYPE_XUL_VISUAL |
XUL 要素と同等の要素? | nsIXTFXULVisual |
ELEMENT_TYPE_BINDABLE |
XBL によるバインディングが可能な要素? | nsIXTFBindableElement |
isAttributeHandler
はとりあえず false
にしておけばよさそう。true
にすると nsIXTFAttributeHandler インターフェースも実装しなくてはいけないから面倒くさそう。
getScriptingInterfaces
で nsIDOMElement
とかを返しておかないと自動的に nsIDOMElement
だと認識されない。つまり document.getElementsByTagName("calendar")[0].nodeType
とかやっても undefined
になる。
void getScriptingInterfaces(out unsigned long count, [array, size_is(count), retval] out nsIIDPtr array);
IDL を見ると out だなんて使われているのでどう実装したらいいものか悩んだが、FAQ によると out の引数にはオブジェクトが渡されるからその value プロパティに値をセットすればいいそうな。配列のほうは JavaScript の配列を自動的に変換してくれる。 retval がついているのは返り値として指定すればいいということみたい。
ここでは elementType
に ELEMENT_TYPE_XML_VISUAL
を指定していたので nsIXTFXMLVisual インターフェースも実装しなくてはいけない。が、その前にその派生元である nsIXTFVisual インターフェースを実装。まずは visualContent
、ここには実際に表示に使われる要素を指定する。今回は onCreated()
の呼び出しの中でセットするが、それ以降は指定される要素自体を変更してはいけない。要素の属性や子要素の変更ならできる。
insertionPoint
には visualContent
に指定した要素か、その子孫要素を指定する。appendChild
とかの実際の適用先がここに指定された要素になる。null
を指定しておけば子要素を挿入しても表示されないらしい。
applyDocumentStyleSheets
は多分読んで字のごとく。製作者スタイルシートを適用するかどうかだと思う。
そして nsIXTFXMLVisual インターフェースの実装。といっても onCreated メソッドしかない。要素の作成時に呼び出される。今回はここでカレンダーを表す表を作成、visualContent
にセットしている。
要素の作成には XMLContentBuilder
を使用。最初に名前空間 URI をセットしてから beginElement(tagName)
と endElement()
、attrib(name, value)
、textNode(text)
などを使って内容を構築していく。
サンプル
サンプルも作成。getScriptingInterfaces
で nsIDOMEventTarget
を返しておけば、addEventListener
でイベントハンドラを登録することもできる。スクリーンショットあり。
パッケージング
そもそも Firefox 1.5 にしか対応していないのでパッケージングも非常に楽。1.0 のことなど考えず chrome.manifest が使えたりする。XPIファイル及びファイル構成は以下のとおり。XPI ファイルをインストールすることで calendar 要素が使用できるようになる。
- XTFCalendar-0.2.0.xpi
- components/
- content/
- chrome.manifest
- install.rdf
- readme.txt
参考文献
- XTF: An eXtensible Tag Framework by Alex Fritze (拙訳)
- seamonkey/content/xtf/public/
- FOSDEM 2004 Presentation by Alex Fritze
- XPConnect and XPIDL FAQ by jband, mang, and mccabe
- Install Manifests - Devmo
- Chrome Registration - Devmo
日本語文字と欧文文字の間のスペース ― 2005年09月02日 14時15分
私は以前は日本語文字と欧文文字の間にスペースを入れていなかった。(メールなどテキストでしか表せない媒体ではスペースを入れることもあったが。) つまり「今日 Firefox を使った」ではなく「今日Firefoxを使った」と表記していたのだ。
なぜか。その理由は CSS 3 に text-autospace プロパティが存在したことによる。CSS で日本語文字と欧文文字 (表意文字と表音文字) の間の間隔を制御できるというのなら、それはスタイルシートによって制御すべき事柄であり、見た目の調整のためにスペースという物理的な文字を入れるべきではないのではと思ったからだ。実際 MS Word や OOo Writer といったワープロソフトではこちらがスペースを入れなくても適宜日本語文字と欧文文字の間隔を調整してくれる。(もちろんこの設定を切ることもできる。)
しかし、周りを見てみると日本語文字と欧文文字の間にスペースを入れているところは多いし、text-autospace も CSS 3 Text Effect Module 20050627 WD から外れたし、IE の text-autospace の先行実装もあまり見栄えがよくなるとは思えないし、何よりもスペースを入れたほうが実際問題読みやすい。なのでこれからは日本語文字と欧文文字の間にスペースを入れていくことにする。
複数のFirefoxをバッチで同時起動 ― 2005年09月02日 16時52分
複数の Firefox を同時起動する話。私は Windows で 1.0 をメインに使っていて、複数の Firefox を同時起動するときというのは nightly をテスト目的に使うときだけなので、以下のようなバッチファイルを使っている。
@echo off
if not exist profile (
mkdir profile
echo // Default Settings> profile\user.js
echo user_pref("browser.cache.disk.capacity", 5000^);>> profile\user.js
echo user_pref("browser.link.open_newwindow", 3^);>> profile\user.js
echo user_pref("browser.shell.checkDefaultBrowser", false^);>> profile\user.js
echo user_pref("browser.startup.homepage", "http://www.google.co.jp/firefox"^);>> profile\user.js
echo user_pref("browser.tabs.autoHide", false^);>> profile\user.js
echo user_pref("dom.event.contextmenu.enabled", false^);>> profile\user.js
echo user_pref("intl.accept_languages", "ja,en-us,en"^);>> profile\user.js
echo.>> profile\user.js
echo // Settings for DOM Inspector>> profile\user.js
echo user_pref("inspector.dom.showWhitespaceNodes", false^);>> profile\user.js
echo.>> profile\user.js
echo // Settings for XUL Development>> profile\user.js
echo user_pref("nglayout.debug.disable_xul_cache", true^);>> profile\user.js
echo user_pref("javascript.options.strict", true^);>> profile\user.js
echo user_pref("javascript.options.showInConsole", true^);>> profile\user.js
echo user_pref("browser.dom.window.dump.enabled", true^);>> profile\user.js
)
set MOZ_NO_REMOTE=1
start firefox.exe -console -profile "profile"
set MOZ_NO_REMOTE=0
これを解凍した (nightly は ZIP 版を使っているので) Firefox のディレクトリ内におき、それを実行するだけ。if の中で echo を連続するよりも別に user.js を作っておいてそれを copy したほうがスマートだろうが、ファイルひとつで収まるのが手軽なのでこうしている。プロファイル名ではなくプロファイルディレクトリを直接指定しているのでほかのプロファイルマネージャに影響を及ぼすこともない。テスト目的なのでブックマークなどを引き継ぐ必要はないが、初期設定だけ済ませておきたいというのに便利だ。
私がこの MOZ_NO_REMOTE のセットにバッチを使う方法を見たのはどこか海外の人のブログだったと思うが、朝顔日記でその意味が詳しく解説されている。MOZ_NO_REMOTE とは DDE を制御するものだそうな。
それにしても -console
をつけると JavaScript コンソールに "Error: Warning: unrecognized command line flag -console" というエラーが出るのはいかがなものか。つけないとコンソールが出てこず、デバッグ用のメッセージを確認できないし。
XTF Readme 和訳 ― 2005年09月04日 00時25分
JavaScript で XTF を実装するにあたって、"XTF: An eXtensible Tag Framework" を訳してみたのだが、翻訳したものをどう扱ったらいいのかわからなかったので公開はしなかった。しかし、Mozilla Japan 翻訳部門に尋ねてみたところ、LXR 上にある文書は Mozilla ソースコードの一部であり、MPL/GPL/LGPL トリプルライセンスが適用されるとのことなので、それに沿って公開することにした。
ということでこれがその「XTF: 拡張可能なタグフレームワーク」。内容がちょっと古いのだが、XTF を実装する上でほぼ唯一の情報源 (ソースコードを除く) といってもいい文書だと思う。かなりの直訳なので読みづらいとは思うが。
Firefox 1.5 の JavaScript の主な変更点 ― 2005年09月04日 15時52分
えむもじらにて Firefox 1.5 の主な変更点が取り上げられているが、JavaScript 関係の話題がないようなので補足しておく。その後 JavaScript に関する話題も追加された。
E4X
E4X (ECMAScript for XML) がサポートされた (Bug 246441) 。これにより JavaScript のデータ型として XML を扱うことができるようになる。サンプルは ECMAScript for XML - d.y.d.などを参照。
Array extras
Array オブジェクトのインスタンスメソッドに indexOf 、lastIndexOf 、forEach 、map 、filter 、some 、every が追加された (Bug 290592) 。詳細については Core JavaScript 1.5 Reference の Array の項を参照 (ひょっとすると 1.5 Reference からは解説が外されるかもしれない) 。
なお、これらに関して関数をマッピングするのだから Array オブジェクトではなく Function オブジェクトのインスタンスメソッドであるべきだという意見が出たが、結局変更はされず代わりに Array/String generics がサポートされた (Bug 304828) 。
Array/String generics
Array オブジェクト、String オブジェクトのインスタンスメソッドが、クラスメソッドとしても利用できるようになった (Bug 304828) 。具体的には someArray.join(", ")
というのが Array.join(someArray, ", ")
とも書けるようになった。
これにより配列のようで配列でないオブジェクト (length 、0 ~ n プロパティを持つオブジェクト、arguments とか DOMNodeList とか) の扱いが楽になる (これまでは Array.prototype.join.apply(arguments)
とかしなければいけなかったのが Array.join(arguments)
とできるようになる) 。例えばよくありそうな getElementsByClassName はこうかける。
function getElementsByClassName(name)
{
return Array.filter(document.getElementsByTagName("*"), function (element) {
return element.className.split(/\s+/).indexOf(name) != -1;
});
}
JavaScript 1.6?
これはまだよくわからないのだが、Firefox 1.5 では以上の変更点をもって対応する JavaScript のバージョンが 1.6 に引き上げられるかもしれない (Bug 306664) 。実際に引き上げられた。個人的には Rhino (Java で書かれた JavaScript エンジン) が 1.6 で E4X をサポートしたのにならって、SpiderMonkey (Firefox に使われている JavaScript エンジン) でもバージョン 1.6 なら E4X を ("E4X=1" パラメータなしで) 解釈してほしいと思っているのだが。
XTF (おまけ)
JavaScript ではないが、XForms のサポート (拡張として提供される) は XTF (eXtensible Tag Framework) のサポートの上に成り立っている。これについての技術的なことは「XTF Readme 和訳」や「JavaScript で XTF の実装」を参考に (と自分の記事の宣伝) 。
最近のコメント