Hatena::ブログ(Diary)

vivid memo このページをアンテナに追加 RSSフィード

vivid code というサイトのメモ代わりに記事を書いていました。
現在ははてなブログに移行し、「ひだまりソケットは壊れない」 というブログで記事を書いています。 はてな id も id:nobuoka に変更しました。

2011-01-15

Firefox の拡張機能で全てのウィンドウで共通の 1 つの処理を行う (または異なるウィンドウ間で情報の共有を行う)

Firefox の拡張機能の話。 バックグラウンドで行うべき何らかの処理 *1 があるとします。 以下のように load イベントを捕捉してその処理を起動すると、新しくウィンドウ *2 を開くたびにそのバックグラウンドの処理が行われてしまいます。

/** 名前空間用オブジェクト */
var myapp = {};
/** バックグラウンドで行う処理を起動する関数 */
myapp.launch_background_proc = function() { ... };
/** 初期化用関数 */
myapp.onLoad = function(evt) {
    myapp.launch_background_proc();
};
// ブラウザの新しいウィンドウを開くたびにイベント発生
// (XUL の overlay 要素の中で実行した場合)
window.addEventListener( "load", myapp.onLoad, false );

これが望む動作なのであればいいのですが、全てのウィンドウで 1 つの処理を共有させたい (つまり、2 個目以降のウィンドウを開いた場合には新たにバックグラウンドで行う処理を起動したくない) 場合 *3 には違う方法を採らなければいけません。 ここではそれを簡単に行うための方法を紹介します。

JavaScript コードモジュールを使う

全てのウィンドウで共通の 1 つの処理を行うための方法はいくつかあります。 例えば、既に起動しているウィンドウを全て調べ (nsIWindowMediator を使用) て、バックグラウンドで実行する処理がまだ起動されていない場合だけ新たに起動する、という方法でもできなくはありません。 しかし一番簡単なのは JavaScript コードモジュールを使う方法だと思います。

JavaScript コードモジュールを使うと、特権を持った全てのスコープで共通の JavaScript オブジェクトを生成できます。 すなわち、わざわざ他の全てのウィンドウを調べて既にバックグラウンドで行う処理が起動されているかどうかを調べる、などということはせずに、JavaScript コードモジュールのオブジェクトに情報を持たせておき、それを見て起動するかどうか決める、ということができます。

使用方法

詳しい使用方法は以下にあります。

*1:例えば、API を使用して絶えず twitter と通信を行い、常に最新の情報を保持しておくような処理。

*2:ここでいうウィンドウとは、JavaScript のグローバルオブジェクト (window) を新たに作るもののことを言います。 詳しくは chrome コードでウィンドウを取り扱う方法を述べた文書 を見てください。

*3:例えば twitter との通信を例に考えると、最新の情報は 1 箇所で保持しておけば良い (複数のウィンドウで別々に情報を保持しておく必要はない) ので、全てのウィンドウで 1 つの処理を共有させるのが良いでしょう。

2011-01-10

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