Querying Places

  • Revision slug: Querying_Places
  • Revision title: Querying Places
  • Revision id: 131192
  • Created:
  • Creator: BrettWilson
  • Is current revision? No
  • Comment Result types

Revision Content

Most information from the Firefox history and bookmarks system ("Places") comes from executing queries on the history system (nsINavHistory.executeQuery/executeQueries). This will give you a nsINavHistoryResult object which will contain a tree structure of the results. The definitions of these structures is in browser/components/places/public/nsINavHistoryService.idl

Executing a query

There are two parts of a query: a list of one or more nsINavHistoryQuery objects, and an nsINavHistoryQueryOptions object. You first need to fill out these structures with the parameters you want. Use nsINavHistoryService.getNewQuery and nsINavHistoryService.getNewQueryOptions to retrieve empty objects. These objects typically have empty values for each possible field, so using them in their initial state will give you all of history in a flat list:

var historyService = Components.classes.["@mozilla.org/browser/nav-history-service;1"].
    getService(Ci.nsINavHistoryService);

// get all history sorded by ascending visit date
var options = historyService.getNewQueryOptions();
options.sortingMode = options.SORT_BY_DATE_ASCENDING;

// no query parameters will return everything
var query = historyService.getNewQuery();

var result = historyService.executeQuery(query, options)

Result types

The query options structure nsINavHistoryQueryOptions has an attribute resultType for the type of the results that you want. It is important to understand the differences between these:

  • RESULTS_AS_URI: This is the default, and means that you get one result node of type RESULT_TYPE_URI for each URI that matches the query. The visit date for each node will be the last visit date for that URL. You will definitely want this when showing bookmark folders.
  • RESULTS_AS_VISIT: This means that you want one entry for each time a page was visited matching the given query. Therefore, you may get duplicate entries for URLs, each with a different date. The nodes will be of RESULT_TYPE_VISIT which will give you access to a session ID for each visit. This session ID is the same for all pages that were reached by clicking links. A new session starts when the user types a new URL or follows a bookmark. It is used to compute the dividing lines in the places history view.
  • RESULTS_AS_FULL_VISIT: This is the same as visits but the results will be of type RESULT_TYPE_FULL_VISIT and will have additional information about the visit, such as the referring visit, and how the transition happened (typed, redirect, link, etc.). This information is typically not needed and causes the result nodes (of which there may be many) to be larger, which is why it is a separate option.

Bookmark queries

The contents of bookmark folders can be retrieved by setting the "folders" member in the query object. This item is an array of folder IDs from the bookmark service. Typically, you will only have one folder ID in this list, which will given you the contents of that folder. You can set multiple folders and the result will be the intersection of all the folders.

Generally, you want to set the grouping mode in the options to GROUP_BY_FOLDER for bookmarks queries. This will result in a hierarchical structure containing the bookmark folder hierarchy rooted at where you requested. Note: This doesn't actually have any effect in the current implementation. The query system always assumes GROUP_BY_FOLDER for bookmarks queries. See bug 324579.

For sorting, you will generally want to use SORT_BY_NONE (the default) since this will return items in their "natural" order as specified by the user in the bookmarks manager. Other sortings will work, however.

For bookmark queries you will generally want no query parameters to retrieve all items from the requested folder(s). When you specify exactly one folder, GROUP_BY_FOLDER, and no query parameters, the system will be more efficient querying and keeping the results up-to-date since this maps to exactly one bookmark folder.

var bookmarkService = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"].
    getService(Ci.nsINavBookmarksService)
options.setFolders([bookmarkService.toolbarRoot], 1);
options.setGroupingMode([options.GROUP_BY_FOLDER], 1);
var result historyService.executeQueries(query, options);

Using the results

Note: Be careful when accessing nodes and do not keep references to them around. Notifications sent to the result from the history and bookmarks system, as well as commands executed by the programmer such as sorting may cause the structure to change and nodes may be inserted, removed, or replaced.

