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

2010-03-31

Firefox アドオン中での文字コードの変更方法 (XPCOM を JavaScript から使用する)

Firefox のアドオン (拡張機能等) を開発する際に、文字コードを変更する必要がでてくることもあるかと思います。 ここでは、JavaScript のコード中の文字列の文字コードを XPCOM *1 を使用して変更する方法について記します。

ここに書いてある方法はあくまで Firefox アドオン中で実行可能な方法であり、通常の web ページ内の JavaScript などでは使用できません。 文字コードの変換はアドオンから外部のプログラムなどに文字情報を送る際などに必要になると思います。

nsIScriptableUnicodeConverter

使用するのは nsIScriptableUnicodeConverter です。 これを使用することで JavaScript 上の通常の文字列 *2 を各種文字コードでエンコードされた文字列に変換したり、逆に各種文字コードでエンコードされた文字列を Unicode 文字列に変換したりできます。

nsIScriptableUnicodeConverter を使用するために、まずインスタンスを取得します。

var unicodeConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
                          .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);

次に、変換対象の文字コードを設定します。 次の例では Shift_JIS を文字コードとして設定しています。

try {
    unicodeConverter.charset = "Shift_JIS";
} catch(e) {
    // 設定しようとした文字コードがサポートされていない場合は例外発生
    // 例外処理
}

最後に文字コードの変換を行います。 次の例では文字列 "テスト文字列" を Shift_JIS に変換し、そのあとで逆の変換をしています。

var str = "テスト文字列";
// 文字列 str を Shift_JIS (unicodeConverter.charset に設定した文字コード) に変換する
// 変換後、Funish メソッドを使用し, その返り値を変換後の文字列の末尾に繋げなければならない
var sjisStr = unicodeConverter.ConvertFromUnicode( str ) + unicodeConverter.Finish();
// 変換後の文字列をまた Unicode 文字列に戻す
var ucStr = unicodeConverter.ConvertToUnicode( sjisStr );

これで文字コードの変換ができます。 nsIScriptableUnicodeConverter は他にもメソッドをいくつか持っているので、詳しくは MDC のドキュメントを読んでください。

*1:XPCOM とは Mozilla プロジェクトにおいて開発されているクロスプラットフォームなコンポーネント技術で、JavaScript からもそのコンポーネントを利用できます。

*2:Unicode 文字列。 内部的には UTF-16 ですかね。