Now browsing the archives for 6月, 2008.

[userChrome.css] Places Style Throbber

Firefox 3 のスマートロケーションバー入力中にまれに一瞬だけ表示される回転するアイコンがかっこいいので、読み込み中のタブやアドオンマネージャの更新チェック中などの Throbber アイコンに転用するための userChrome.css を作った。ただし、回転が停止しているバージョンのアイコンは今までどおりです。

Places Style Throbber Places Style Throbber

userChrome.css

/* ::::: Places Style Throbber ::::: */

#navigator-throbber[busy="true"],
toolbar[iconsize="small"] #navigator-throbber[busy="true"],
toolbar[mode="text"] #navigator-throbber[busy="true"],
.tabbrowser-tab[busy] > .tab-icon-image,
.alltabs-item[busy] > .menu-iconic-left > .menu-iconic-icon,
#sidebar-throbber[loading="true"],
#checkForUpdates[loading="true"],
#extensionsManager richlistitem[loading="true"] .updateBadge,
#extensionsManager .addonThrobber,
#extensionsManager .throbber {
    list-style-image: url("chrome://browser/skin/places/searching_16.png") !important;
}

TOP

nsISafeOutputStream で安全なファイルの書き込み

(1) 通常のファイルの書き込み

function writeFile(aFile, aText) {
    var stream = Cc["@mozilla.org/network/file-output-stream;1"].
                 createInstance(Ci.nsIFileOutputStream);
    stream.init(aFile, 0x02 | 0x08 | 0x20, 0644, 0);
    stream.write(aText, aText.length);
    stream.close();
}

(2) nsISafeOutputStream を使った安全なファイルの書き込み

function writeFileSafely(aFile, aText) {
    var stream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
                 createInstance(Ci.nsIFileOutputStream);
    stream.init(aFile, 0x02 | 0x08 | 0x20, 0644, 0);
    stream.write(aText, aText.length);
    stream.QueryInterface(Ci.nsISafeOutputStream);
    stream.finish();
}

(1) の方式は多分ファイル書き込み時にいったん0バイトにしてから書き込みが行われるため、ファイルの書き込み中の強制終了などにより破損する可能性が高い。
一方、(2) の方式は “test-1.txt” のような別名でいったんファイルの書き出しが行われた後、その内容を本来のファイル “test.txt” へ上書きコピーするため、破損の可能性が低いと思われる。
パフォーマンスの面では、簡単なベンチマークを試したところ (2) が劣るようであったが、書き込むデータサイズが大きい(数MB以上)場合はほとんど差異がなくなるようだった。

TOP

JavaScript 関数と XPCOM メソッドの例外ハンドリング

JavaScript の関数がスローする例外の内容を知るには、例外オブジェクトの値そのものを調べる。
XPCOM のメソッドがスローする例外の内容を知るには、例外オブジェクト (nsIXPCException オブジェクト) の result プロパティなどを調べる。

例えば以下のような純粋な JavaScript の関数があるとすると、

const Cr = Components.results;
function test() {
    throw Cr.NS_ERROR_FAILURE;
}

関数実行時に catch したオブジェクトの値そのものを調べることで例外の内容を知ることができる。

try {
    test();
}
catch (ex if ex == Cr.NS_ERROR_FAILURE) {
    alert("Failed!");
}

一方、上記の関数 test と同じ内容のメソッドを JavaScript 製 XPCOM のメソッドとして実装した場合、メソッド実行時に catch した例外は nsIXPCException オブジェクトとなり、 result プロパティなどから例外の内容を知ることができる。

try {
    Cc["@XXX"].getService(Ci.XXX).test();
}
catch (ex if ex.result == Cr.NS_ERROR_FAILURE) {
    alert("Failed!");
}

Components.Exception コンストラクタを使えば、純粋な JavaScript の関数で nsIXPCException オブジェクトの例外をスローすることも可能。エラーコンソールに例外発生のソースファイルなどの詳細表示ができるといった利点が挙げられる。

