chrome URL からファイル一覧を取得する ― 2011年01月12日 01時41分
Firefox にて、ディレクトリを指す chrome URL から、そのディレクトリ以下の全ファイルの URL を返すサンプルコードを書きました。
ChromeFiles.get("chrome://browser/content/");
/* => [ "chrome://browser/content/NetworkPanel.xhtml",
* "chrome://browser/content/aboutDialog.css",
* ...,
* "chrome://browser/content/browser.css",
* "chrome://browser/content/browser.js",
* "chrome://browser/content/browser.xul",
* ... ]
*/
特徴として、実際のファイルが (.jar
または .xpi
に) パッケージ化されているかどうかに関わらず、ファイル一覧を取得できることが挙げられます。ソースコード全体は上記 Gist へのリンクを参照してもらうとして、以下は各関数の解説です。
var ChromeFiles = {
get: function CF_get(spec) {
const ios = Cc['@mozilla.org/network/io-service;1'].
getService(Ci.nsIIOService);
let uri = ios.newURI(spec, null, null);
return this.getByURI(uri);
},
...
};
単に、文字列として受け取った URL を nsIURI
のインスタンスにして、getByURI()
へ処理を委譲しているだけです。
getByURI: function CF_getByURI(uri) {
// 1. ディレクトリを指す URL にする
let baseURI = uri.clone().QueryInterface(Ci.nsIURL);
baseURI.path = baseURI.directory;
// 2. chrome URL からローカルファイルシステムでの URL へ変換
const registry = Cc['@mozilla.org/chrome/chrome-registry;1'].
getService(Ci.nsIChromeRegistry);
let localURI = registry.convertChromeURL(baseURI);
// 3. ディレクトリ中のファイル名を取得
let leafNames = null;
if (localURI instanceof Ci.nsIFileURL) {
leafNames = this.getLeafNamesByDirectory(localURI.file);
} else if (localURI instanceof Ci.nsIJARURI) {
leafNames = this.getLeafNamesByJARURI(localURI);
} else {
throw new Error('Unknown URI: ' + localURI.spec);
}
// 4. ディレクトリのパスとファイル名を結合
let baseSpec = baseURI.spec;
return leafNames.sort().map(function (leafName) baseSpec + leafName);
},
-
chrome URL において、
chrome://{package}/content/
とchrome://{package}/content/{package}.xul
は同じリソースを表し、nsIIOService#newURI()
の引数に前者を渡しても返ってくる URI オブジェクトのspec
は後者になります。確実にディレクトリを指す URI オブジェクトを得るためには、自分で URI オブジェクトのプロパティを変更しなければなりません。ところが、
newURI()
で作られたオブジェクトは可変でない (immutable な) ことがあり、このときプロパティに値を設定しようとすると例外が発生します。chrome URL の場合clone()
で生成した URI オブジェクトは可変になるので、まずは URI オブジェクトを複製します。パスからディレクトリ部分だけを抜き出すのは、
nsIURL
インターフェースのdirectory
プロパティを使うのが簡単です。nsIURL
インターフェースを経由すれば、ディレクトリ以外にもファイル名や拡張子などをすぐに取得できます。 -
chrome URL からローカルファイルシステム上でのファイル位置をあらわす URL への変換は、
nsIChromeRegistry#convertChromeURL()
で一発です。これにより得られる URL は大抵の場合 file URL か jar URL かのいずれかです。 -
ディレクトリ中のファイル名一覧を配列として取得します。処理本体は file URL の場合と jar URL の場合で別になります。
なお、
QueryInterface()
を使わなくとも、instanceof
演算子でnsIFileURL
インターフェースを実装していることを確認できたなら、それ以降はnsIFileURL
のfile
プロパティからnsIFile
オブジェクトを取得できます。 -
得られたファイル名一覧の順序はわからないので、辞書順で並べ替えます。その後にディレクトリ部分を表す chrome URL と結合すれば、ディレクトリ直下のファイルを指す chrome URL の一覧が得られます。
getLeafNamesByDirectory: function CF_getLeafNamesByDirectory(dir) {
let files = dir.directoryEntries;
let leafNames = [];
while (files.hasMoreElements()) {
let file = files.getNext().QueryInterface(Ci.nsIFile);
if (file.isFile())
leafNames.push(file.leafName);
}
return leafNames;
},
ディレクトリを表す nsIFile
オブジェクトから、その子ファイルの名前一覧を取得します。単に子ファイルを列挙していき、それがディレクトリなどでないときにファイル名を取得するだけです。
getLeafNamesByJARURI: function CF_getLeafNamesByJARURI(jarURI) {
// 1. ZipReader を作成
let zip = this.openZipReader(jarURI.JARFile);
try {
// 2. ディレクトリ直下のファイルのパスを取得
let baseEntry = jarURI.JAREntry;
let pattern = baseEntry + '?*~' + baseEntry + '?*/*';
let entries = zip.findEntries(pattern);
// 3. ファイル名部分だけを抜き出し、返す
let leafNames = [];
while (entries.hasMore())
leafNames.push(entries.getNext().substring(baseEntry.length));
return leafNames;
} finally {
zip.close();
}
},
jar URI は jar:file://path/to/file.jar!/path/to/entry/
のような形で表されます。nsIJARURI
インターフェースは、file://path/to/file.jar
の部分を示す JARFile
プロパティ (返ってくるのは nsIURI
オブジェクト) と、/path/to/entry/
の部分を指す JAREntry
プロパティ (返ってくるのは文字列) を持っています。
-
JAR ファイル (または XPI ファイル) は ZIP 書庫なので、内部のファイル情報を読み取るためには
nsIZipReader
のインスタンスを作成し書庫を開く必要があります。 -
書庫内部のファイル名一覧を取得するには
findEntries()
を使います。ここで指定するファイル名のパターンにおいて、"?"
は任意の1文字を、"*"
は任意の文字列を、"pattern1~pattern2"
はpattern1
にマッチするがpattern2
にはマッチしないものを表します。"/path/to/entry/*"
というパターンでは/path/to/entry/
自信も含まれてしまうので、ディレクトリではないファイルだけを抽出するために"/path/to/entry/?*"
と指定します。また、それだけだと子孫ディレクトリ中のファイルも含まれるので、"/path/to/entry/?*/*"
を除外してやります。 -
findEntries()
で得られた値にはディレクトリ部分も含まれるので、その部分は切り取ってファイル名だけにします。
openZipReader: function CF_openZipReader(uri) {
let zip = Cc['@mozilla.org/libjar/zip-reader;1'].
createInstance(Ci.nsIZipReader);
if (uri instanceof Ci.nsIFileURL) {
// 1. file URL なら単にそのファイルを開く
zip.open(uri.file);
} else if (uri instanceof Ci.nsIJARURI) {
// 2. jar URL なら JAR ファイル内部のファイルを開く
let innerZip = this.openZipReader(uri.JARFile);
zip.openInner(innerZip, uri.JAREntry);
} else {
throw new Error('Unknown URI: ' + uri.spec);
}
return zip;
},
-
JAR ファイルがローカルファイルシステム上に直接存在するなら、単に
open()
メソッドにファイルオブジェクトを渡して開くだけです。 -
開こうとする JAR ファイルが別の書庫内に存在することもあります。その場合は
openInner()
に、JAR ファイルが含まれる書庫と、その書庫内での JAR ファイルのパスを指定してやります。nsIZipReader#openInner()
は Firfox 4 で追加されたものですが、Firefox 4 より前では jar URL がネストすることはないといっていいので、ここで使っても問題ないでしょう。
たとえば Firefox 4 Beta でツリー型タブを使うと、chrome://treestyletab/content/
の実体は jar:jar:file://{profile}/extensions/[email protected]!/chrome/treestyletab.jar!/content/treestyletab/
といった URL になりますが、上記のようにすればその内部のファイル構成を知ることができます。
また、resource://gre/modules/XPCOMUtils.jsm
といった resource URL に関しても、nsIResProtocolHandler#resolveURI()
を使えばローカルファイルシステム上の URL へ変換でき、上と同様にファイル一覧の取得などが可能になります。
最近のコメント