[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

[Places] nsINavHistoryResultNode からブックマークの各種情報を取得する

[Places] ビューと nsIPlacesView インタフェースから引き続き、 Places データベースからの検索結果 (nsINavHistoryResult オブジェクト) から得たブックマークのノード (nsINavHistoryResultNode オブジェクト) について、各種情報を取得する。

ブックマークの種類

あるノードの種類を調べる、例えばフォルダかどうかを調べるには、 type プロパティを調べる。

if (node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER)
    alert("The node is a folder.");

PlacesUtils には nodeIs* メソッドがたくさん用意されているので、こちらを使った方が便利。

if (PlacesUtils.nodeIsFolder(node))
    alert("The node is a folder.");
else if (PlacesUtils.nodeIsLivemarkContainer(node))
    alert("The node is a Live Bookmark.");

ブックマークのタグ

nsINavHistoryResultNode の tags プロパティではすべてのタグが「, 」で連結された文字列として取得できる。

var tags = node.tags;

一方、 nsITaggingService#getTagsForURI ですべてのタグを配列として取得することも可能。なお、 PlacesUtils.tagging は nsITaggingService へのショートカット、 PlacesUtils._uri は URI 文字列から nsIURI オブジェクトを生成する便利メソッドである。

var tags = PlacesUtils.tagging.getTagsForURI(PlacesUtils._uri(node.uri), {});

ブックマークのアノテーション

Places では、ブックマークの「説明」プロパティなどは、ブックマークアイテムに対するアノテーションのひとつとして管理されている。アノテーションを取得するには nsIAnnotationService#getItemAnnotation を使う。 PlacesUtils.annotations は nsIAnnotationService へのショートカット、 DESCRIPTION_ANNO はアノテーション名 “bookmarkProperties/description” である。

var desc = PlacesUtils.annotations.getItemAnnotation(node.itemId, DESCRIPTION_ANNO);

以下のように PlacesUIUtils の便利メソッドを使ってブックマークの itemId に対応する「説明」プロパティを取得することも可能。

var desc = PlacesUIUtils.getItemDescription(node.itemId);

ブックマークのアイコン

nsINavHistoryResultNode の icon プロパティから nsIURI オブジェクトとして取得可能。

var iconLink = node.icon ? node.icon.spec : null;

取得した URI は「moz-anno:favicon:http://en-us.www.mozilla.com/favicon.ico」のような特殊なプロトコルで示される。この URI をブラウザに直接貼り付けてみればわかるように、「moz-anno:」プロトコルはプロトコルハンドラによって自動的にアイコンの画像データへ変換される。

一方、あるブックマークの URL を引数としてアイコンの URL を取得するには、 nsIFaviconService#getFaviconForPage を使用する。 PlacesUtils.favicons は nsIFaviconService へのショートカットである。

var icon = PlacesUtils.favicons.getFaviconForPage(PlacesUtils._uri(node.uri));

アイコンの MIME 型と画像データを取得するには、 nsIFaviconService#getFaviconData を使用する。

var mimeType = {};
var iconData = PlacesUtils.favicons.getFaviconData(icon, mimeType, {});
mimeType = mimeType.value;

BASE64 形式の data: URI に変換するには以下のようにする。

var dataURI = "data:" + mimeType + ";" + "base64," + btoa(String.fromCharCode.apply(null, iconData));

TOP

[Places] ビューと nsIPlacesView インタフェース

nsIPlacesView インタフェース

ブックマークや履歴といった Places データベースに保持されている内容は、ツリー/メニュー/ツールバーといった色々な GUI ウィジェット(「places view」あるいは単に「ビュー」と呼ぶ)として実際に目に見える形で表示される。各ビューはいずれも XBL にて nsIPlacesView インタフェースで定められた各種プロパティ・メソッドを実装しており、ビューの違いを意識することなくコントローラ側で各種機能を実装できる設計となっている。

Places ではデータベースへの問い合わせ結果を所定のインタフェースを介してビューに結びつけて表示させる。問い合わせ結果全体を表す nsINavHistoryResult オブジェクトからは、個々の「行」に対応する nsINavHistoryResultNode オブジェクトへアクセス可能である。このオブジェクトを「result node」あるいは単に「ノード」と呼ぶ。あるビュー上でユーザが現在選択している項目に対応するノードは、 nsIPlacesView インタフェースの selectedNode プロパティや getSelection メソッドによって取得可能である。

placesUIOverlay.js

引き続き、 [Places] 右クリックメニューへのメニュー項目追加 にて新たに追加した「Show Information」メニュー項目をクリックした際に実行される showBookmarkInformation 関数を実装する。

まずは右クリックメニューの対象となるビューと、そのビューにて選択しているノードを取得する。ここで、対象のビューとしてブックマークサイドバーのツリーだけを考慮すると、うっかり以下のようにやってしまうところである。

var view = document.getElementById("bookmarks-view");    // XULElement
var node = view.selectedNode;    // nsINavHistoryResultNode

しかし、これではせっかくのビューの違いを意識しない Places の設計が台無しである。ツリー/メニュー/ツールバーすべてを考慮して右クリックメニューの対象となっているビューを取得するには、以下のようにすればよい。 PlacesUIUtils.getViewForNode は、引数に指定した DOM ノードの先祖をたどって直近のビューを見つけ出す便利メソッドである。

var view = PlacesUIUtils.getViewForNode(document.popupNode);    // XULElement
var node = view.selectedNode;    // nsINavHistoryResultNode

あとはノードの各プロパティの情報を表示するだけ。各プロパティの詳細は nsINavHistoryService.idl 参照。

alert(
    "title     : " + node.title + "
" + 
    "uri       : " + node.uri + "
" + 
    "type      : " + node.type + "
" + 
    "icon      : " + (node.icon ? node.icon.spec : "") + "
" + 
    "itemId    : " + node.itemId + "
" + 
    "tags      : " + node.tags
);

TOP

[Places] 右クリックメニューへのメニュー項目追加

Firefox 2 でのブックマークの右クリックメニューは、ポップアップを表示するたびに JavaScript によって menuitem 要素を動的に生成する実装方式であったため、拡張機能によってメニュー項目を追加しづらいという問題があった。しかし、 Firefox 3 にて Places として実装が一新され、右クリックメニューの menuitem 要素や呼び出される command 要素は placesOverlay.xul という XUL ファイルで実装する形になり、拡張機能からのメニュー項目追加がしやすくなった。

Places の右クリックメニューは、以下の5つの場所で使用される。

ブックマークメニュー chrome://browser/content/browser.xul
ブックマークツールバー
ブックマークサイドバー chrome://browser/content/bookmarks/bookmarksPanel.xul
履歴サイドバー chrome://browser/content/history/history-panel.xul
履歴とブックマークの管理 chrome://browser/content/places/places.xul

各 XUL はいずれも placesOverlay.xul (chrome://browser/content/places/placesOverlay.xul) をオーバーレイしている。そこで、拡張機能から placesOverlay.xul に対してさらにオーバーレイして menuitem 要素などを追加することで、上記すべての場所の右クリックメニューへ一括してメニュー項目を追加することが可能となる。

サンプル

例として、ブックマークの右クリックメニューへ「Show Information」という新しいメニュー項目を追加する。
Places Context Menu

chrome.manifest

overlay    chrome://browser/content/places/placesOverlay.xul    chrome://myext/content/placesUIOverlay.xul

placesUIOverlay.xul

<popup id=”placesContext”>, <commandset id=”placesCommands”> をマージポイントとして menuitem 要素と command 要素を追加する。

<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

    <script type="application/x-javascript" src="placesUIOverlay.js" />

    <commandset id="placesCommands">
        <command id="placesCmd_showInfo" oncommand="showBookmarkInformation();" />
    </commandset>

    <popup id="placesContext">
        <menuseparator insertafter="placesContext_openSeparator" />
        <menuitem id="placesContext_showInfo"
                  command="placesCmd_showInfo"
                  label="Show Information"
                  insertafter="placesContext_openSeparator"
                  selectiontype="single"
                  selection="bookmark|folder"
                  forcehideselection="livemarkChild|livemark/feedURI|PlacesOrganizer/OrganizerQuery" />
    </popup>

</overlay>

ブックマークの右クリックメニューは、右クリックした対象が通常のブックマークか、フォルダか、区切りか、などの条件によってメニュー項目数が増減する。メニュー項目を表示する条件は、 menuitem 要素へ追加された以下の4つの特殊な属性の値によって決定される。詳細は PlacesController クラスの buildContextMenu や _buildSelectionMetadata のコメントを参照。

属性 役割
selectiontype single, multiple のいずれかの値をセットすることで、選択している対象が単数か複数かの条件によってメニュー項目を表示する。
selection any, bookmark, folder, separator といった値を指定することで、選択している対象がブックマークか、フォルダか、区切りかといった条件でメニュー項目を表示する。 | 区切りで複数指定可能。
forcehideselection selection 属性の逆で、選択している対象の種類によってメニュー項目を非表示にする。
hideifnoinsetionpoint よくわからん。

上記の例では、単一のブックマークあるいはフォルダを選択しているときにだけ「Show Information」メニュー項目を表示する。ただし、例外としてライブブックマークや特殊な「ブックマークツールバー」フォルダを選択しているときはメニュー項目を表示しない。
また、上記の例ではメニューの区切り(menuseparator 要素)も追加しているが、ありがたいことに Places の右クリックメニューは連続するメニューの区切りを自動的に1個にして表示してくれるので、新たに追加するメニューの区切りについては、どういった条件で表示するのかを意識する必要が無い。

placesUIOverlay.js

function showBookmarkInformation() {
    // TODO
}

[Places] ビューと nsIPlacesView インタフェース へつづく…

TOP