Using the Places annotation service

The annotation service, provided by the nsIAnnotationService interface, is designed to store arbitrary data about a web page or about an item in the Places database in Firefox 3. It is usable from trusted Firefox code such as extensions, but not from web pages.

For an overview of the Places database design, see The Places database.

Most methods in the service are duplicated with one method labeled as a 'Page Annotation' taking an nsIURI and the others labeled as an 'Item Annotation' and taking the id of an item in the places database. Which one to use is usually dictated by the application. URI's are easier to use for services that want to annotate a web page loaded in the browser, while ID's are easier to use for services already working with the places database. Id's also offer the ability to add annotations to containers and queries of the database.

Creating the annotation service

The annotation contract ID is;1

var annotationService = Components.classes[";1"]

Note that the annotation service is not thread-safe. You should only use it from the main thread.

Naming your annotations

For your annotation name, you should use the format <namespace>/<name>. For example, "my_extension/page_state". The annotation service does not currently enforce the annotation name format, but this may change in the future. Also, we may add functions to get all of "your" annotations matching a given namespace.

You should not use any characters in your annotation names that are not valid as part of an HTML path. This includes colons, spaces, most punctuation, and non-ASCII characters.

You should try to have relatively few annotation names. The service stores these names in a separate table, and the fewer names there are, the more efficient retrieving names will be. You should not design your service so that you require hundreds of unique annotation names.

Setting an annotation

The annotation provides a variety of scriptable and non-scriptable setters for annotations on both pages and on items in the Places database (see nsIAnnotationService.idl for the exact declarations). From C++ you must use the setter for the explicit data type being saved:

  • setPageAnnotationString(aURI, aName, aValue, aFlags, aExpiration);
  • setPageAnnotationInt32(aURI, aName, aValue, aFlags, aExpiration);
  • setPageAnnotationInt64(aURI, aName, aValue, aFlags, aExpiration);
  • setPageAnnotationDouble(aURI, aName, aValue, aFlags, aExpiration);
  • setPageAnnotationBinary(aURI, aName, aData, aDataLen, aFlags, aExpiration);

And likewise for items in the Places database:

  • setItemAnnotationString(aItemId, aName, aValue, aFlags, aExpiration);
  • setItemAnnotationInt32(aItemId, aName, aValue, aFlags, aExpiration);
  • setItemAnnotationInt64(aItemId, aName, aValue, aFlags, aExpiration);
  • setItemAnnotationDouble(aItemId, aName, aValue, aFlags, aExpiration);
  • setItemAnnotationBinary(aItemId, aName, aValue, aDataLen, aFlags, aExpiration);

From JavaScript there are two simple function to perform all of these operations:

  • setPageAnnotation(aURI, aName, aValue, aFlags, aExpiration);
  • setItemAnnotation(aItemId, aName, aValue, aFlags, aExpiration);

These annotations all take similar parameters:

  • URI or ItemID: This is the nsIURI of the page to annotate, or for items in the places database, the id of the item.
  • name: This is the name of the annotation. See the section on naming above.
  • value: The value of the annotation.
  • flags: Currently unused. Should be 0.
  • expiration: The expiration time for the annotation (see "Lifetime of annotations" below).
var ioservice = Components.classes[";1"]
var uri = ioservice.newURI("", null, null);

annotationService.setPageAnnotation(uri, "my_extension/some_annotation",
  "This is the annotation value", 0, 0);

Retrieving annotations

There are corresponding getters for the setters above (see nsIAnnotationService.idl for the exact declarations):

From C++:

  • getPageAnnotationString(aURI, aName);
  • getPageAnnotationInt32(aURI, aName);
  • getPageAnnotationInt64(aURI, aName);
  • getPageAnnotationDouble(aURI, aName);
  • getPageAnnotationBinary(aURI, aName, aData, aDataLen, aMimeType);
  • getItemAnnotationString(aItemId, aName);
  • getItemAnnotationInt32(aItemId, aName);
  • getItemAnnotationInt64(aItemId, aName);
  • getItemAnnotationDouble(aItemId, aName);
  • getItemAnnotationBinary(aItemId, aName, aData, aDataLen, aMimeType);

From JavaScript:

  • getPageAnnotation(aURI, aName);
  • getItemAnnotation(aItemId, aName);

These functions will return/throw NS_ERROR_NOT_AVAILABLE if the annotation requested does not exist. You can use the Annotations Service's hasAnnotation method to determine in advance if the page has the requested annotation. However, it is more efficient to just try to do the operation and catch the exception; the extra check requires an additional database lookup (which has higher overhead). But then again you won't know if the exception meant that the annotation did not exist or something else is broken.

