XMLHttpRequest を JS XPCOM などから呼び出す

Ajaxなどのために javascript中で XMLHttpRequest を使用する際は,

va req = new XMLHttpRequest();

でオブジェクトを作成できますが,
JS XPCOM などから使用すると, XMLHttpRequestオブジェクト が定義されていません.


JS XPCOMから利用する場合は, このオブジェクトを実装しているコンポーネントを使用します.

var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
        .createInstance(Ci.nsIXMLHttpRequest);
var url = "http://example.com/";
req.open('GET', url, false);
req.send(null);
if(req.status == 200) {
    // なにか処理
}

RDFデータソースの使い方

RDFデータソースの使い方について簡単にまとめてみました.


RDFデータソースはFirefoxでデータを扱う上で重要な要素で,
ブックマークや履歴など様々な場所で利用されている.
XUL の tree 要素を使用する場合にはほぼ必須です.

RDFについて

RDFは,

subject ----> [predicate(property)] ---> target

という関係を表す.
これをトリプル (triple) と呼ぶ.


rdf/xmlで表すと,

<subject>
  <predicate>target</predicate>
</subject>

とか,

<subject  predicate="target" />

とか.


subjectにはリソースの場所を表すためのURIが必要.
URI には, URL や URN などが用いられる.

<subject rdf:about="urn:hoge" predicate="target" />
<subject rdf:about="http://d.hatena.ne.jp/ao3/subjecgt1" predicate="target" />


subject には複数の predicate を設定可能.

<subject predicate1="target1" predicate2="target2" />


コンテナモデルもある.
※追記: コンテナモデルのRDF/XML表現が間違ってたので修正.

 container -- 1:* --> [rdf:li] ---> subject

コンテナの種類には,

  • 順序ありコンテナ (rdf:Seq)
  • 順序なしコンテナ (rdf:Bag)
  • 代替要素を表すためのコンテナ (rdf:Alt)

がある.


rdf/xmlであらわすと,

<rdf:Seq rdf:about="urn:subjects">
  <rdf:li><subject predicate="target1" /></rdf:li>
  <rdf:li><subject predicate="target2" /></rdf:li>
  <rdf:li><subject predicate="target3" /></rdf:li>
</rdf:Seq>


このとき, URI を利用して, ほかの subject を参照可能.

<rdf:rdf xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  <rdf:Seq rdf:about="urn:subjects">
    <rdf:li rdf:resource="urn:hoge:1" />
    <rdf:li rdf:resource="urn:hoge:2" />
    <rdf:li rdf:resource="urn:hoge:3" />
  </rdf:Seq>
  <subject rdf:about="urn:hoge:1" predicate="target1" />
  <subject rdf:about="urn:hoge:2" predicate="target2" />
  <subject rdf:about="urn:hoge:3" predicate="target3" />
</rdf:rdf>


通常, subject, predicate は独自定義する場合, XML-namespace をつける.

<rdf:rdf xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:nc1="http://d.hatena.ne.jp/ao3/person#">
  <rdf:Seq rdf:about="urn:persons">
    <rdf:li rdf:resource="urn:persons:1" />
    <rdf:li rdf:resource="urn:persons:2" />
    <rdf:li rdf:resource="urn:persons:3" />
  </rdf:Seq>
  <nc1:person rdf:about="urn:persons:1" nc1:name="hoge" nc1:age="12" />
  <nc1:person rdf:about="urn:persons:2" nc1:name="foo" nc1:age="32" />
  <nc1:person rdf:about="urn:persons:3" nc1:name="boo" nc1:age="91" />
</rdf:rdf>

使用する XPCOM のサービス

nsIRDFService

データソースの要素 (nsIRDFNode, nsIRDFResource) を取得するために用いる.

var RDFService = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
nsIRDFContainerUtils

コンテナ(nsIRDFContainer)を作成・判別するために用いる.

var RDFContainerUtils = Cc["@mozilla.org/rdf/container-utils;1"]
        .getService(Ci.nsIRDFContainerUtils);

データソースの使用方法

nsIRDFDataSource インタフェースを実装したオブジェクトを用いる.
データソースを処理し, 必要なデータを読み取る方法について説明する.