function test() {
    throw new Components.Exception("Failed!", Cr.NS_ERROR_FAILURE);
}

TOP

[Places] フォルダ内のブックマークを列挙する

Places データベースへの問い合わせ結果から得たブックマークのノードは nsINavHistoryResultNode インタフェースを実装するオブジェクトであるが、ブックマークフォルダは nsINavHistoryContainerResultNode インタフェースも合わせて実装しており、 childCount プロパティで子ノードの数を調べたり、 getChild メソッドで指定したインデックスの子ノードを取得することができる。
例えば、[Places] ビューと nsIPlacesView インタフェース の続きとして、右クリックしたブックマークフォルダ内の子ノードにアクセスするには、以下のようにすればよい。ただし、ビュー上でフォルダが開いていることを前提とする。

// assume that node is instanceof Ci.nsINavHistoryContainerResultNode and is open.
for (var i = 0; i < node.childCount; i++) {
    var childNode = node.getChild(i);
}

一方、ビュー上でフォルダが閉じた状態になっている場合、 childCount プロパティなどを使って子ノードへアクセスしようとすると NS_ERROR_NOT_AVAILABLE 例外がスローされる。そこで、ブックマークフォルダのノードの containerOpen プロパティへ true をセットすることで一時的にフォルダを開いてから子ノードへアクセスし、処理が終わった後に false をセットしてフォルダを閉じるようにする。以下は、引数で指定したブックマークフォルダ内のすべてのブックマークを再帰的に取得し、配列として返す関数である。

function flatChildNodes(aNode) {
    var ret = [];
    var closeOriginally = !aNode.containerOpen;
    if (closeOriginally)
        // if the folder is closed, open it.
        aNode.containerOpen = true;
    for (var i = 0; i < aNode.childCount; i++) {
        var childNode = aNode.getChild(i);
        if (PlacesUtils.nodeIsBookmark(childNode))
            ret.push(childNode);
        else if (PlacesUtils.nodeIsFolder(childNode) && 
                 !PlacesUtils.nodeIsLivemarkContainer(childNode))
        // call this function recursive
        ret = ret.concat(arguments.callee(childNode));
    }
    if (closeOriginally)
        // don't forget to restore the folder's original closed state
        aNode.containerOpen = false;
    return ret;
}

上記の例では、ビュー上に表示されているブックマークフォルダ、つまりデータベースへの問い合わせ結果が取得済みのブックマークフォルダを対象としていた。ビューへの表示が無い場面でフォルダ内のノードへアクセスするには、データベースへ問い合わせを行う必要がある。通常 Places データベースへの問い合わせを行うには、 nsINavHistoryService#getNewQuery でクエリオブジェクトを生成し executeQuery で実行するという手続きが必要であるが、 PlacesUtils.getFolderContents を使えばより単純なコードでフォルダ内のノードへアクセス可能である。

var result = PlacesUtils.getFolderContents(node.itemId);    // nsINavHistoryResult
var parentNode = result.root;
for (var i = 0; i < parentNode.childCount; i++) {
    var childNode = parentNode.getChild(i);
}

以下は、ブックマークフォルダの itemId プロパティを引数に、そのフォルダ内の全ブックマークを再帰的に取得して配列として返す関数である。

function flatChildNodes(aItemId) {
    var ret = [];
    var parentNode = PlacesUtils.getFolderContents(aItemId).root;
    for (var i = 0; i < parentNode.childCount; i++) {
        var childNode = parentNode.getChild(i);
        if (PlacesUtils.nodeIsBookmark(childNode))
            ret.push(childNode);
        else if (PlacesUtils.nodeIsFolder(childNode) && 
                 !PlacesUtils.nodeIsLivemarkContainer(childNode))
            // call this function recursive
            ret = ret.concat(arguments.callee(childNode.itemId));
    }
    return ret;
}

TOP