The nsINavHistoryResult object returned by executeQuery/executeQueries contains the list of matches to the given history or bookmarks query. These results are contained in a tree structure made up of nodes. A node's type can be retrieved using its type attribute. This type tells you what interface you can QueryInterface the node to in order to get at more detailed information:

  • nsINavHistoryResultNode: Base class for all nodes. Contains URI, title, and other general info.
  • nsINavHistoryVisitResultNode: Derived from nsINavHistoryResultNode, contains session information.
  • nsINavHistoryFullVisitResultNode: Derived from nsINavHistoryVisitResultNode, contains information about how the user navigated to this page. Note: currently unimplemented, see bug 320831.
  • nsINavHistoryContainerResultNode: General container node giving access to its children. Derived from nsINavHistoryResultNode.
  • nsINavHistoryQueryResultNode: A type of container representing a query of the history system. It allows you to get the query options and parameters.
  • nsINavHistoryFolderResultNode: Derived from nsINavHistoryQueryResultNode, this represents a special type of query mapping to the exact contents of one bookmarks folder. It gives easy access to its folder ID, and also updates itself more efficiently than a general query.

Example of detecting the type of a node

var Ci = Components.interfaces;
switch(node.type)
  case node.RESULT_TYPE_URI:
    dump("URI result " + node.uri + "\n");
    break;
  case node.RESULT_TYPE_VISIT:
    var visit = node.QueryInterface(Ci.nsINavHistoryVisitResultNode);
    dump("Visit result " + node.uri + " session = " + visit.sessionId + "\n");
    break;
  case node.RESULT_TYPE_FULL_VISIT:
    var fullVisit = node.QueryInterface(Ci.nsINavHistoryFullVisitResultNode);
    dump("Full visit result " + node.uri + " session = " + fullVisit.sessionId + " transitionType = " +
         fullVisit.transitionType + "\n");
    break;
  case node.RESULT_TYPE_HOST:
    var container = node.QueryInterface(Ci.nsINavHistoryContainerResultNode);
    dump("Host " + container.title + "\n");
    break;
  case node.RESULT_TYPE_REMOTE_CONTAINER:
    var container = node.QueryInterface(Ci.nsINavHistoryContainerResultNode);
    dump("Remote container " + container.title + " type = " + container.remoteContainerType + "\n");
    break;
  case node.RESULT_TYPE_QUERY:
    var query = node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
    dump("Query, place URI = " + query.uri + "\n");
    break;
  case node.RESULT_TYPE_FOLDER:
    // note that the folder is also a query and so has a query and an options structure
    var folder = node.QueryInterface(Ci.nsINavHistoryFolderResultNode);
    dump("Folder " + folder.title + " id = " + folder.folderId + "\n");
    break;
  case node.RESULT_TYPE_SEPARATOR:
    dump("-----------\n");
    break;
}

Containers

Containers hold lists of other containers and result nodes. Each result has a container representing the root of the query. It can be retrieved using the root attribute of the result. For general queries, this root container is a nsINavHistoryQueryResultNode with the query parameters and options that you supplied in the original query. For queries mapping to one bookmark folder, this will be a nsINavHistoryFolderResultNode.

Containers can be open or closed. This corresponds to the open and closed state in a tree view, and can also be mapped to showing and hiding menus. To get at a container's contents, you must first open the container. Most container types populate themselves lazily, so opening a container actually corresponds to executing the given query. While a container is open, it will listen to the history and bookmarks systems' notifications and modify their contents to keep themselves up-to-date. For this reason, it is best to close a container as soon as you are done with it, since it will give better performance. If you close a container and re-open it before any history or bookmark change notifications come, the results will generally still be there and this operation will be fast.

Example of traversing a container:

var cont = result.root;
cont.containerOpen = true;
for (var i = 0; i < cont.childCount; i ++) {
  var node = cont.getChild(i);
  dump(node.title + "\n");
}
cont.containerOpen = false;

Remote containers

Remote containers are a way for extension authors and others to provide content for containers in a places query result. First, you should create a service that implements nsIRemoteContainer. Then you create a bookmark folder associated with your service by using nsINavBookmarksService.createContainer. The type parameter is a string containing the contract ID of your service. The bookmark service and other components will call CreateService using this contract ID to get your nsIRemoteContainer implementation.