URI からデータソースを読み込み
var ds = RDFService.GetDataSource("http://d.hatena.ne.jp/ao3/hoge.rdf");

非同期で読み込まれるので ds.loaded が true になるまでは, データがすべて存在するか不明.

URI から読み込んだデータソースを再読み込み
ds.QueryInterface(Ci.nsIRDFRemoteDataSource);
ds.Refresh(false);

Refresh(false) は非同期読み込み. Refresh(true) はブロッキング読み込み.
非同期で読み込むことが推奨されるとのこと.

ArcLabelsIn

指定したリソースを保持しているノード(親ノード?)の一覧を返す.

ArcLabelsOut

指定したリソースの子ノードの一覧を返す.

GetTarget, GetTargets

データソースに対して, subject, predicate を指定して, それに属する target を取得する.

subject --- [predicate] --- >> target <<
var baseURI = "http://d.hatena.ne.jp/ao3/rdf#";
var subject = RDFService.GetResource("urn:datas:1");
var predicate = RDFService.GetResource(baseURI + "text");
var target = ds.GetTarget(subject, predicate, true);

predicateは, nc1:text のような namespace を使った定義ができないため,
URI絶対パス(?)で記述する必要がある.
途中までを定数などにおいておくと記述が楽になる.

GetSoruce, GetSources

データソースに対して, predicate, target を指定して, それを保持する subject を取得する.

>> subject << --- [predicate] --- target
var baseURI = "http://d.hatena.ne.jp/ao3/rdf#";
var predicate = RDFService.GetResource(baseURI + "text");
var target = ds.GetLiteral("foo");
var subject = ds.GetTarget(predicate, target, true);
コンテナモデル

nsIRDFContainer, nsIRDFContainerUtils を使う.

var ds;	// とにかく データソース がある.
var containerResource = RDFService.GetResource("urn:datas"); // コンテナのURI

// コンテナモデルか判定する
if (! RDFContainerUtils.IsContainer(ds, containerResource) ) {
    return false;
}

// containerResource をコンテナモデルとして初期化
var container = Cc["@mozilla.org/rdf/container;1"]
        .createInstance(Ci.nsIRDFContainer);
container.Init(ds, containerResource);

// コンテナに属するノードを列挙
var sources = container.GetElements();
while(sources.hasMoreElements()) {
  var subject = sources.getNext().QueryInterface(Ci.nsIRDFResource);
  var predicate = RDFService.GetResource(baseURI + "text");
  var target = ds.GetTarget(subject, predicate, true);
}

独自のデータソースの作成方法

データソース作成
var ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
        .createInstance(Ci.nsIRDFDataSource);
リソース作成

subject, predicate を作成する際に用いる.

var resource = RDFService.GetResource("urn:datas");
リテラル作成

target を作成する際に用いる. GetLiteral() 以外は使ったことがないので動作は不明.

var literal = RDFService.GetLiteral("foo");
var intLiteral = RDFService.GetIntLiteral(1);
var dateLIteral = RDFService.GetDateLiteral(new Date()); // これでいいか不明
トリプル作成
const baseURI = "http://d.hatena.ne.jp/ao3/rdf#";
var subject = RDFService.GetResource("urn:datas:1");
var predicate = RDFService.GetResource(baseURI + "text");
var target = RDFService.GetLiteral("hogehoge");
ds.Assert(subject, predicate, target, true);
コンテナモデルの利用

nsIRDFContainerUtils を利用する.

// container を作成
var containerResource = RDFService.GetResource("urn:datas");
var container = RDFContainerUtils.MakeSeq(ds, containerResource);

// container に subject を追加
var subject = RDFService.GetResource("urn:datas:1");
container.AppendElement(subject);

// subject に predicate を追加
ds.Assert(subject,
      RDFService.GetResource(baseURI + "text"),
      RDFService.GetLiteral("hogehoge"), true );

データソースをファイル (RDF/XML) へ保存

// 書き出すファイル
var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
file.initWithPath("c:\\dump.rdf");

// 出力ストリーム
var fos = Cc["@mozilla.org/network/file-output-stream;1"]
        .createInstance(Ci.nsIFileOutputStream);
fos.init(file, -1, -1, 0);

