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
コメント
トラックバック
このエントリのトラックバックURL: http://nanto.asablo.jp/blog/2005/09/02/60454/tb
コメントをどうぞ
※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。
※投稿には管理者が設定した質問に答える必要があります。