Once your service is associate with a folder, it will get callbacks when the folder is moved or deleted, or when a container result node representing your container is opened or closed. In response to these operations, your service should update any bookkeeping information associated with the folder. Your service can also declare whether your service's containers have read-only children or whether they can be modified like normal bookmarks.

There are two modes of operation that remote container implementations can use. First, it can act like a bookmarks provider and create real bookmarks inside a regular bookmarks folder. An example of this is the livemark service. The livemark service reads a feed and creates bookmarks in a folder corresponding to the items in that stream. These bookmarks are managed by the bookmarks service so the livemarks service does not care when containers are opened and closed. It only needs to handle the cases when a folder is moved or deleted (to update the information that associates a feed with the folder) and declares that livemarks are read-only.

The second mode of operation is more active. Such services can respond to container open and close operations and populate the results at runtime. They can therefore generate more dynamic content at the time it is shown. When a folder with a container type is opened, the service will be notified and given the container result node. The service can then create children in that container using appendURINode, appendFolderNode, etc. Of special note is appendContainerNode that can be used to create more remote containers. These remote containers are not associated with any bookmark folder (use appendFolderNode for that). For example, a file browser could be created that dynamically creates containers associated with subfolders. Keep in mind that each container has a property bag that can be used to associate random information with the node such as patch.

Revision Source

<p>Most information from the Firefox history and bookmarks system ("Places") comes from executing queries on the history system (nsINavHistory.executeQuery/executeQueries). This will give you a nsINavHistoryResult object which will contain a tree structure of the results. The definitions of these structures is in <a class="external" href="http://lxr.mozilla.org/seamonkey/source/browser/components/places/public/nsINavHistoryService.idl">browser/components/places/public/nsINavHistoryService.idl</a>
</p>
<h3 name="Executing_a_query"> Executing a query </h3>
<p>There are two parts of a query: a list of one or more nsINavHistoryQuery objects, and an nsINavHistoryQueryOptions object. You first need to fill out these structures with the parameters you want. Use nsINavHistoryService.getNewQuery and nsINavHistoryService.getNewQueryOptions to retrieve empty objects. These objects typically have empty values for each possible field, so using them in their initial state will give you all of history in a flat list:
</p>
<pre>var historyService = Components.classes.["@mozilla.org/browser/nav-history-service;1"].
    getService(Ci.nsINavHistoryService);

// get all history sorded by ascending visit date
var options = historyService.getNewQueryOptions();
options.sortingMode = options.SORT_BY_DATE_ASCENDING;

// no query parameters will return everything
var query = historyService.getNewQuery();

var result = historyService.executeQuery(query, options)
</pre>
<h4 name="Result_types"> Result types </h4>
<p>The query options structure nsINavHistoryQueryOptions has an attribute <code>resultType</code> for the type of the results that you want. It is important to understand the differences between these:
</p>
<ul><li> <b>RESULTS_AS_URI:</b> This is the default, and means that you get one result node of type RESULT_TYPE_URI for each URI that matches the query. The visit date for each node will be the last visit date for that URL. You will definitely want this when showing bookmark folders.
</li><li> <b>RESULTS_AS_VISIT:</b> This means that you want one entry for each time a page was visited matching the given query. Therefore, you may get duplicate entries for URLs, each with a different date. The nodes will be of RESULT_TYPE_VISIT which will give you access to a session ID for each visit. This session ID is the same for all pages that were reached by clicking links. A new session starts when the user types a new URL or follows a bookmark. It is used to compute the dividing lines in the places history view.
</li><li> <b>RESULTS_AS_FULL_VISIT:</b> This is the same as visits but the results will be of type RESULT_TYPE_FULL_VISIT and will have additional information about the visit, such as the referring visit, and how the transition happened (typed, redirect, link, etc.). This information is typically not needed and causes the result nodes (of which there may be many) to be larger, which is why it is a separate option.
</li></ul>
<h4 name="Bookmark_queries"> Bookmark queries </h4>
<p>The contents of bookmark folders can be retrieved by setting the "folders" member in the query object. This item is an array of folder IDs from the bookmark service. Typically, you will only have one folder ID in this list, which will given you the contents of that folder. You can set multiple folders and the result will be the intersection of all the folders.
</p><p>Generally, you want to set the grouping mode in the options to GROUP_BY_FOLDER for bookmarks queries. This will result in a hierarchical structure containing the bookmark folder hierarchy rooted at where you requested. <i>Note: This doesn't actually have any effect in the current implementation. The query system always assumes GROUP_BY_FOLDER for bookmarks queries. See <a class="external" href="https://bugzilla.mozilla.org/show_bug.cgi?id=324579">bug 324579</a>.</i>
</p><p>For sorting, you will generally want to use SORT_BY_NONE (the default) since this will return items in their "natural" order as specified by the user in the bookmarks manager. Other sortings will work, however.
</p><p>For bookmark queries you will generally want no query parameters to retrieve all items from the requested folder(s). When you specify exactly one folder, GROUP_BY_FOLDER, and no query parameters, the system will be more efficient querying and keeping the results up-to-date since this maps to exactly one bookmark folder.
</p>
<pre>var bookmarkService = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"].
    getService(Ci.nsINavBookmarksService)