try {
    // シリアライザ
    var serializer = Cc["@mozilla.org/rdf/xml-serializer;1"]
            .createInstance(Ci.nsIRDFXMLSerializer);

    // DataSourceのセット
    serializer.init(dataSource);

    // 書き出し
    serializer.QueryInterface(Ci.nsIRDFXMLSource);
    serializer.Serialize(fos);
} finally {
    fos.close();
}

Catalyst 8.12公開されてる!

Catalyst8.12がリリースされました。
インストールすることで、ATI Streamが有効になるようです。

うちのPCはRadeon HD4670なので、さっそくドライバをいれてみて、Stream対応のビデオトランスコーダ AVIVO Video Converterを使ってみようと思います。
感想は後日。


そのうちStreamを使ったプログラミングとかしてみたいなぁ。


まぁ当分は, Firefoxとの戦いですけど...


追記:
ドライバのダウンロード、akamaiのネットワークつかってるようなので、2MB/sとかでたりしました。すごい高速。
が、SSLの警告で足りFirefoxだとページエラーになったり。よくわからないことに。IEにかえるとすんなり動いた。

たまに resource:// が動作しない・・・

Component.utils.import を積極的に使ってみているんですが,
たまに NS_ERROR_NOT_AVAILABLE というエラーが出て, 「自作ライブラリ」の読み込みが一切できなくなりました.
※ XPCOMUtils.jsm はエラーにならず.


XPの場合, 一度アドオンを削除したら直ったんですが,
Vistaの場合, 最初から全くエラーが出て読み込みできません.


Component.utils.import はショートカットなので, その実体を直接使ってもエラーに.
ソースコードを追っかけてみると,

if (!mFastLoadFile || !flSvc) {
return NS_ERROR_NOT_AVAILABLE;
}

http://mxr.mozilla.org/firefox/source/js/src/xpconnect/loader/mozJSComponentLoader.cpp#857

たぶんここに引っかかってるんじゃないかと思うわけですが.
ちゃんと目を通して無いんで不明.


あと mozIJSSubScriptLoader を使って, resource:// のパスを読み込むと, パスを全く認識してないようなエラーメッセージがでました.
しかし, アドレスバーにこのパスを貼り付けると, 中身がそのまま表示されます.


ちなみに, resource:// のパスの定義は chrome.manifest で独自定義しています.
もしかしたら, ここに原因があるんじゃないかという気もします.
こっちは, ProtocolHandler をみればいいのかな・・・


にしても, はてなはタイトル入力中に間違ってEnter押すと投稿されちゃって不便だ.
本文の空白チェックぐらいつけてほしい.
実際のデータは本文/タイトルの区別がないから仕方ないんだろうけど.

JS XPCOMを再読み込みする

JS XPCOMを作成していたらソースを変更したのに反映されないという問題がおきました。


以下のファイルを消すと再読み込みできます。(Windows XPの場合)

C:\Documents and Settings\<ユーザ名>\Application Data\Mozilla\Firefox\Profiles\<プロファイルディレクトリ>\compreg.dat

※追記: ファイル名書いてませんでしたorz

XPCOMUtilsを使おう

Firefox3でしか使えませんが、
Firefox拡張機能開発で、

  • QueryInterface
  • NSGetModule

メソッドを書くとき、やたら長いコードを毎回書かなくてはならず、めんどくさくてしょうがない方のためのモジュールです。


https://developer.mozilla.org/Ja/XPCOMUtils.jsm
http://mxr.mozilla.org/mozilla/source/js/src/xpconnect/loader/XPCOMUtils.jsm


ソースコードに使い方が書いてありますが、改めて説明。

XPCOMUtilsを読み込む

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

QueryInterface の書き方

XPCOMUtils.generateQI([Components.interfaces.nsIRunnable]);

これだけで、QueryInterfaceの処理を実行してくれるFunctionオブジェクトを生成してくれます。
インターフェースを複数指定する場合は,配列に並べるだけ。
nsISupportsは暗黙的に追加されます。

NSGetModule の書き方

JS XPCOMを作る場合に, NSGetModuleメソッドを作ることになりますが、この手順をXPCOMUtilsで省略できます。

