nsIProcess.init 実行時に MacOS X で NS_ERROR_FAILURE が発生する問題

自作の Firefox 拡張機能 *1 の互換性の報告の中に 「MacOS X Snow Leopard において nsIProcess.init が失敗する (例外が発生する)」 というものがあったのでちょっと調べてみました。 具体的な例外のメッセージは以下のとおり。

Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIProcess.init]

nsIProcess とは

nsIProcess とは XPCOM コンポーネントの 1 つで、OS のプロセスを扱うためのものです。 詳しいドキュメントは以下にあります。

例えば、Linux において /usr/bin/firefox というプログラムを新たに起動するためには、以下のように JavaScript から XPCOM を使用します。

// 実行するファイルのパス
var exe_path = "/usr/bin/firefox";

// 実行するファイルをあらわす nsILocalFile コンポーネントの作成
var exe_file = Components.classes["@mozilla.org/file/local;1"].createInstance( Components.interfaces.nsILocalFile );
exe_file.initWithPath( exe_path );
// 実際はここで file が存在するかどうかや実行可能かどうかを調べる必要あり

// nsIProcess を作成してプロセスを起動する
var process = Components.classes["@mozilla.org/process/util;1"].createInstance( Components.interfaces.nsIProcess );
process.init( exe_file );
// プロセスの起動 (最初のパラメータが true なら、スレッドはプロセスが終わるまでブロックされる)
// args は引数として渡す文字列の配列. ここでは引数は渡していない
var args = new Array();
process.run( false, args, args.length );

上記のコード中で使用している nsILocalFile コンポーネントは、ローカルディスク上のファイルをあらわすものです。 これは nsIFile を継承しているので、そこで定義されているメソッドやプロパティも使用できます。 詳しいドキュメントは以下。

MacOS X でエラーが発生する原因

さて、当然ながら実行するファイルのパスとして存在しないパスや実行できないファイルを指定すると例外が発生するのは当然なのですが、なぜか MacOS X では /Applications/Calculator.app というような実行可能なファイル (のように見えるもの) を nsIProcess で実行しようとすると、冒頭のエラーが発生してしまいました。

ネット上をあさってみると次のような情報が見つかりました。

どうやら、MacOS X で /Applications/Calculator.app のようなバンドルアプリケーションは、実行可能なファイルに見えるものの実際にはファイルではなくディレクトリであり、その中の Contents/MacOS/appname が本当の実行可能ファイルである、ということのようです。 つまり、/Applications/Calculator.app を起動したいのであれば、/Applications/Calculator.app/Contents/MacOS/Calculator を起動するように nsIProcess に指示する必要があるようです。

ちなみに、nsIFile のメソッドを使って /Applications/Calculator.app がどのようなファイルなのか調べると、次のような結果となりました。

// /Applications/Calculator.app を表す nsILocalFile の取得
var path = "/Applications/Calculator.app";
var file = Components.classes["@mozilla.org/file/local;1"].createInstance( Components.interfaces.nsILocalFile );
file.initWithPath( path );

// ファイルなのか? -> ファイルではない
file.isFile(); //=> false
// ディレクトリなのか? -> ディレクトリである
file.isDirectory(); //=> true
// 実行可能か? -> 実行可能である (しかし nsIProcess に渡すと例外発生)
file.isExecutable(); //=> true

回避方法

この記事のはてブのコメントid:teramako さん、id:amino_acid9 さんが指摘されているように、元々ここに書いてあった方法は正しい回避方法ではなかったので、元々の内容は削除しました。

id:teramako さんがよりよい回避方法を下記記事にて述べられていますのでそちらをご覧ください。

*1:AppLauncher という、Firefox から外部アプリケーションを起動するための拡張機能。 詳しくは AppLauncher - Add-ons for Firefox