options.setFolders([bookmarkService.toolbarRoot], 1);
options.setGroupingMode([options.GROUP_BY_FOLDER], 1);
var result historyService.executeQueries(query, options);
</pre>
<h3 name="Using_the_results"> Using the results </h3>
<p><i>Note: Be careful when accessing nodes and do not keep references to them around. Notifications sent to the result from the history and bookmarks system, as well as commands executed by the programmer such as sorting may cause the structure to change and nodes may be inserted, removed, or replaced.</i>
</p><p>The nsINavHistoryResult object returned by executeQuery/executeQueries contains the list of matches to the given history or bookmarks query. These results are contained in a tree structure made up of nodes. A node's type can be retrieved using its <code>type</code> attribute. This type tells you what interface you can QueryInterface the node to in order to get at more detailed information:
</p>
<ul><li> <b>nsINavHistoryResultNode:</b> Base class for all nodes. Contains URI, title, and other general info.
</li><li> <b>nsINavHistoryVisitResultNode:</b> Derived from nsINavHistoryResultNode, contains session information.
</li><li> <b>nsINavHistoryFullVisitResultNode:</b> Derived from nsINavHistoryVisitResultNode, contains information about how the user navigated to this page. <i>Note: currently unimplemented, see <a class="external" href="https://bugzilla.mozilla.org/show_bug.cgi?id=320831">bug 320831</a>.</i>
</li><li> <b>nsINavHistoryContainerResultNode:</b> General container node giving access to its children. Derived from nsINavHistoryResultNode.
</li><li> <b>nsINavHistoryQueryResultNode:</b> A type of container representing a query of the history system. It allows you to get the query options and parameters.
</li><li> <b>nsINavHistoryFolderResultNode:</b> Derived from nsINavHistoryQueryResultNode, this represents a special type of query mapping to the exact contents of one bookmarks folder. It gives easy access to its folder ID, and also updates itself more efficiently than a general query.
</li></ul>
<p>Example of detecting the type of a node
</p>
<pre>var Ci = Components.interfaces;
switch(node.type)
  case node.RESULT_TYPE_URI:
    dump("URI result " + node.uri + "\n");
    break;
  case node.RESULT_TYPE_VISIT:
    var visit = node.QueryInterface(Ci.nsINavHistoryVisitResultNode);
    dump("Visit result " + node.uri + " session = " + visit.sessionId + "\n");
    break;
  case node.RESULT_TYPE_FULL_VISIT:
    var fullVisit = node.QueryInterface(Ci.nsINavHistoryFullVisitResultNode);
    dump("Full visit result " + node.uri + " session = " + fullVisit.sessionId + " transitionType = " +
         fullVisit.transitionType + "\n");
    break;
  case node.RESULT_TYPE_HOST:
    var container = node.QueryInterface(Ci.nsINavHistoryContainerResultNode);
    dump("Host " + container.title + "\n");
    break;
  case node.RESULT_TYPE_REMOTE_CONTAINER:
    var container = node.QueryInterface(Ci.nsINavHistoryContainerResultNode);
    dump("Remote container " + container.title + " type = " + container.remoteContainerType + "\n");
    break;
  case node.RESULT_TYPE_QUERY:
    var query = node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
    dump("Query, place URI = " + query.uri + "\n");
    break;
  case node.RESULT_TYPE_FOLDER:
    // note that the folder is also a query and so has a query and an options structure
    var folder = node.QueryInterface(Ci.nsINavHistoryFolderResultNode);
    dump("Folder " + folder.title + " id = " + folder.folderId + "\n");
    break;
  case node.RESULT_TYPE_SEPARATOR:
    dump("-----------\n");
    break;
}
</pre>
<h4 name="Containers"> Containers </h4>
<p>Containers hold lists of other containers and result nodes. Each result has a container representing the root of the query. It can be retrieved using the <code>root</code> attribute of the result. For general queries, this root container is a nsINavHistoryQueryResultNode with the query parameters and options that you supplied in the original query. For queries mapping to one bookmark folder, this will be a nsINavHistoryFolderResultNode.
</p><p>Containers can be open or closed. This corresponds to the open and closed state in a tree view, and can also be mapped to showing and hiding menus. To get at a container's contents, you must first open the container. Most container types populate themselves lazily, so opening a container actually corresponds to executing the given query. While a container is open, it will listen to the history and bookmarks systems' notifications and modify their contents to keep themselves up-to-date. For this reason, it is best to close a container as soon as you are done with it, since it will give better performance. If you close a container and re-open it before any history or bookmark change notifications come, the results will generally still be there and this operation will be fast.
</p><p>Example of traversing a container:
</p>
<pre>var cont = result.root;
cont.containerOpen = true;
for (var i = 0; i &lt; cont.childCount; i ++) {
  var node = cont.getChild(i);
  dump(node.title + "\n");
}
cont.containerOpen = false;
</pre>
<h3 name="Remote_containers"> Remote containers </h3>
<p>Remote containers are a way for extension authors and others to provide content for containers in a places query result. First, you should create a service that implements <a class="external" href="http://lxr.mozilla.org/seamonkey/source/browser/components/places/public/nsIRemoteContainer.idl">nsIRemoteContainer</a>. Then you create a bookmark folder associated with your service by using <code>nsINavBookmarksService.createContainer</code>. The <code>type</code> parameter is a string containing the contract ID of your service. The bookmark service and other components will call CreateService using this contract ID to get your nsIRemoteContainer implementation.
</p><p>Once your service is associate with a folder, it will get callbacks when the folder is moved or deleted, or when a container result node representing your container is opened or closed. In response to these operations, your service should update any bookkeeping information associated with the folder. Your service can also declare whether your service's containers have read-only children or whether they can be modified like normal bookmarks.
</p><p>There are two modes of operation that remote container implementations can use. First, it can act like a bookmarks provider and create real bookmarks inside a regular bookmarks folder. An example of this is the <a class="external" href="http://lxr.mozilla.org/seamonkey/source/browser/components/places/src/nsLivemarkService.cpp">livemark service</a>. The livemark service reads a feed and creates bookmarks in a folder corresponding to the items in that stream. These bookmarks are managed by the bookmarks service so the livemarks service does not care when containers are opened and closed. It only needs to handle the cases when a folder is  moved or deleted (to update the information that associates a feed with the folder) and declares that livemarks are read-only.
</p><p>The second mode of operation is more active. Such services can respond to container open and close operations and populate the results at runtime. They can therefore generate more dynamic content at the time it is shown. When a folder with a container type is opened, the service will be notified and given the container result node. The service can then create children in that container using <code>appendURINode</code>, <code>appendFolderNode</code>, etc. Of special note is <code>appendContainerNode</code> that can be used to create more remote containers. These remote containers are not associated with any bookmark folder (use <code>appendFolderNode</code> for that). For example, a file browser could be created that dynamically creates containers associated with subfolders. Keep in mind that each container has a property bag that can be used to associate random information with the node such as patch.
</p>
Revert to this revision