The getter functions return only the value of the annotation (with the exception of the C++ getPageAnnotationBinary and getItemAnnotationBinary methods which return the mimetype as well). Four functions are provided to get this information:

  • getPageAnnotationInfo(aURI, aName, aFlags, aExpiration, aMimeType, aType)
  • getItemAnnotationInfo(aItemId, aName, aFlags, aExpiration, aMimeType, aType)
  • getPageAnnotationType(aURI, aName);
  • getItemAnnotationType(aItemId, aName);

The returned type will be one of the VALUE_TYPE constants in mozIStorageValueArray.idl:

After bug 377066 the VALUE_TYPE_* type handling was changed to this:

  • TYPE_INT32 = 1
  • TYPE_INT64 = 5
try {
  var value = annotationService.getPageAnnotation(uri, "my_extension/some_annotation");
} catch(e) {
  // annotation does not exist

Other functions

  • getPagesWithAnnotation(aName, resultCount, results);
  • getItemsWithAnnotation(aName, resultCount, results);
Retrieves a list of all pages/items with the given annotation. C++ callers will want to use GetPagesWithAnnotationCOMArray which returns a COM array, making memory management much easier and reducing the chance of leaks.
  • getPageAnnotationNames(aURI, count, result);
  • getItemAnnotationNames(aItem, count, result);
Retrieves a list of all the annotations on a given URI or id. C++ callers will want to use GetPageAnnotationNamesTArray which returns a COM array, making memory management much easier and reducing the chance of leaks.
  • pageHasAnnotation(aURI, aName);
  • itemHasAnnotation(aItemId, aName);
Returns true if the page/item has an annotation with the given name.
  • removePageAnnotation(aURI, aName);
  • removeItemAnnotation(aItemId, aName);
Removes a given annotation from a page/item.
  • removePageAnnotations(aURI);
  • removeItemAnnotations(aItemId);
Removes all the annotations from a given page/item.
  • copyPageAnnotations(aSourceURI, aDestURI, aOverwriteDest);
  • copyItemAnnotations(aSourceItemId, aDestItemId, aOverwriteDest);
Copies all the annotations from one page/item to another. You can specify whether you want to preserve or replace the destination annotations in case of collisions.

The annotation protocol

The annotation service provides a protocol handler for the "moz-anno:" protocol. This allows you to link directly to data stored in the annotation service. You can get an annotation URI for a given URI/name pair by calling:

  • getAnnotationURI(aURI, aName);

In order for the annotation protocol to work, the annotation in question must have been declared with a MIME type. Annotations with no MIME type will not work.

The annotation service also provides special-case handling of favicons. When the annotation name is "favicon," the annotation protocol handler will pass the request through to the favicon service for handling. To get a favicon annotation URI for a given favicon, use nsIFaviconService.getFaviconLinkForIcon and to get the favicon annotation URI for a given page, use nsIFaviconService.getFaviconLinkForPage. It is important that you use these functions and not make up your own URIs, because these functions will efficiently default to the default page favicon when it does not exist. See Using the Places favicon service for more information.

Lifetime of annotations

Annotation expiration can be explicitly stated when the annotation is created. If no expiration is specified, the annotation is expired when the number of visits to the url is set to zero.

Valid values for expiration are:

  • EXPIRE_WITH_HISTORY : Default. Annotations live as long as the URI is in history (eg: Has >0 visits).
  • EXPIRE_NEVER : Never expire. Must be explictly removed.
  • EXPIRE_SESSION : Removed at application exit.
  • EXPIRE_DAYS : Removed at 7 days.
  • EXPIRE_WEEKS : Removed at 30 days.
  • EXPIRE_MONTHS : Removed at 180 days.
annotationService.setPageAnnotation(uri, "my_extension/some_annotation",
  "This is the annotation value", 0, annotationService.EXPIRE_NEVER);

Annotation Observers

Observers can also be added to the service to watch for changes to annotations. To add or remove an observer use the methods:

  • addObserver(aObserver);
  • removeObserver(aObserver);

where aObserver is an object implementing the nsIAnnotationObserver interface. The objects must implement four methods, which are called when an annotation is set/removed on a URI or item respectively:

  • onPageAnnotationSet(aURI, aName);
  • onItemAnnotationSet(aItemId, aName);
  • onPageAnnotationRemoved(aURI, aName);
  • onItemAnnotationRemoved(aItemId, aName);
var observer = {
  onPageAnnotationSet : function(aURI, aName) { },
  onItemAnnotationSet : function(aItemId, aName) { },
  onPageAnnotationRemoved : function(aURI, aName) { },
  onItemAnnotationRemoved: function(aItemId, aName) { }