// XPCOMの実装
function MyComponent{}
MyComponent.prototype = {
   classDescription: "クラスの説明",
   classID: Components.ID('{GUIDをここに書く}'),
   contractID: "@example.com/mycomponent;1",
// _xpcom_factory: オプション,
// _xpcom_categories: オプション,
   QueryInterface: XPCOMUtils.generateQI([Components.interfaces.myIComponent])
}

// NSGetModuleを定義
function NSGetModule(aCompMgr, aFileSpec) {
   return XPCOMUtils.generateModule([MyComponent]);
}

という感じ。お手軽です。

ちなみに, XPCOMUtils.generateNSGetModule という NSGetModule を定義するメソッドも用意されてます。
ソースコードの説明では, なぜかこっちを使っていませんでした。

オプションと書いている, _xpcom_factory と _xpcom_categories について。

_xpcom_factory は createInstance で実行されるコードを記述できます。
XPCOMのサービスを定義する場合にSingletonで実装しておきたい・・・などの場合に使えます。

デフォルトの動作で書いてみると、

  _xpcom_factory : {
     createInstance: function(aOuter, aIID) {
        return (new MyComponent()).QueryInterface(aIID);
     },
     QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIFactory])
  }

という実装になるようです。


「カスタムファクトリを私用する場合, オブジェクトは nsIFactory を実装している必要があるよ」
と書いてありますので, 一応 QueryInterface で nsIFactory を定義しています。


_xpcom_categories は、 nsICategoryManager.addCategoryEntry()に渡すパラメータを指定するようです。

char* addCategoryEntry ( char* category , char* entry , char* value , PRBool persist , PRBool replace )

http://www.xulplanet.com/references/xpcomref/ifaces/nsICategoryManager.html

nsICategoryManager.addCategoryEntry()の役割を把握してないので、
ソースコードの説明を貼り付けておきます。ついでに訳してみます。

_xpcom_categories: [{
  // Each object in the array specifies the parameters to pass to
  // nsICategoryManager.addCategoryEntry(). 'true' is passed for
  // both aPersist and aReplace params.
  // 配列中の各オブジェクトの示すパラメータを
  // nsICategoryManager.addCategoryEntry() に渡します.
  // パラメータの aPersist と aReplace には 'true' が渡されます.
  category: "some-category",
  // optional, defaults to the object's classDescription
  // オプション: デフォルトは オブジェクトの classDescription を使います.
  entry: "entry name",
  // optional, defaults to the object's contractID (unless
  // 'service' is specified)
  // オプション: デフォルトは オブジェクトの contractID を使います.
  // ( (下の) 'service'が指定されない場合)
  value: "...",
  // optional, defaults to false. When set to true, and only if 'value'
  // is not specified, the concatenation of the string "service," and the
  // object's contractID is passed as aValue parameter of addCategoryEntry.
  // オプション: デフォルトではfalseです. trueを指定し, (上の)'value'を指定しない場合,
  // 文字列 "service," を連結したオブジェクトの contractID を
  // addCategoryEntry の aValueパラメータに渡します。
  service: true
}]

拡張機能開発でOSネイティブソケットを使う

Firefox拡張機能開発の話。


既存のXPCOMオブジェクトを利用して、javascriptで通信プログラムを書くのに挫折気味です。
あまりにブルーな気分になったので、オープンソースなライブラリでも使用してやろうかと思い、まずCのsocket関係の関数って直接使えるのか・・・という疑問があり試してみました。


とりあえず、bind, listen、acept, recv, sendなどの基本的な関数をXPCOMでラップしてポートが開けるのかテスト。
普通に開けてacceptも可能でした。
当然ながら、acceptするとスレッドがブロックするので、nsIThreadとかnsIThreadPoolとかと組み合わせてスレッドを使ってみましたが普通に動作しました。


socket関係の関数を呼び出そうとすると、関数の参照が置き換えられてsecurity sandboxに引っかかるとかはないようです。
まぁそんなことする側も大変ですしね。
当然と言えば当然か。
これで代替案が使えそうです。
# が、とあるライブラリを使おうと思ってみたものの、サンプルコードはともかく、リファレンスページがnot foundとはこれいかに・・・。


まぁ移植性考えたら全部,javascriptで書くべきなんでしょうけどね。