Bug 524091 - Remove microsummaries support. (Service removal)
authorMarco Bonardo <mbonardo@mozilla.com>
Tue, 26 Apr 2011 14:25:00 +0200
changeset 68756 034a23531362a33e21cb83c22ddff9ba5c503c0f
parent 68755 914e10b323c11cef1e9b93762d1ca5bf19bb71ee
child 68757 5e98268edaf1c72870c854bb54b91ce7ca5edcea
push idunknown
push userunknown
push dateunknown
bugs524091
milestone6.0a1
Bug 524091 - Remove microsummaries support. (Service removal)
toolkit/components/places/nsIMicrosummaryService.idl
toolkit/components/places/nsMicrosummaryService.js
deleted file mode 100644
--- a/toolkit/components/places/nsIMicrosummaryService.idl
+++ /dev/null
@@ -1,393 +0,0 @@
-
-#include "nsISupports.idl"
-
-interface nsIDOMDocument;
-interface nsIDOMNode;
-interface nsIURI;
-interface nsISimpleEnumerator;
-interface nsIMicrosummary;
-
-[scriptable, uuid(560b0980-be95-47e9-81cc-4428c073127c)]
-interface nsIMicrosummaryObserver : nsISupports
-{
-    /**
-     * Called when an observed microsummary updates its content.
-     * Since an observer might watch multiple microsummaries at the same time,
-     * the microsummary whose content has been updated gets passed
-     * to this handler.
-     * XXX Should this be onContentUpdated?
-     *
-     * @param microsummary
-     *        the microsummary whose content has just been updated
-     *
-     */
-    void onContentLoaded(in nsIMicrosummary microsummary);
-
-    /**
-     * Called when an observed microsummary encounters an error during an update.
-     * 
-     * @param microsummary
-     *        the microsummary which could not be updated
-     *
-     */
-    void onError(in nsIMicrosummary microsummary);
-
-    /**
-     * Called when an element is appended to a microsummary set.
-     * XXX Should this be in a separate nsICollectionObserver interface?
-     *
-     * @param microsummary
-     *        the microsummary that has just been appended to the set
-     *
-     */
-    void onElementAppended(in nsIMicrosummary microsummary);
-};
-
-
-[scriptable, uuid(05b48344-d0a7-427e-934e-9a6e0d5ecced)]
-interface nsIMicrosummaryGenerator : nsISupports
-{
-    // Has the generator itself, which may be a remote resource, been loaded.
-    readonly attribute boolean loaded;
-
-    // An arbitrary descriptive name for this microsummary generator.
-    readonly attribute AUTF8String name;
-
-    // The canonical location and unique identifier of the generator.
-    // It tells us where the generator comes from and where to go for updates.
-    //
-    // For generators referenced by web pages via <link> tags, this URI is
-    // the URL specified by the tag.
-    //
-    // For generators installed via nsSidebar::addMicrosummaryGenerator,
-    // this URI is a URN of the form urn:source:<url>, where <url> is the
-    // remote location from which we installed the generator.
-    //
-    // For generators installed via some other mechanism (f.e. an extension
-    // that dynamically creates generators), this URI is a URN in a form
-    // of the extension's own choosing, with the only restriction being that
-    // the URI be globally unique.  To ensure this, we recommend that such
-    // extensions incorporate UUIDs created by nsUUIDGenerator into the URNs
-    // of the generators they create.
-    readonly attribute nsIURI uri;
-
-    /**
-     * microsummary-generator equivalence test
-     * generators equal if their canonical locations equal, see uri attribute.
-     */
-    boolean equals(in nsIMicrosummaryGenerator aOther);
-
-    // For generators installed by the user or bundled with the browser, the
-    // local URI points to the location of the local file containing the
-    // generator's XML.
-    readonly attribute nsIURI localURI;
-
-    // Whether or not this generator needs page content to generate
-    // a microsummary. Microsummaries generated by XSLT templates need page
-    // content, while those which represent the actual microsummary content
-    // do not.
-    readonly attribute boolean needsPageContent;
-
-    /**
-     * Generate a microsummary by processing the generator template
-     * against the page content.  If a generator doesn't need content,
-     * pass null as the parameter to this method.
-     *
-     * XXX In the future, this should support returning rich text content,
-     * so perhaps we should make it return a DOM node now and have the caller
-     * convert that to text if it doesn't support rich content.
-     *
-     * @param   pageContent
-     *          the content of the page being summarized
-     * @returns the text result of processing the template
-     *
-     */
-    AString generateMicrosummary(in nsIDOMNode aPageContent);
-
-    /**
-     * Calculate the interval until the microsummary should be updated for
-     * the next time, depending on the page content. If the generator doesn't
-     * specify an interval, null is returned.
-     *
-     * @param   pageContent
-     *          the content of the page being summarized
-     * @returns the interval in milliseconds until the next update request
-     *
-     */
-    long calculateUpdateInterval(in nsIDOMNode aPageContent);
-};
-
-
-[scriptable, uuid(6867dc21-077f-4462-937d-cd8b7c680e0c)]
-interface nsIMicrosummary : nsISupports
-{
-    // the URI of the page being summarized
-    readonly attribute nsIURI pageURI;
-
-    // The generator that generates this microsummary. May need to be loaded.
-    attribute nsIMicrosummaryGenerator generator;
-
-    // The content of the microsummary.
-    // Since generators and pages can be remote resources, and we need them
-    // to generate the content, this may not always be available.
-    readonly attribute AString content;
-
-    // The minimum interval after which this microsummary would like to be refreshed
-    // (or null if it doesn't care).
-    readonly attribute long updateInterval;
-
-    // Used when a site author has indicated that a microsummary (or an
-    // associated generator) will no longer be available.
-    readonly attribute boolean needsRemoval;
-
-    /**
-     * Add a microsummary observer to this microsummary.
-     *
-     * @param observer
-     *        the microsummary observer to add
-     *
-     */
-    void addObserver(in nsIMicrosummaryObserver observer);
-
-    /**
-     * Remove a microsummary observer from this microsummary.
-     *
-     * @param observer
-     *        the microsummary observer to remove
-     *
-     */
-    void removeObserver(in nsIMicrosummaryObserver observer);
-
-    /**
-     * Microsummary equivalence test
-     * Microsummaries equal if they summarize the same page with the same
-     * microsummary-generator (see also nsIMicrosummaryGenerator::equals).
-     *
-     * Note: this method returns false if either objects don't have a generator
-     */
-    boolean equals(in nsIMicrosummary aOther);
-
-    /**
-     * Update the microsummary, first loading its generator and page content
-     * as necessary.  If you want know when a microsummary finishes updating,
-     * add an observer before calling this method.
-     *
-     */
-    void update();
-};
-
-[scriptable, uuid(7111e88d-fecd-4b17-b7a9-1fa74e23153f)]
-interface nsIMicrosummarySet : nsISupports
-{
-    /**
-     * Add a microsummary observer to this microsummary set.  Adding an observer
-     * to a set is the equivalent of adding it to each constituent microsummary.
-     *
-     * @param observer
-     *        the microsummary observer to add
-     *
-     */
-    void addObserver(in nsIMicrosummaryObserver observer);
-
-    /**
-     * Remove a microsummary observer from this microsummary.
-     *
-     * @param observer
-     *        the microsummary observer to remove
-     *
-     */
-    void removeObserver(in nsIMicrosummaryObserver observer);
-
-    /**
-     * Retrieve a enumerator of microsummaries in the set.
-     *
-     * @returns an enumerator of nsIMicrosummary objects
-     *
-     */
-    nsISimpleEnumerator Enumerate();
-};
-
-
-[scriptable, uuid(D58143A2-74FA-4B13-94ED-113AF8936D80)]
-interface nsIMicrosummaryService : nsISupports
-{
-    /**
-     * Return a microsummary generator for the given URI.
-     *
-     * @param   generatorURI
-     *          the URI of the generator
-     *
-     * @returns an nsIMicrosummaryGenerator for the given URI.
-     *
-     */
-    nsIMicrosummaryGenerator getGenerator(in nsIURI generatorURI);
-
-    /**
-     * Install the microsummary generator from the resource at the supplied URI.
-     * Callable by content via the addMicrosummaryGenerator() sidebar method.
-     *
-     * @param   generatorURI
-     *          the URI of the resource providing the generator
-     *
-     */
-    void addGenerator(in nsIURI generatorURI);
-
-    /**
-     * Install the microsummary generator in the given XML definition.
-     *
-     * @param   xmlDefinition
-     *          an nsIDOMDocument XML document defining the generator
-     *
-     * @returns the newly-installed nsIMicrosummaryGenerator object
-     *
-     */
-    nsIMicrosummaryGenerator installGenerator(in nsIDOMDocument xmlDefinition);
-
-    /**
-     * Get the set of bookmarks with microsummaries.
-     *
-     * In the old RDF-based bookmarks datastore, bookmark IDs are nsIRDFResource
-     * objects.  In the new Places-based datastore, they are nsIURI objects.
-     *
-     * @returns an nsISimpleEnumerator enumeration of bookmark IDs
-     *
-     */
-    nsISimpleEnumerator getBookmarks();
-
-    /**
-     * Get the set of microsummaries available for a given page.  The set
-     * might change after this method returns, since this method will trigger
-     * an asynchronous load of the page in question (if it isn't already loaded)
-     * to see if it references any page-specific microsummaries.
-     *
-     * If the caller passes a bookmark ID, and one of the microsummaries
-     * is the current one for the bookmark, this method will retrieve content
-     * from the datastore for that microsummary, which is useful when callers
-     * want to display a list of microsummaries for a page that isn't loaded,
-     * and they want to display the actual content of the selected microsummary
-     * immediately (rather than after the content is asynchronously loaded).
-     *
-     * @param   pageURI
-     *          the URI of the page for which to retrieve available microsummaries
-     *
-     * @param   bookmarkID (optional)
-     *          the ID of the bookmark for which this method is being called
-     *
-     * @returns an nsIMicrosummarySet of nsIMicrosummaries for the given page
-     *
-     */
-    nsIMicrosummarySet getMicrosummaries(in nsIURI pageURI,
-                                         in long long bookmarkID);
-
-    /**
-     * Get the current microsummary for the given bookmark.
-     *
-     * @param   bookmarkID
-     *          the bookmark for which to get the current microsummary
-     *
-     * @returns the current microsummary for the bookmark, or null
-     *          if the bookmark does not have a current microsummary
-     *
-     */
-    nsIMicrosummary getMicrosummary(in long long bookmarkID);
-
-    /**
-     * Create a microsummary for a given page URI and generator URI.
-     *
-     * @param   pageURI
-     *          the URI of the page to be summarized
-     *
-     * @param   generatorURI
-     *          the URI of the microsummary generator
-     *
-     * @returns an nsIMicrosummary for the given page and generator URIs.
-     *
-     */
-    nsIMicrosummary createMicrosummary(in nsIURI pageURI, in nsIURI generatorURI);
-
-    /**
-     * Set the current microsummary for the given bookmark.
-     *
-     * @param   bookmarkID
-     *          the bookmark for which to set the current microsummary
-     *
-     * @param   microsummary
-     *          the microsummary to set as the current one
-     *
-     */
-    void setMicrosummary(in long long bookmarkID,
-                         in nsIMicrosummary microsummary);
-
-    /**
-     * Remove the current microsummary for the given bookmark.
-     *
-     * @param   bookmarkID
-     *          the bookmark for which to remove the current microsummary
-     *
-     */
-    void removeMicrosummary(in long long bookmarkID);
-
-    /**
-     * Whether or not the given bookmark has a current microsummary.
-     *
-     * @param   bookmarkID
-     *          the bookmark for which to set the current microsummary
-     *
-     * @returns a boolean representing whether or not the given bookmark
-     *          has a current microsummary
-     *
-     */
-    boolean hasMicrosummary(in long long bookmarkID);
-
-    /**
-     * Whether or not the given microsummary is the current microsummary
-     * for the given bookmark.
-     *
-     * @param   bookmarkID
-     *          the bookmark to check
-     *
-     * @param   microsummary
-     *          the microsummary to check
-     *
-     * @returns whether or not the microsummary is the current one
-     *          for the bookmark
-     *
-     */
-    boolean isMicrosummary(in long long bookmarkID,
-                           in nsIMicrosummary microsummary);
-
-    /**
-     * Refresh a microsummary, updating its value in the datastore and UI.
-     * If this method can refresh the microsummary instantly, it will.
-     * Otherwise, it'll asynchronously download the necessary information
-     * (the generator and/or page) before refreshing the microsummary.
-     *
-     * Callers should check the "content" property of the returned microsummary
-     * object to distinguish between sync and async refreshes.  If its value
-     * is "null", then it's an async refresh, and the caller should register
-     * itself as an nsIMicrosummaryObserver via nsIMicrosummary.addObserver()
-     * to find out when the refresh completes.
-     *
-     * @param   bookmarkID
-     *          the bookmark whose microsummary is being refreshed
-     *
-     * @returns the microsummary being refreshed
-     *
-     */
-    nsIMicrosummary refreshMicrosummary(in long long bookmarkID);
-};
-
-[scriptable, uuid(f9e577a8-19d9-4ca0-a140-b9e43f014470)]
-interface nsILiveTitleNotificationSubject : nsISupports
-{
-    // The ID of the bookmark displaying this title.
-    // Note: in the old bookmarks code, this is an RDF resource.  In Places
-    // it is currently a URI, but after the fix for bug 360133 lands it will
-    // become an integer.
-    readonly attribute long long bookmarkID;
-
-    // The microsummary being displayed as the live title for the bookmark.
-    // The actual value of the microsummary (i.e. the string that gets displayed
-    // to the user) is stored in the content property of this object.
-    readonly attribute nsIMicrosummary microsummary;
-};
deleted file mode 100644
--- a/toolkit/components/places/nsMicrosummaryService.js
+++ /dev/null
@@ -1,2150 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License
- * Version 1.1 (the "License"); you may not use this file except in
- * compliance with the License. You may obtain a copy of the License
- * at http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS"
- * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
- * the License for the specific language governing rights and
- * limitations under the License.
- *
- * The Original Code is Microsummarizer.
- *
- * The Initial Developer of the Original Code is the Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2006
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *  Myk Melez <myk@mozilla.org> (Original Author)
- *  Simon B├╝nzli <zeniko@gmail.com>
- *  Asaf Romano <mano@mozilla.com>
- *  Dan Mills <thunder@mozilla.com>
- *  Ryan Flint <rflint@dslr.net>
- *  Dietrich Ayala <dietrich@mozilla.com>
- *  Marco Bonardo <mak77@bonardo.net>
- *
- * Alternatively, the contents of this file may be used under the
- * terms of either the GNU General Public License Version 2 or later
- * (the "GPL"), or the GNU Lesser General Public License Version 2.1
- * or later (the "LGPL"), in which case the provisions of the GPL or
- * the LGPL are applicable instead of those above. If you wish to
- * allow use of your version of this file only under the terms of
- * either the GPL or the LGPL, and not to allow others to use your
- * version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the
- * notice and other provisions required by the GPL or the LGPL. If you
- * do not delete the provisions above, a recipient may use your
- * version of this file under the terms of any one of the MPL, the GPL
- * or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cr = Components.results;
-const Cu = Components.utils;
-
-const PERMS_FILE    = 0644;
-const MODE_WRONLY   = 0x02;
-const MODE_CREATE   = 0x08;
-const MODE_TRUNCATE = 0x20;
-
-const NS_ERROR_MODULE_DOM = 2152923136;
-const NS_ERROR_DOM_BAD_URI = NS_ERROR_MODULE_DOM + 1012;
-
-// How often to check for microsummaries that need updating, in milliseconds.
-const CHECK_INTERVAL = 15 * 1000; // 15 seconds
-
-// The default value of the update interval, used if there is no user's pref.
-const DEFAULT_UPDATE_INTERVAL_MINUTES = 30;
-
-const MICSUM_NS = "http://www.mozilla.org/microsummaries/0.1";
-const XSLT_NS = "http://www.w3.org/1999/XSL/Transform";
-
-const ANNO_MICSUM_GEN_URI    = "microsummary/generatorURI";
-const ANNO_MICSUM_EXPIRATION = "microsummary/expiration";
-const ANNO_STATIC_TITLE      = "bookmarks/staticTitle";
-const ANNO_CONTENT_TYPE      = "bookmarks/contentType";
-
-const MAX_SUMMARY_LENGTH = 4096;
-const MAX_GENERATOR_NAME_LENGTH = 60;
-const MIN_GENERATOR_NAME_LENGTH = 6;
-
-const USER_MICROSUMMARY_GENS_DIR = "microsummary-generators";
-
-const TOPIC_SHUTDOWN = "places-shutdown";
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
-  Cu.import("resource://gre/modules/NetUtil.jsm");
-  return NetUtil;
-});
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-function MicrosummaryService() {
-  Services.obs.addObserver(this, TOPIC_SHUTDOWN, true);
-
-  this._ans = Cc["@mozilla.org/browser/annotation-service;1"].
-              getService(Ci.nsIAnnotationService);
-  this._ans.addObserver(this, false);
-  Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).
-                                           getBranch("browser.microsummary.").
-                                           QueryInterface(Ci.nsIPrefBranch2).
-                                           addObserver("", this, true);
-  this._initTimers();
-  this._cacheLocalGenerators();
-}
-
-MicrosummaryService.prototype = {
-  get _bms() {
-    var svc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
-              getService(Ci.nsINavBookmarksService);
-    this.__defineGetter__("_bms", function() svc);
-    return this._bms;
-  },
-
-  // The update interval as specified by the user.
-  get _updateInterval() {
-    var updateInterval = getPref("browser.microsummary.updateInterval",
-                                 DEFAULT_UPDATE_INTERVAL_MINUTES);
-    // the minimum update interval is 1 minute
-    return Math.max(updateInterval, 1) * 60 * 1000;
-  },
-
-  // A cache of local microsummary generators.  This gets built on startup
-  // by the _cacheLocalGenerators() method.
-  _localGenerators: {},
-
-  // The timer that periodically checks for microsummaries needing updating.
-  _timer: null,
-
-  // XPCOM registration
-  classID: Components.ID("{460a9792-b154-4f26-a922-0f653e2c8f91}"),
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummaryService, 
-                                         Ci.nsITimerCallback,
-                                         Ci.nsISupportsWeakReference,
-                                         Ci.nsIAnnotationObserver,
-                                         Ci.nsIObserver]),
-
-  // nsIObserver
-  observe: function MSS_observe(subject, topic, data) {
-    switch (topic) {
-      case TOPIC_SHUTDOWN:
-        this._destroy();
-        break;
-      case "nsPref:changed":
-        if (data == "enabled")
-          this._initTimers();
-        break;
-    }
-  },
-  // cross-session timer used to periodically check for generator or
-  // microsummaries updates.
-  notify: function MSS_notify(timer) {
-    if (timer == this._timer)
-      this._updateMicrosummaries();
-    else
-      this._updateGenerators();
-  },
-  _initTimers: function MSS__initTimers() {
-    if (this._timer)
-      this._timer.cancel();
-
-    if (!getPref("browser.microsummary.enabled", true))
-      return;
-
-    // Periodically update microsummaries that need updating.
-    this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-    this._timer.initWithCallback(this, CHECK_INTERVAL,
-                                 this._timer.TYPE_REPEATING_SLACK);
-  },
-
-  _destroy: function MSS__destroy() {
-    Services.obs.removeObserver(this, TOPIC_SHUTDOWN, true);
-    this._ans.removeObserver(this);
-    Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).
-                                             getBranch("browser.microsummary.").
-                                             QueryInterface(Ci.nsIPrefBranch2).
-                                             removeObserver("", this);
-    this._timer.cancel();
-    this._timer = null;
-  },
-
-  _updateMicrosummaries: function MSS__updateMicrosummaries() {
-    var now = Date.now();
-    var updateInterval = this._updateInterval;
-    for (let i = 0; i < this._bookmarks.length; i++) {
-      var bookmarkID = this._bookmarks[i];
-
-      // Skip this page if its microsummary hasn't expired yet.
-      if (this._ans.itemHasAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION) &&
-          this._ans.getItemAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION) > now)
-        continue;
-
-      // Reset the expiration time immediately, so if the refresh is failing
-      // we don't try it every 15 seconds, potentially overloading the server.
-      this._setAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION, now + updateInterval);
-
-      // Try to update the microsummary, but trap errors, so an update
-      // that throws doesn't prevent us from updating the rest of them.
-      try {
-        this.refreshMicrosummary(bookmarkID);
-      }
-      catch(ex) {
-        Cu.reportError(ex);
-      }
-    }
-  },
-
-  _updateGenerators: function MSS__updateGenerators() {
-    var generators = this._localGenerators;
-    var update = getPref("browser.microsummary.updateGenerators", true);
-    if (!generators || !update)
-      return;
-    for (let uri in generators) {
-      generators[uri].update();
-    }
-  },
-  _updateMicrosummary: function MSS__updateMicrosummary(bookmarkID, microsummary) {
-    var title = this._bms.getItemTitle(bookmarkID);
-
-    // Ensure the user-given title is cached
-    if (!this._ans.itemHasAnnotation(bookmarkID, ANNO_STATIC_TITLE))
-      this._setAnnotation(bookmarkID, ANNO_STATIC_TITLE, title);
-
-    // A string identifying the bookmark to use when logging the update.
-    var bookmarkIdentity = bookmarkID;
-
-    // Update if the microsummary differs from the current title.
-    if (!title || title != microsummary.content) {
-      this._bms.setItemTitle(bookmarkID, microsummary.content);
-      var subject = new LiveTitleNotificationSubject(bookmarkID, microsummary);
-      LOG("updated live title for " + bookmarkIdentity +
-          " from '" + (title == null ? "<no live title>" : title) +
-          "' to '" + microsummary.content + "'");
-      Services.obs.notifyObservers(subject, "microsummary-livetitle-updated", title);
-    }
-    else {
-      LOG("didn't update live title for " + bookmarkIdentity + "; it hasn't changed");
-    }
-
-    // Whether or not the title itself has changed, we still save any changes
-    // to the update interval, since the interval represents how long to wait
-    // before checking again for updates, and that can vary across updates,
-    // even when the title itself hasn't changed.
-    this._setAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION,
-                  Date.now() + (microsummary.updateInterval || this._updateInterval));
-  },
-
-  /**
-   * Load local generators into the cache.
-   */
-  _cacheLocalGenerators: function MSS__cacheLocalGenerators() {
-    // Load generators from the user's profile.
-    var msDir = Services.dirsvc.get("ProfDS", Ci.nsIFile);
-    msDir.append(USER_MICROSUMMARY_GENS_DIR);
-    if (msDir.exists())
-      this._cacheLocalGeneratorDir(msDir);
-  },
-
-  /**
-   * Load local generators from a directory into the cache.
-   *
-   * @param   dir
-   *          nsIFile object pointing to directory containing generator files
-   */
-  _cacheLocalGeneratorDir: function MSS__cacheLocalGeneratorDir(dir) {
-    var files = dir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
-    var file = files.nextFile;
-
-    while (file) {
-      // Recursively load generators so support packs containing
-      // lots of generators can organize them into multiple directories.
-      if (file.isDirectory())
-        this._cacheLocalGeneratorDir(file);
-      else
-        this._cacheLocalGeneratorFile(file);
-
-      file = files.nextFile;
-    }
-    files.close();
-  },
-
-  /**
-   * Load a local generator from a file into the cache.
-   * 
-   * @param   file
-   *          nsIFile object pointing to file from which to load generator
-   */
-  _cacheLocalGeneratorFile: function MSS__cacheLocalGeneratorFile(file) {
-    var uri = NetUtil.ioService.newFileURI(file);
-
-    var t = this;
-    var callback =
-      function MSS_cacheLocalGeneratorCallback(resource) {
-        try {
-          t._handleLocalGenerator(resource);
-        }
-        finally {
-          resource.destroy();
-        }
-      };
-    var resource = new MicrosummaryResource(uri);
-    resource.load(callback);
-  },
-
-  _handleLocalGenerator: function MSS__handleLocalGenerator(resource) {
-    if (!resource.isXML)
-      throw(resource.uri.spec + " microsummary generator loaded, but not XML");
-
-    var generator = new MicrosummaryGenerator(null, resource.uri);
-    generator.initFromXML(resource.content);
-
-    // Add the generator to the local generators cache.
-    // XXX Figure out why Firefox crashes on shutdown if we index generators
-    // by uri.spec but doesn't crash if we index by uri.spec.split().join().
-    //this._localGenerators[generator.uri.spec] = generator;
-    this._localGenerators[generator.uri.spec.split().join()] = generator;
-
-    LOG("loaded local microsummary generator\n" +
-        "  file: " + generator.localURI.spec + "\n" +
-        "    ID: " + generator.uri.spec);
-  },
-
-  // nsIMicrosummaryService
-
-  /**
-   * Return a microsummary generator for the given URI.
-   *
-   * @param   generatorURI
-   *          the URI of the generator
-   */
-  getGenerator: function MSS_getGenerator(generatorURI) {
-    return this._localGenerators[generatorURI.spec] ||
-           new MicrosummaryGenerator(generatorURI);
-  },
-  /**
-   * Install the microsummary generator from the resource at the supplied URI.
-   * Callable by content via the addMicrosummaryGenerator() sidebar method.
-   *
-   * @param   generatorURI
-   *          the URI of the resource providing the generator
-   */
-  addGenerator: function MSS_addGenerator(generatorURI) {
-    var t = this;
-    var callback =
-      function MSS_addGeneratorCallback(resource) {
-        try {
-          t._handleNewGenerator(resource);
-        }
-        finally {
-          resource.destroy();
-        }
-      };
-    var resource = new MicrosummaryResource(generatorURI);
-    resource.load(callback);
-  },
-
-  _handleNewGenerator: function MSS__handleNewGenerator(resource) {
-    if (!resource.isXML)
-      throw(resource.uri.spec + " microsummary generator loaded, but not XML");
-
-    // XXX Make sure it's a valid microsummary generator.
-
-    var rootNode = resource.content.documentElement;
-
-    // Add a reference to the URI from which we got this generator so we have
-    // a unique identifier for the generator and also so we can check back later
-    // for updates.
-    rootNode.setAttribute("uri", "urn:source:" + resource.uri.spec);
-
-    this.installGenerator(resource.content);
-  },
- 
-  /**
-   * Install a microsummary generator from the given XML definition.
-   *
-   * @param   xmlDefinition
-   *          an nsIDOMDocument XML document defining the generator
-   *
-   * @returns the newly-installed nsIMicrosummaryGenerator generator
-   */
-  installGenerator: function MSS_installGenerator(xmlDefinition) {
-    var rootNode = xmlDefinition.getElementsByTagNameNS(MICSUM_NS, "generator")[0];
- 
-    var generatorID = rootNode.getAttribute("uri");
- 
-    // The existing cache entry for this generator, if it is already installed.
-    var generator = this._localGenerators[generatorID];
-
-    var topic;
-    if (generator)
-      topic = "microsummary-generator-updated";
-    else {
-      // This generator is not already installed.  Save it as a new file.
-      topic = "microsummary-generator-installed";
-      var generatorName = rootNode.getAttribute("name");
-      var fileName = sanitizeName(generatorName) + ".xml";
-      var file = Services.dirsvc.get("ProfDS", Ci.nsIFile);
-      file.append(USER_MICROSUMMARY_GENS_DIR);
-      if (!file.exists() || !file.isDirectory()) {
-        file.create(Ci.nsIFile.DIRECTORY_TYPE, 0777);
-      }
-
-      file.append(fileName);
-      file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
-      generator = new MicrosummaryGenerator(null,
-                                            NetUtil.ioService.newFileURI(file));
-      this._localGenerators[generatorID] = generator;
-    }
- 
-    // Initialize (or reinitialize) the generator from its XML definition,
-    // the save the definition to the generator's file.
-    generator.initFromXML(xmlDefinition);
-    generator.saveXMLToFile(xmlDefinition);
-
-    LOG("installed generator " + generatorID);
-
-    Services.obs.notifyObservers(generator, topic, null);
-
-    return generator;
-  },
-
-  /**
-   * Get the set of microsummaries available for a given page.  The set
-   * might change after this method returns, since this method will trigger
-   * an asynchronous load of the page in question (if it isn't already loaded)
-   * to see if it references any page-specific microsummaries.
-   *
-   * If the caller passes a bookmark ID, and one of the microsummaries
-   * is the current one for the bookmark, this method will retrieve content
-   * from the datastore for that microsummary, which is useful when callers
-   * want to display a list of microsummaries for a page that isn't loaded,
-   * and they want to display the actual content of the selected microsummary
-   * immediately (rather than after the content is asynchronously loaded).
-   *
-   * @param   pageURI
-   *          the URI of the page for which to retrieve available microsummaries
-   *
-   * @param   bookmarkID (optional)
-   *          the ID of the bookmark for which this method is being called
-   *
-   * @returns an nsIMicrosummarySet of nsIMicrosummaries for the given page
-   */
-  getMicrosummaries: function MSS_getMicrosummaries(pageURI, bookmarkID) {
-    var microsummaries = new MicrosummarySet();
-
-    if (!getPref("browser.microsummary.enabled", true))
-      return microsummaries;
-    // Get microsummaries defined by local generators.
-    for (let genURISpec in this._localGenerators) {
-      var generator = this._localGenerators[genURISpec];
-      if (generator.appliesToURI(pageURI)) {
-        var microsummary = new Microsummary(pageURI, generator);
-
-        // If this is the current microsummary for this bookmark, load the content
-        // from the datastore so it shows up immediately in microsummary picking UI.
-        if (bookmarkID != -1 && this.isMicrosummary(bookmarkID, microsummary))
-          microsummary._content = this._bms.getItemTitle(bookmarkID);
-
-        microsummaries.AppendElement(microsummary);
-      }
-    }
-
-    // If a bookmark identifier has been provided, list its microsummary
-    // synchronously, if any.
-    if (bookmarkID != -1 && this.hasMicrosummary(bookmarkID)) {
-      var currentMicrosummary = this.getMicrosummary(bookmarkID);
-      if (!microsummaries.hasItemForMicrosummary(currentMicrosummary))
-        microsummaries.AppendElement(currentMicrosummary);
-    }
-
-    // Get microsummaries defined by the page.  If we don't have the page,
-    // download it asynchronously, and then finish populating the set.
-    var resource = getLoadedMicrosummaryResource(pageURI);
-    if (resource) {
-      try {
-        microsummaries.extractFromPage(resource);
-      }
-      finally {
-        resource.destroy();
-      }
-    }
-    else {
-      // Load the page with a callback that will add the page's microsummaries
-      // to the set once the page has loaded.
-      var callback = function MSS_extractFromPageCallback(resource) {
-        try {
-          microsummaries.extractFromPage(resource);
-        }
-        finally {
-          resource.destroy();
-        }
-      };
-      try {
-        resource = new MicrosummaryResource(pageURI);
-        resource.load(callback);
-      }
-      catch(e) {
-        // We don't have to do anything special if the call fails besides
-        // destroying the Resource object.  We can just return the list
-        // of microsummaries without including page-defined microsummaries.
-        if (resource)
-          resource.destroy();
-        LOG("error downloading page to extract its microsummaries: " + e);
-      }
-    }
-
-    return microsummaries;
-  },
-
-  /**
-   * Change all occurrences of a specific value in a given field to a new value.
-   *
-   * @param   fieldName
-   *          the name of the field whose values should be changed
-   * @param   oldValue
-   *          the value that should be changed
-   * @param   newValue
-   *          the value to which it should be changed
-   */
-  _changeField: function MSS__changeField(fieldName, oldValue, newValue) {
-    for (let i = 0; i < this._bookmarks.length; i++) {
-      var bookmarkID = this._bookmarks[i];
-      if (this._ans.itemHasAnnotation(bookmarkID, fieldName) &&
-          this._ans.getItemAnnotation(bookmarkID, fieldName) == oldValue)
-        this._setAnnotation(bookmarkID, fieldName, newValue);
-    }
-  },
-
-  /**
-   * Get the set of bookmarks that have microsummaries.
-   *
-   * This caches the list of microsummarized bookmarks. The cache is
-   * managed by observing the annotation service, and updating 
-   * when a microsummary annotation is added or removed.
-   *
-   * @returns an array of item ids for microsummarized bookmarks
-   */
-  get _bookmarks() {
-    var bookmarks = this._ans.getItemsWithAnnotation(ANNO_MICSUM_GEN_URI);
-    this.__defineGetter__("_bookmarks", function() bookmarks);
-    return this._bookmarks;
-  },
-
-  _setAnnotation: function MSS__setAnnotation(aBookmarkId, aFieldName, aFieldValue) {
-    this._ans.setItemAnnotation(aBookmarkId,
-                                aFieldName,
-                                aFieldValue,
-                                0,
-                                this._ans.EXPIRE_NEVER);
-  },
-
-  /**
-   * Get the set of bookmarks with microsummaries.
-   *
-   * This is the external version of this method and is accessible via XPCOM.
-   * Use it outside this component. Inside the component, use _bookmarks
-   * (with underscore prefix) instead for performance.
-   *
-   * @returns an nsISimpleEnumerator enumeration of bookmark IDs
-   */
-  getBookmarks: function MSS_getBookmarks() {
-    return new ArrayEnumerator(this._bookmarks);
-  },
-
-  /**
-   * Get the current microsummary for the given bookmark.
-   *
-   * @param   bookmarkID
-   *          the bookmark for which to get the current microsummary
-   *
-   * @returns the current microsummary for the bookmark, or null
-   *          if the bookmark does not have a current microsummary
-   */
-  getMicrosummary: function MSS_getMicrosummary(bookmarkID) {
-    if (!this.hasMicrosummary(bookmarkID))
-      return null;
-
-    var pageURI = this._bms.getBookmarkURI(bookmarkID);
-    var generatorURI = NetUtil.newURI(this._ans.getItemAnnotation(bookmarkID,
-                                                                  ANNO_MICSUM_GEN_URI));
-    var generator = this.getGenerator(generatorURI);
-
-    return new Microsummary(pageURI, generator);
-  },
-
-  /**
-   * Get a microsummary for a given page URI and generator URI.
-   *
-   * @param   pageURI
-   *          the URI of the page to be summarized
-   *
-   * @param   generatorURI
-   *          the URI of the microsummary generator
-   *
-   * @returns an nsIMicrosummary for the given page and generator URIs.
-   */
-  createMicrosummary: function MSS_createMicrosummary(pageURI, generatorURI) {
-    var generator = this.getGenerator(generatorURI);
-    return new Microsummary(pageURI, generator);
-  },
-
-  /**
-   * Set the current microsummary for the given bookmark.
-   *
-   * @param   bookmarkID
-   *          the bookmark for which to set the current microsummary
-   *
-   * @param   microsummary
-   *          the microsummary to set as the current one
-   */
-  setMicrosummary: function MSS_setMicrosummary(bookmarkID, microsummary) {
-    this._setAnnotation(bookmarkID, ANNO_MICSUM_GEN_URI, microsummary.generator.uri.spec);
-
-    if (microsummary.content)
-      this._updateMicrosummary(bookmarkID, microsummary);
-    else
-      this.refreshMicrosummary(bookmarkID);
-  },
-
-  /**
-   * Remove the current microsummary for the given bookmark.
-   *
-   * @param   bookmarkID
-   *          the bookmark for which to remove the current microsummary
-   */
-  removeMicrosummary: function MSS_removeMicrosummary(bookmarkID) {
-    // Restore the user's title
-    if (this._ans.itemHasAnnotation(bookmarkID, ANNO_STATIC_TITLE))
-      this._bms.setItemTitle(bookmarkID, this._ans.getItemAnnotation(bookmarkID, ANNO_STATIC_TITLE));
-
-    var fields = [ANNO_MICSUM_GEN_URI,
-                  ANNO_MICSUM_EXPIRATION,
-                  ANNO_STATIC_TITLE,
-                  ANNO_CONTENT_TYPE];
-
-    for (let i = 0; i < fields.length; i++) {
-      var field = fields[i];
-      if (this._ans.itemHasAnnotation(bookmarkID, field))
-        this._ans.removeItemAnnotation(bookmarkID, field);
-    }
-  },
-
-  /**
-   * Whether or not the given bookmark has a current microsummary.
-   *
-   * @param   bookmarkId
-   *          the bookmark id to check
-   *
-   * @returns a boolean representing whether or not the given bookmark
-   *          currently has a microsummary
-   */
-  hasMicrosummary: function MSS_hasMicrosummary(aBookmarkId) {
-    return (this._bookmarks.indexOf(aBookmarkId) != -1);
-  },
-
-  /**
-   * Whether or not the given microsummary is the current microsummary
-   * for the given bookmark.
-   *
-   * @param   bookmarkID
-   *          the bookmark to check
-   *
-   * @param   microsummary
-   *          the microsummary to check
-   *
-   * @returns whether or not the microsummary is the current one
-   *          for the bookmark
-   */
-  isMicrosummary: function MSS_isMicrosummary(aBookmarkID, aMicrosummary) {
-    if (!aMicrosummary || !aBookmarkID)
-      throw Cr.NS_ERROR_INVALID_ARG;
-
-    if (this.hasMicrosummary(aBookmarkID)) {
-      var currentMicrosummarry = this.getMicrosummary(aBookmarkID);
-      if (aMicrosummary.equals(currentMicrosummarry))
-        return true;
-    }
-    return false
-  },
-
-  /**
-   * Refresh a microsummary, updating its value in the datastore and UI.
-   * If this method can refresh the microsummary instantly, it will.
-   * Otherwise, it'll asynchronously download the necessary information
-   * (the generator and/or page) before refreshing the microsummary.
-   *
-   * Callers should check the "content" property of the returned microsummary
-   * object to distinguish between sync and async refreshes.  If its value
-   * is "null", then it's an async refresh, and the caller should register
-   * itself as an nsIMicrosummaryObserver via nsIMicrosummary.addObserver()
-   * to find out when the refresh completes.
-   *
-   * @param   bookmarkID
-   *          the bookmark whose microsummary is being refreshed
-   *
-   * @returns the microsummary being refreshed
-   */
-  refreshMicrosummary: function MSS_refreshMicrosummary(bookmarkID) {
-    if (!this.hasMicrosummary(bookmarkID))
-      throw "bookmark " + bookmarkID + " does not have a microsummary";
-
-    var pageURI = this._bms.getBookmarkURI(bookmarkID);
-    if (!pageURI)
-      throw("can't get URL for bookmark with ID " + bookmarkID);
-    var generatorURI = NetUtil.newURI(this._ans.getItemAnnotation(bookmarkID,
-                                                                  ANNO_MICSUM_GEN_URI));
-
-    var generator = this._localGenerators[generatorURI.spec] ||
-                    new MicrosummaryGenerator(generatorURI);
-
-    var microsummary = new Microsummary(pageURI, generator);
-
-    // A microsummary observer that calls the microsummary service
-    // to update the datastore when the microsummary finishes loading.
-    var observer = {
-      _svc: this,
-      _bookmarkID: bookmarkID,
-      onContentLoaded: function MSS_observer_onContentLoaded(microsummary) {
-        try {
-          this._svc._updateMicrosummary(this._bookmarkID, microsummary);
-        }
-        catch (ex) {
-          Cu.reportError("refreshMicrosummary() observer: " + ex);
-        }
-        finally {
-          this._svc = null;
-          this._bookmarkID = null;
-          microsummary.removeObserver(this);
-        }
-      },
-
-      onError: function MSS_observer_onError(microsummary) {
-        if (microsummary.needsRemoval)
-          this._svc.removeMicrosummary(this._bookmarkID);
-      }
-    };
-
-    // Register the observer with the microsummary and trigger the microsummary
-    // to update itself.
-    microsummary.addObserver(observer);
-    microsummary.update();
-    
-    return microsummary;
-  },
-
-  // nsIAnnotationObserver
-  onItemAnnotationSet: function(aItemId, aAnnotationName) {
-    if (aAnnotationName == ANNO_MICSUM_GEN_URI &&
-        this._bookmarks.indexOf(aItemId) == -1)
-      this._bookmarks.push(aItemId);
-  },
-  onItemAnnotationRemoved: function(aItemId, aAnnotationName) {
-    var index = this._bookmarks.indexOf(aItemId);
-    var isMicsumAnno = aAnnotationName == ANNO_MICSUM_GEN_URI ||
-                       !aAnnotationName.length; /* all annos were removed */
-    if (index > -1 && isMicsumAnno)
-      this._bookmarks.splice(index, 1);
-  },
-  onPageAnnotationSet: function(aUri, aAnnotationName) {},
-  onPageAnnotationRemoved: function(aUri, aAnnotationName) {},
-};
-
-
-
-
-
-function LiveTitleNotificationSubject(bookmarkID, microsummary) {
-  this.bookmarkID = bookmarkID;
-  this.microsummary = microsummary;
-}
-
-LiveTitleNotificationSubject.prototype = {
-  bookmarkID: null,
-  microsummary: null,
-
-  // nsISupports
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsILiveTitleNotificationSubject]),
-};
-
-
-
-
-
-function Microsummary(aPageURI, aGenerator) {
-  this._observers = [];
-  this._pageURI = aPageURI || null;
-  this._generator = aGenerator || null;
-  this._content = null;
-  this._pageContent = null;
-  this._updateInterval = null;
-  this._needsRemoval = false;
-}
-Microsummary.prototype = {
-  get _mss() {
-    var svc = Cc["@mozilla.org/microsummary/service;1"].
-              getService(Ci.nsIMicrosummaryService);
-    this.__defineGetter__("_mss", function() svc);
-    return this._mss;
-  },
-  // nsISupports
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummary]),
-
-  // nsIMicrosummary
-  get content() {
-    // If we have everything we need to generate the content, generate it.
-    if (!this._content && this.generator.loaded &&
-        (this.pageContent || !this.generator.needsPageContent)) {
-      this._content = this.generator.generateMicrosummary(this.pageContent);
-      this._updateInterval = this.generator.calculateUpdateInterval(this.pageContent);
-    }
-
-    // Note: we return "null" if the content wasn't already generated and we
-    // couldn't retrieve it from the generated title annotation or generate it
-    // ourselves.  So callers shouldn't count on getting content; instead,
-    // they should call update if the return value of this getter is "null",
-    // setting an observer to tell them when content generation is done.
-    return this._content;
-  },
-  get generator() this._generator,
-  set generator(newValue) this._generator = newValue,
-  get pageURI() this._pageURI,
-  equals: function(aOther) {
-    return this._generator &&
-           this._pageURI.equals(aOther.pageURI) &&
-           this._generator.equals(aOther.generator);
-  },
-  get pageContent() {
-    if (!this._pageContent) {
-      // If the page is currently loaded into a browser window, use that.
-      var resource = getLoadedMicrosummaryResource(this.pageURI);
-      if (resource) {
-        this._pageContent = resource.content;
-        resource.destroy();
-      }
-    }
-    return this._pageContent;
-  },
-  set pageContent(newValue) this._pageContent = newValue,
-  get updateInterval() this._updateInterval,
-  set updateInterval(newValue) this._updateInterval = newValue,
-  get needsRemoval() this._needsRemoval,
-  // nsIMicrosummary
-  addObserver: function MS_addObserver(observer) {
-    // Register the observer, but only if it isn't already registered,
-    // so that we don't call the same observer twice for any given change.
-    if (this._observers.indexOf(observer) == -1)
-      this._observers.push(observer);
-  },
-  
-  removeObserver: function MS_removeObserver(observer) {
-    if (this._observers.indexOf(observer) != -1)
-      this._observers.splice(this._observers.indexOf(observer), 1);
-  },
-
-  /**
-   * Regenerates the microsummary, asynchronously downloading its generator
-   * and content as needed.
-   */
-  update: function MS_update() {
-    LOG("microsummary.update called for page:\n  " + this.pageURI.spec +
-        "\nwith generator:\n  " + this.generator.uri.spec);
-
-    var t = this;
-
-    // We use a common error callback here to flag this microsummary for removal
-    // if either the generator or page content have gone permanently missing.
-    var errorCallback = function MS_errorCallback(resource) {
-      if (resource.status == 410) {
-        t._needsRemoval = true;
-        LOG("server indicated " + resource.uri.spec + " is gone. flagging for removal");
-      }
-      resource.destroy();
-      for (let i = 0; i < t._observers.length; i++) {
-        t._observers[i].onError(t);
-      }
-    };
-    // If we don't have the generator, download it now.  After it downloads,
-    // we'll re-call this method to continue updating the microsummary.
-    if (!this.generator.loaded) {
-      // If this generator is identified by a URN, then it's a local generator
-      // that should have been cached on application start, so it's missing.
-      if (this.generator.uri.scheme == "urn") {
-        // If it was installed via nsSidebar::addMicrosummaryGenerator (i.e. it
-        // has a URN that identifies the source URL from which we installed it),
-        // try to reinstall it (once).
-        if (/^source:/.test(this.generator.uri.path)) {
-          this._reinstallMissingGenerator();
-          return;
-        }
-        else
-          throw "missing local generator: " + this.generator.uri.spec;
-      }
-
-      LOG("generator not yet loaded; downloading it");
-      var generatorCallback =
-        function MS_generatorCallback(resource) {
-          try {
-            t._handleGeneratorLoad(resource);
-          }
-          finally {
-            resource.destroy();
-          }
-        };
-      var resource = new MicrosummaryResource(this.generator.uri);
-      resource.load(generatorCallback, errorCallback);
-      return;
-    }
-
-    // If we need the page content, and we don't have it, download it now.
-    // Afterwards we'll re-call this method to continue updating the microsummary.
-    if (this.generator.needsPageContent && !this.pageContent) {
-      LOG("page content not yet loaded; downloading it");
-      var pageCallback =
-        function MS_pageCallback(resource) {
-          try {
-            t._handlePageLoad(resource);
-          }
-          finally {
-            resource.destroy();
-          }
-        };
-      var resource = new MicrosummaryResource(this.pageURI);
-      resource.load(pageCallback, errorCallback);
-      return;
-    }
-
-    LOG("generator (and page, if needed) both loaded; generating microsummary");
-
-    // Now that we have both the generator and (if needed) the page content,
-    // generate the microsummary, then let the observers know about it.
-    this._content = this.generator.generateMicrosummary(this.pageContent);
-    this._updateInterval = this.generator.calculateUpdateInterval(this.pageContent);
-    this.pageContent = null;
-    for (let i = 0; i < this._observers.length; i++) {
-      this._observers[i].onContentLoaded(this);
-    }
-    LOG("generated microsummary: " + this.content);
-  },
-
-  _handleGeneratorLoad: function MS__handleGeneratorLoad(resource) {
-    LOG(this.generator.uri.spec + " microsummary generator downloaded");
-    if (resource.isXML)
-      this.generator.initFromXML(resource.content);
-    else if (resource.contentType == "text/plain")
-      this.generator.initFromText(resource.content);
-    else if (resource.contentType == "text/html")
-      this.generator.initFromText(resource.content.body.textContent);
-    else
-      throw("generator is neither XML nor plain text");
-
-    // Only trigger a [content] update if we were able to init the generator. 
-    if (this.generator.loaded)
-      this.update();
-  },
-
-  _handlePageLoad: function MS__handlePageLoad(resource) {
-    if (!resource.isXML && resource.contentType != "text/html")
-      throw("page is neither HTML nor XML");
-
-    this.pageContent = resource.content;
-    this.update();
-  },
-
-  /**
-   * Try to reinstall a missing local generator that was originally installed
-   * from a URL using nsSidebar::addMicrosummaryGenerator.
-   */
-  _reinstallMissingGenerator: function MS__reinstallMissingGenerator() {
-    LOG("attempting to reinstall missing generator " + this.generator.uri.spec);
-
-    var t = this;
-    var loadCallback =
-      function MS_missingGeneratorLoadCallback(resource) {
-        try {
-          t._handleMissingGeneratorLoad(resource);
-        }
-        finally {
-          resource.destroy();
-        }
-      };
-    var errorCallback =
-      function MS_missingGeneratorErrorCallback(resource) {
-        try {
-          t._handleMissingGeneratorError(resource);
-        }
-        finally {
-          resource.destroy();
-        }
-      };
-    try {
-      // Extract the URI from which the generator was originally installed.
-      var sourceURL = this.generator.uri.path.replace(/^source:/, "");
-      var sourceURI = NetUtil.newURI(sourceURL);
-
-      var resource = new MicrosummaryResource(sourceURI);
-      resource.load(loadCallback, errorCallback);
-    }
-    catch(ex) {
-      Cu.reportError(ex);
-      this._handleMissingGeneratorError();
-    }
-  },
-
-  /**
-   * Handle a load event for a missing local generator by trying to reinstall
-   * the generator.  If this fails, call _handleMissingGeneratorError to unset
-   * microsummaries for bookmarks using this generator so we don't repeatedly
-   * try to reinstall the generator, creating too much traffic to the website
-   * from which we downloaded it.
-   *
-   * @param resource
-   *        the nsIMicrosummaryResource representing the downloaded generator
-   */
-  _handleMissingGeneratorLoad: function MS__handleMissingGeneratorLoad(resource) {
-    try {
-      // Make sure the generator is XML, since local generators have to be.
-      if (!resource.isXML)
-        throw("downloaded, but not XML " + this.generator.uri.spec);
-
-      // Store the generator's ID in its XML definition.
-      var generatorID = this.generator.uri.spec;
-      resource.content.documentElement.setAttribute("uri", generatorID);
-
-      // Reinstall the generator and replace our placeholder generator object
-      // with the newly installed generator.
-      this.generator = this._mss.installGenerator(resource.content);
-
-      // A reinstalled generator should always be loaded.  But just in case
-      // it isn't, throw an error so we don't get into an infinite loop
-      // (otherwise this._update would try again to reinstall it).
-      if (!this.generator.loaded)
-        throw("supposedly installed, but not in cache " + this.generator.uri.spec);
-
-      LOG("reinstall succeeded; resuming update " + this.generator.uri.spec);
-      this.update();
-    }
-    catch(ex) {
-      Cu.reportError(ex);
-      this._handleMissingGeneratorError(resource);
-    }
-  },
-
-  /**
-   * Handle an error event for a missing local generator load by unsetting
-   * the microsummaries for bookmarks using this generator so we don't
-   * repeatedly try to reinstall the generator, creating too much traffic
-   * to the website from which we downloaded it.
-   *
-   * @param resource
-   *        the nsIMicrosummaryResource representing the downloaded generator
-   */
-  _handleMissingGeneratorError: function MS__handleMissingGeneratorError(resource) {
-    LOG("reinstall failed; removing microsummaries " + this.generator.uri.spec);
-    var bookmarks = this._mss.getBookmarks();
-    while (bookmarks.hasMoreElements()) {
-      var bookmarkID = bookmarks.getNext();
-      var microsummary = this._mss.getMicrosummary(bookmarkID);
-      if (microsummary.generator.uri.equals(this.generator.uri)) {
-        LOG("removing microsummary for " + microsummary.pageURI.spec);
-        this._mss.removeMicrosummary(bookmarkID);
-      }
-    }
-  }
-
-};
-
-
-
-
-
-function MicrosummaryGenerator(aURI, aLocalURI, aName) {
-  this._uri = aURI || null;
-  this._localURI = aLocalURI || null;
-  this._name = aName || null;
-  this._loaded = false;
-  this._rules = [];
-  this._template = null;
-  this._content = null;
-}
-
-MicrosummaryGenerator.prototype = {
-  // nsISupports
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummaryGenerator]),
-
-  // nsIMicrosummaryGenerator
-
-  // Normally this is just the URL from which we download the generator,
-  // but for generators stored in the app or profile generators directory
-  // it's the value of the generator tag's "uri" attribute (or its local URI
-  // should the "uri" attribute be missing).
-  get uri() this._uri || this.localURI,
-  // For generators bundled with the browser or installed by the user,
-  // the local URI is the URI of the local file containing the generator XML.
-  get localURI() this._localURI,
-  get name() this._name,
-  get loaded() this._loaded,
-  equals: function(aOther) {
-    // XXX: could the uri attribute for an exposed generator ever be null?
-    return aOther.uri.equals(this.uri);
-  },
-
-  /**
-   * Determines whether or not the generator applies to a given URI.
-   * By default, the generator does not apply to any URI.  In order for it
-   * to apply to a URI, the URI must match one or more of the generator's
-   * "include" rules and not match any of the generator's "exclude" rules.
-   *
-   * @param   uri
-   *          the URI to test to see if this generator applies to it
-   *
-   * @returns boolean
-   *          whether or not the generator applies to the given URI
-   */
-  appliesToURI: function(uri) {
-    var applies = false;
-    for (let i = 0; i < this._rules.length; i++) {
-      var rule = this._rules[i];
-      switch (rule.type) {
-      case "include":
-        if (rule.regexp.test(uri.spec))
-          applies = true;
-        break;
-      case "exclude":
-        if (rule.regexp.test(uri.spec))
-          return false;
-        break;
-      }
-    }
-
-    return applies;
-  },
-
-  get needsPageContent() {
-    if (this._template)
-      return true;
-    if (this._content)
-      return false;
-
-    throw("needsPageContent called on uninitialized microsummary generator");
-  },
-
-  /**
-   * Initializes a generator from text content.  Generators initialized
-   * from text content merely return that content when their generate() method
-   * gets called.
-   *
-   * @param   text
-   *          the text content
-   */
-  initFromText: function(text) {
-    this._content = text;
-    this._loaded = true;
-  },
-
-  /**
-   * Initializes a generator from an XML description of it.
-   * 
-   * @param   xmlDocument
-   *          An XMLDocument object describing a microsummary generator.
-   *
-   */
-  initFromXML: function(xmlDocument) {
-    // XXX Make sure the argument is a valid generator XML document.
-
-    // XXX I would have wanted to retrieve the info from the XML via E4X,
-    // but we'll need to pass the XSLT transform sheet to the XSLT processor,
-    // and the processor can't deal with an E4X-wrapped template node.
-
-    // XXX Right now the code retrieves the first "generator" element
-    // in the microsummaries namespace, regardless of whether or not
-    // it's the root element.  Should it matter?
-    var generatorNode = xmlDocument.getElementsByTagNameNS(MICSUM_NS, "generator")[0];
-    if (!generatorNode)
-      throw Cr.NS_ERROR_FAILURE;
-
-    this._name = generatorNode.getAttribute("name");
-
-    // We have to retrieve the URI from local generators via the "uri" attribute
-    // of its generator tag.
-    if (this.localURI && generatorNode.hasAttribute("uri"))
-      this._uri = NetUtil.newURI(generatorNode.getAttribute("uri"), null, null);
-    function getFirstChildByTagName(tagName, parentNode, namespace) {
-      var nodeList = parentNode.getElementsByTagNameNS(namespace, tagName);
-      for (let i = 0; i < nodeList.length; i++) {
-        // Make sure that the node is a direct descendent of the generator node
-        if (nodeList[i].parentNode == parentNode)
-          return nodeList[i];
-      }
-      return null;
-    }
-
-    // Slurp the include/exclude rules that determine the pages to which
-    // this generator applies.  Order is important, so we add the rules
-    // in the order in which they appear in the XML.
-    this._rules.splice(0);
-    var pages = getFirstChildByTagName("pages", generatorNode, MICSUM_NS);
-    if (pages) {
-      // XXX Make sure the pages tag exists.
-      for (let i = 0; i < pages.childNodes.length ; i++) {
-        var node = pages.childNodes[i];
-        if (node.nodeType != node.ELEMENT_NODE ||
-            node.namespaceURI != MICSUM_NS ||
-            (node.nodeName != "include" && node.nodeName != "exclude"))
-          continue;
-        var urlRegexp = node.textContent.replace(/^\s+|\s+$/g, "");
-        this._rules.push({ type: node.nodeName, regexp: new RegExp(urlRegexp) });
-      }
-    }
-
-    // allow the generators to set individual update values (even varying
-    // depending on certain XPath expressions)
-    var update = getFirstChildByTagName("update", generatorNode, MICSUM_NS);
-    if (update) {
-      function _parseInterval(string) {
-        // convert from minute fractions to milliseconds
-        // and ensure a minimum value of 1 minute
-        return Math.round(Math.max(parseFloat(string) || 0, 1) * 60 * 1000);
-      }
-
-      this._unconditionalUpdateInterval =
-        update.hasAttribute("interval") ?
-        _parseInterval(update.getAttribute("interval")) : null;
-      // collect the <condition expression="XPath Expression" interval="time"/> clauses
-      this._updateIntervals = new Array();
-      for (let i = 0; i < update.childNodes.length; i++) {
-        node = update.childNodes[i];
-        if (node.nodeType != node.ELEMENT_NODE || node.namespaceURI != MICSUM_NS ||
-            node.nodeName != "condition")
-          continue;
-        if (!node.getAttribute("expression") || !node.getAttribute("interval")) {
-          LOG("ignoring incomplete conditional update interval for " + this.uri.spec);
-          continue;
-        }
-        this._updateIntervals.push({
-          expression: node.getAttribute("expression"),
-          interval: _parseInterval(node.getAttribute("interval"))
-        });
-      }
-    }
-
-    var templateNode = getFirstChildByTagName("template", generatorNode, MICSUM_NS);
-    if (templateNode) {
-      this._template = getFirstChildByTagName("transform", templateNode, XSLT_NS) ||
-                       getFirstChildByTagName("stylesheet", templateNode, XSLT_NS);
-    }
-    // XXX Make sure the template is a valid XSL transform sheet.
-
-    this._loaded = true;
-  },
-  generateMicrosummary: function MSD_generateMicrosummary(pageContent) {
-    var content;
-    if (this._content)
-      content = this._content;
-    else if (this._template)
-      content = this._processTemplate(pageContent);
-    else
-      throw("generateMicrosummary called on uninitialized microsummary generator");
-
-    // Clean up the output
-    content = content.replace(/^\s+|\s+$/g, "");
-    if (content.length > MAX_SUMMARY_LENGTH) 
-      content = content.substring(0, MAX_SUMMARY_LENGTH);
-
-    return content;
-  },
-
-  calculateUpdateInterval: function MSD_calculateUpdateInterval(doc) {
-    if (this._content || !this._updateIntervals || !doc)
-      return null;
-    for (let i = 0; i < this._updateIntervals.length; i++) {
-      try {
-        if (doc.evaluate(this._updateIntervals[i].expression, doc, null,
-                         Ci.nsIDOMXPathResult.BOOLEAN_TYPE, null).booleanValue)
-          return this._updateIntervals[i].interval;
-      }
-      catch (ex) {
-        Cu.reportError(ex);
-        // remove the offending conditional update interval
-        this._updateIntervals.splice(i--, 1);
-      }
-    }
-
-    return this._unconditionalUpdateInterval;
-  },
-
-  _processTemplate: function MSD__processTemplate(doc) {
-    LOG("processing template " + this._template + " against document " + doc);
-
-    // XXX Should we just have one global instance of the processor?
-    var processor = Cc["@mozilla.org/document-transformer;1?type=xslt"].
-                    createInstance(Ci.nsIXSLTProcessor);
-
-    // Turn off document loading of all kinds (document(), <include>, <import>)
-    // for security (otherwise local generators would be able to load local files).
-    processor.flags |= Ci.nsIXSLTProcessorPrivate.DISABLE_ALL_LOADS;
-
-    processor.importStylesheet(this._template);
-    var fragment = processor.transformToFragment(doc, doc);
-
-    LOG("template processing result: " + fragment.textContent);
-
-    // XXX When we support HTML microsummaries we'll need to do something
-    // more sophisticated than just returning the text content of the fragment.
-    return fragment.textContent;
-  },
-
-  saveXMLToFile: function MSD_saveXMLToFile(xmlDefinition) {
-    var file = this.localURI.QueryInterface(Ci.nsIFileURL).file.clone();
-
-    LOG("saving definition to " + file.path);
-
-    // Write the generator XML to the local file.
-    var outputStream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
-                       createInstance(Ci.nsIFileOutputStream);
-    var localFile = file.QueryInterface(Ci.nsILocalFile);
-    outputStream.init(localFile, (MODE_WRONLY | MODE_TRUNCATE | MODE_CREATE),
-                      PERMS_FILE, 0);
-    var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
-                     createInstance(Ci.nsIDOMSerializer);
-    serializer.serializeToStream(xmlDefinition, outputStream, null);
-    if (outputStream instanceof Ci.nsISafeOutputStream) {
-      try {
-        outputStream.finish();
-      }
-      catch (e) {
-        outputStream.close();
-      }
-    }
-    else
-      outputStream.close();
-  },
-
-  update: function MSD_update() {
-    // Update this generator if it was downloaded from a remote source and has
-    // been modified since we last downloaded it.
-    var genURI = this.uri;
-    if (genURI && /^urn:source:/i.test(genURI.spec)) {
-      let genURL = genURI.spec.replace(/^urn:source:/, "");
-      genURI = NetUtil.newURI(genURL, null, null);
-    }
-
-    // Only continue if we have a valid remote URI
-    if (!genURI || !/^https?/.test(genURI.scheme)) {
-      LOG("generator did not have valid URI; skipping update: " + genURI.spec);
-      return;
-    }
-
-    // We use a HEAD request to check if the generator has been modified since
-    // the last time we downloaded it. If it has, we move to _preformUpdate() to
-    // actually download and save the new generator.
-    var t = this;
-    var loadCallback = function(resource) {
-      if (resource.status != 304)
-        t._performUpdate(genURI);
-      else
-        LOG("generator is already up to date: " + genURI.spec);
-      resource.destroy();
-    };
-    var errorCallback = function(resource) {
-      resource.destroy();
-    };
-
-    var file = this.localURI.QueryInterface(Ci.nsIFileURL).file.clone();
-    var lastmod = new Date(file.lastModifiedTime);
-    LOG("updating generator: " + genURI.spec);
-    var resource = new MicrosummaryResource(genURI);
-    resource.lastMod = lastmod.toUTCString();
-    resource.method = "HEAD";
-    resource.load(loadCallback, errorCallback);
-  },
-
-  _performUpdate: function MSD__performUpdate(uri) {
-    var t = this;
-    var loadCallback = function(resource) {
-      try {
-        t._handleUpdateLoad(resource);
-      }
-      finally {
-        resource.destroy();
-      }
-    };
-    var errorCallback = function(resource) {
-      resource.destroy();
-    };
-
-    var resource = new MicrosummaryResource(uri);
-    resource.load(loadCallback, errorCallback);
-  },
-
-  _handleUpdateLoad: function MSD__handleUpdateLoad(resource) {
-    if (!resource.isXML)
-      throw("update failed, downloaded resource is not XML: " + this.uri.spec);
-
-    // Preserve the generator's ID.
-    // XXX Check for redirects and update the URI if it changes.
-    var generatorID = this.uri.spec;
-    resource.content.documentElement.setAttribute("uri", generatorID);
-
-    // Reinitialize this generator with the newly downloaded XML and save to disk.
-    this.initFromXML(resource.content);
-    this.saveXMLToFile(resource.content);
-
-    // Let observers know we've updated this generator
-    Services.obs.notifyObservers(this, "microsummary-generator-updated", null);
-  }
-};
-
-
-/**
- * Microsummary sets are collections of microsummaries.  They allow callers
- * to register themselves as observers of the set, and when any microsummary
- * in the set changes, the observers get notified.  Thus a caller can observe
- * the set instead of each individual microsummary.
- */
-function MicrosummarySet() {
-  this._observers = [];
-  this._elements = [];
-}
-
-MicrosummarySet.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummarySet,
-                                         Ci.nsIMicrosummaryObserver]),
-  // nsIMicrosummaryObserver
-  onContentLoaded: function MSSet_onContentLoaded(microsummary) {
-    for (let i = 0; i < this._observers.length; i++) {
-      this._observers[i].onContentLoaded(microsummary);
-    }
-  },
-  onError: function MSSet_onError(microsummary) {
-    for (let i = 0; i < this._observers.length; i++) {
-      this._observers[i].onError(microsummary);
-    }
-  },
-  // nsIMicrosummarySet
-  addObserver: function MSSet_addObserver(observer) {
-    if (this._observers.length == 0) {
-      for (let i = 0; i < this._elements.length; i++) {
-        this._elements[i].addObserver(this);
-      }
-    }
-    // Register the observer, but only if it isn't already registered,
-    // so that we don't call the same observer twice for any given change.
-    if (this._observers.indexOf(observer) == -1)
-      this._observers.push(observer);
-  },
-
-  removeObserver: function MSSet_removeObserver(observer) {
-    if (this._observers.indexOf(observer) != -1)
-      this._observers.splice(this._observers.indexOf(observer), 1);
-
-    if (this._observers.length == 0) {
-      for (let i = 0; i < this._elements.length; i++) {
-        this._elements[i].removeObserver(this);
-      }
-    }
-  },
-
-  extractFromPage: function MSSet_extractFromPage(resource) {
-    if (!resource.isXML && resource.contentType != "text/html")
-      throw("page is neither HTML nor XML");
-
-    // XXX Handle XML documents, whose microsummaries are specified
-    // via processing instructions.
-    var links = resource.content.getElementsByTagName("link");
-    for (let i = 0; i < links.length; i++) {
-      var link = links[i];
-      if (!link.hasAttribute("rel"))
-        continue;
-      var relAttr = link.getAttribute("rel");
-
-      // The attribute's value can be a space-separated list of link types,
-      // check to see if "microsummary" is one of them.
-      var linkTypes = relAttr.split(/\s+/);
-      if (!linkTypes.some(function(v) {return v.toLowerCase() == "microsummary";}))
-        continue;
-      // Look for a TITLE attribute to give the generator a nice name in the UI.
-      var linkTitle = link.getAttribute("title");
-
-
-      // Unlike the "href" attribute, the "href" property contains
-      // an absolute URI spec, so we use it here to create the URI.
-      var generatorURI = NetUtil.newURI(link.href, resource.content.characterSet,
-                                        null);
-
-      if (!/^https?$/i.test(generatorURI.scheme)) {
-        LOG("can't load generator " + generatorURI.spec + " from page " +
-            resource.uri.spec);
-        continue;
-      }
-
-      var generator = new MicrosummaryGenerator(generatorURI, null, linkTitle);
-      var microsummary = new Microsummary(resource.uri, generator);
-      if (!this.hasItemForMicrosummary(microsummary))
-        this.AppendElement(microsummary);
-    }
-  },
-
-  /**
-   * Determines whether the given microsummary is already represented in the
-   * set.
-   */
-  hasItemForMicrosummary: function MSSet_hasItemForMicrosummary(aMicrosummary) {
-    for (let i = 0; i < this._elements.length; i++) {
-      if (this._elements[i].equals(aMicrosummary))
-        return true;
-    }
-    return false;
-  },
-
-  // XXX Turn this into a complete implementation of nsICollection?
-  AppendElement: function MSSet_AppendElement(element) {
-    // Query the element to a microsummary.
-    // XXX Should we NS_ASSERT if this fails?
-    element = element.QueryInterface(Ci.nsIMicrosummary);
-
-    if (this._elements.indexOf(element) == -1) {
-      this._elements.push(element);
-      element.addObserver(this);
-    }
-    // Notify observers that an element has been appended.
-    for (let i = 0; i < this._observers.length; i++) {
-      this._observers[i].onElementAppended(element);
-    }
-  },
-  Enumerate: function MSSet_Enumerate() {
-    return new ArrayEnumerator(this._elements);
-  }
-};
-
-
-
-
-
-/**
- * An enumeration of items in a JS array.
- * @constructor
- */
-function ArrayEnumerator(aItems) {
-  if (aItems) {
-    for (let i = 0; i < aItems.length; ++i) {
-      if (!aItems[i])
-        aItems.splice(i--, 1);
-    }
-    this._contents = aItems;
-  } else {
-    this._contents = [];
-  }
-}
-
-ArrayEnumerator.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
-
-  _index: 0,
-
-  hasMoreElements: function() {
-    return this._index < this._contents.length;
-  },
-
-  getNext: function() {
-    return this._contents[this._index++];      
-  }
-};
-
-
-
-
-
-/**
- * Outputs aText to the JavaScript console as well as to stdout if the
- * microsummary logging pref (browser.microsummary.log) is set to true.
- * 
- * @param aText
- *        the text to log
- */
-function LOG(aText) {
-  var f = arguments.callee;
-  if (!("_enabled" in f))
-    f._enabled = getPref("browser.microsummary.log", false);
-  if (f._enabled) {
-    dump("*** Microsummaries: " +  aText + "\n");
-    var consoleService = Cc["@mozilla.org/consoleservice;1"].
-                         getService(Ci.nsIConsoleService);
-    consoleService.logStringMessage(aText);
-  }
-}
-
-
-
-
-
-/**
- * A resource (page, microsummary, generator, etc.) identifiable by URI.
- * This object abstracts away much of the code for loading resources
- * and parsing their content if they are XML or HTML.
- * 
- * @constructor
- * 
- * @param   uri
- *          the location of the resource
- */
-function MicrosummaryResource(uri) {
-  // Make sure we're not loading javascript: or data: URLs, which could
-  // take advantage of the load to run code with chrome: privileges.
-  // XXX Perhaps use nsIScriptSecurityManager.checkLoadURI instead.
-  if (!(uri.schemeIs("http") || uri.schemeIs("https") || uri.schemeIs("file")))
-    throw NS_ERROR_DOM_BAD_URI;
-
-  this._uri = uri;
-  this._content = null;
-  this._contentType = null;
-  this._isXML = false;
-  this.__authFailed = false;
-  this._status = null;
-  this._method = "GET";
-  this._lastMod = null;
-
-  // A function to call when we finish loading/parsing the resource.
-  this._loadCallback = null;
-  // A function to call if we get an error while loading/parsing the resource.
-  this._errorCallback = null;
-  // A hidden iframe to parse HTML content.
-  this._iframe = null;
-}
-MicrosummaryResource.prototype = {
-  get uri() this._uri,
-  get content() this._content,
-  get contentType() this._contentType,
-  get isXML() this._isXML,
-  get status() this._status,
-  set status(aStatus) this._status = aStatus,
-  get method() this._method,
-  set method(aMethod) this._method = aMethod,
-  get lastMod() this._lastMod,
-  set lastMod(aMod) this._lastMod = aMod,
-  // Implement notification callback interfaces so we can suppress UI
-  // and abort loads for bad SSL certs and HTTP authorization requests.
-  
-  // Interfaces this component implements.
-  interfaces: [Ci.nsIAuthPromptProvider,
-               Ci.nsIAuthPrompt,
-               Ci.nsIBadCertListener2,
-               Ci.nsISSLErrorListener,
-               Ci.nsIPrompt,
-               Ci.nsIProgressEventSink,
-               Ci.nsIInterfaceRequestor,
-               Ci.nsISupports],
-  // nsISupports
-  QueryInterface: function MSR_QueryInterface(iid) {
-    if (!this.interfaces.some(function(v) {return iid.equals(v);}))
-      throw Cr.NS_ERROR_NO_INTERFACE;
-    // nsIAuthPrompt and nsIPrompt need separate implementations because
-    // their method signatures conflict.  The other interfaces we implement
-    // within MicrosummaryResource itself.
-    switch (iid) {
-    case Ci.nsIAuthPrompt:
-      return this.authPrompt;
-    case Ci.nsIPrompt:
-      return this.prompt;
-    default:
-      return this;
-    }
-  },
-
-  // nsIInterfaceRequestor
-  getInterface: function MSR_getInterface(iid) {
-    return this.QueryInterface(iid);
-  },
-
-  // nsIBadCertListener2
-  // Suppress any certificate errors
-  notifyCertProblem: function MSR_certProblem(socketInfo, status, targetSite) {
-    return true;
-  },
-
-  // nsISSLErrorListener
-  // Suppress any ssl errors
-  notifySSLError: function MSR_SSLError(socketInfo, error, targetSite) {
-    return true;
-  },
-
-  
-  // Suppress UI and abort loads for files secured by authentication.
-
-  // Auth requests appear to succeed when we cancel them (since the server
-  // redirects us to a "you're not authorized" page), so we have to set a flag
-  // to let the load handler know to treat the load as a failure.
-  get _authFailed() this.__authFailed,
-  set _authFailed(newValue) this.__authFailed = newValue,
-
-  // nsIAuthPromptProvider
-  getAuthPrompt: function(aPromptReason, aIID) {
-    this._authFailed = true;
-    throw Cr.NS_ERROR_NOT_AVAILABLE;
-  },
-
-  // HTTP always requests nsIAuthPromptProvider first, so it never needs
-  // nsIAuthPrompt, but not all channels use nsIAuthPromptProvider, so we
-  // implement nsIAuthPrompt too.
-  // nsIAuthPrompt
-  get authPrompt() {
-    var resource = this;
-    return {
-      QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
-      prompt: function(dialogTitle, text, passwordRealm, savePassword, defaultText, result) {
-        resource._authFailed = true;
-        return false;
-      },
-      promptUsernameAndPassword: function(dialogTitle, text, passwordRealm, savePassword, user, pwd) {
-        resource._authFailed = true;
-        return false;
-      },
-      promptPassword: function(dialogTitle, text, passwordRealm, savePassword, pwd) {
-        resource._authFailed = true;
-        return false;
-      }
-    };
-  },
-  // nsIPrompt
-  get prompt() {
-    var resource = this;
-    return {
-      QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
-      alert: function(dialogTitle, text) {
-        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
-      },
-      alertCheck: function(dialogTitle, text, checkMessage, checkValue) {
-        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
-      },
-      confirm: function(dialogTitle, text) {
-        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
-      },
-      confirmCheck: function(dialogTitle, text, checkMessage, checkValue) {
-        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
-      },
-      confirmEx: function(dialogTitle, text, buttonFlags, button0Title, button1Title, button2Title, checkMsg, checkValue) {
-        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
-      },
-      prompt: function(dialogTitle, text, value, checkMsg, checkValue) {
-        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
-      },
-      promptPassword: function(dialogTitle, text, password, checkMsg, checkValue) {
-        resource._authFailed = true;
-        return false;
-      },
-      promptUsernameAndPassword: function(dialogTitle, text, username, password, checkMsg, checkValue) {
-        resource._authFailed = true;
-        return false;
-      },
-      select: function(dialogTitle, text, count, selectList, outSelection) {
-        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
-      }
-    };
-  },
-
-  // XXX We implement nsIProgressEventSink because otherwise bug 253127
-  // would cause too many extraneous errors to get reported to the console.
-  // Fortunately this doesn't screw up XMLHttpRequest, because it ensures
-  // that its implementation of nsIProgressEventSink will always get called
-  // in addition to whatever notification callbacks we set on the channel.
-  // nsIProgressEventSink
-  onProgress: function(aRequest, aContext, aProgress, aProgressMax) {},
-  onStatus: function(aRequest, aContext, aStatus, aStatusArg) {},
-  /**
-   * Initialize the resource from an existing DOM document object.
-   * 
-   * @param   document
-   *          a DOM document object
-   */
-  initFromDocument: function MSR_initFromDocument(document) {
-    this._content = document;
-    this._contentType = document.contentType;
-
-    // Normally we set this property based on whether or not
-    // XMLHttpRequest parsed the content into an XML document object,
-    // but since we already have the content, we have to analyze
-    // its content type ourselves to see if it is XML.
-    this._isXML = (this.contentType == "text/xml" ||
-                   this.contentType == "application/xml" ||
-                   /^.+\/.+\+xml$/.test(this.contentType));
-  },
-
-  /**
-   * Destroy references to avoid leak-causing cycles.  Instantiators must call
-   * this method on all instances they instantiate once they're done with them.
-   */
-  destroy: function MSR_destroy() {
-    this._uri = null;
-    this._content = null;
-    this._loadCallback = null;
-    this._errorCallback = null;
-    this._loadTimer = null;
-    this._authFailed = false;
-    if (this._iframe) {
-      if (this._iframe && this._iframe.parentNode)
-        this._iframe.parentNode.removeChild(this._iframe);
-      this._iframe = null;
-    }
-  },
-
-  /**
-   * Load the resource.
-   * 
-   * @param   loadCallback
-   *          a function to invoke when the resource finishes loading
-   * @param   errorCallback
-   *          a function to invoke when an error occurs during the load
-   */
-  load: function MSR_load(loadCallback, errorCallback) {
-    LOG(this.uri.spec + " loading");
-  
-    this._loadCallback = loadCallback;
-    this._errorCallback = errorCallback;
-
-    var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
-  
-    var loadHandler = {
-      _self: this,
-      handleEvent: function MSR_loadHandler_handleEvent(event) {
-        if (this._self._loadTimer)
-          this._self._loadTimer.cancel();
-
-        this._self.status = event.target.status;
-
-        if (this._self._authFailed || this._self.status >= 400) {
-          // Technically the request succeeded, but we treat it as a failure,
-          // since we won't be able to extract anything relevant from the result.
-
-          // XXX For now HTTP is the only protocol we handle that might fail
-          // auth. This message will need to change once we support FTP, which
-          // returns 0 for all statuses.
-          LOG(this._self.uri.spec + " load failed; HTTP status: " + this._self.status);
-          try {
-            this._self._handleError(event);
-          }
-          finally {
-            this._self = null;
-          }
-        }
-        else if (event.target.channel.contentType == "multipart/x-mixed-replace") {
-          // Technically the request succeeded, but we treat it as a failure,
-          // since we aren't able to handle multipart content.
-          LOG(this._self.uri.spec + " load failed; contains multipart content");
-          try {
-            this._self._handleError(event);
-          }
-          finally {
-            this._self = null;
-          }
-        }
-        else {
-          LOG(this._self.uri.spec + " load succeeded; invoking callback");
-          try {
-            this._self._handleLoad(event);
-          }
-          finally {
-            this._self = null;
-          }
-        }
-      }
-    };
-
-    var errorHandler = {
-      _self: this,
-      handleEvent: function MSR_errorHandler_handleEvent(event) {
-        if (this._self._loadTimer)
-          this._self._loadTimer.cancel();
-        LOG(this._self.uri.spec + " load failed");
-        try {
-          this._self._handleError(event);
-        }
-        finally {
-          this._self = null;
-        }
-      }
-    };
-    // cancel loads that take too long
-    // timeout specified in seconds at browser.microsummary.requestTimeout,
-    // or 300 seconds (five minutes)
-    var timeout = getPref("browser.microsummary.requestTimeout", 300) * 1000;
-    var timerObserver = {
-      _self: this,
-      observe: function MSR_timerObserver_observe() {
-        LOG("timeout loading microsummary resource " + this._self.uri.spec + ", aborting request");
-        request.abort();
-        try {
-          this._self.destroy();
-        }
-        finally {
-          this._self = null;
-        }
-      }
-    };
-    this._loadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-    this._loadTimer.init(timerObserver, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
-
-    request = request.QueryInterface(Ci.nsIDOMEventTarget);
-    request.addEventListener("load", loadHandler, false);
-    request.addEventListener("error", errorHandler, false);
-    
-    request = request.QueryInterface(Ci.nsIXMLHttpRequest);
-    request.open(this.method, this.uri.spec, true);
-    request.setRequestHeader("X-Moz", "microsummary");
-    if (this.lastMod)
-      request.setRequestHeader("If-Modified-Since", this.lastMod);
-
-    // Register ourselves as a listener for notification callbacks so we
-    // can handle authorization requests and SSL issues like cert mismatches.
-    // XMLHttpRequest will handle the notifications we don't handle.
-    request.channel.notificationCallbacks = this;
-
-    // If this is a bookmarked resource, and the bookmarks service recorded
-    // its charset in the bookmarks datastore the last time the user visited it,
-    // then specify the charset in the channel so XMLHttpRequest loads
-    // the resource correctly.
-    try {
-      var resolver = Cc["@mozilla.org/embeddor.implemented/bookmark-charset-resolver;1"].
-                     getService(Ci.nsICharsetResolver);
-      if (resolver) {
-        var charset = resolver.requestCharset(null, request.channel, {}, {});
-        if (charset != "")
-          request.channel.contentCharset = charset;
-      }
-    }
-    catch(ex) {}
-
-    request.send(null);
-  },
-
-  _handleLoad: function MSR__handleLoad(event) {
-    var request = event.target;
-
-    if (request.responseXML) {
-      this._isXML = true;
-      // XXX Figure out the parsererror format and log a specific error.
-      if (request.responseXML.documentElement.nodeName == "parsererror") {
-        this._handleError(event);
-        return;
-      }
-      this._content = request.responseXML;
-      this._contentType = request.channel.contentType;
-      this._loadCallback(this);
-    }
-
-    else if (request.channel.contentType == "text/html") {
-      this._parse(request.responseText);
-    }
-
-    else {
-      // This catches text/plain as well as any other content types
-      // not accounted for by the content type-specific code above.
-      this._content = request.responseText;
-      this._contentType = request.channel.contentType;
-      this._loadCallback(this);
-    }
-  },
-
-  _handleError: function MSR__handleError(event) {
-    // Call the error callback, then destroy ourselves to prevent memory leaks.
-    try {
-      if (this._errorCallback)
-        this._errorCallback(this);
-    }
-    finally {
-      this.destroy();
-    }
-  },
-
-  /**
-   * Parse a string of HTML text.  Used by _load() when it retrieves HTML.
-   * We do this via hidden XUL iframes, which according to bz is the best way
-   * to do it currently, since bug 102699 is hard to fix.
-   * 
-   * @param   htmlText
-   *          a string containing the HTML content
-   */
-  _parse: function MSR__parse(htmlText) {
-    // Find a window to stick our hidden iframe into.
-    var window = Services.wm.getMostRecentWindow("navigator:browser");
-    // XXX We can use other windows, too, so perhaps we should try to get
-    // some other window if there's no browser window open.  Perhaps we should
-    // even prefer other windows, since there's less chance of any browser
-    // window machinery like throbbers treating our load like one initiated
-    // by the user.
-    if (!window) {
-      this._handleError(event);
-      return;
-    }
-    var document = window.document;
-    var rootElement = document.documentElement;
-  
-    // Create an iframe, make it hidden, and secure it against untrusted content.
-    this._iframe = document.createElement('iframe');
-    this._iframe.setAttribute("collapsed", true);
-    this._iframe.setAttribute("type", "content");
-  
-    // Insert the iframe into the window, creating the doc shell.
-    rootElement.appendChild(this._iframe);
-
-    // When we insert the iframe into the window, it immediately starts loading
-    // about:blank, which we don't need and could even hurt us (for example
-    // by triggering bugs like bug 344305), so cancel that load.
-    var webNav = this._iframe.docShell.QueryInterface(Ci.nsIWebNavigation);
-    webNav.stop(Ci.nsIWebNavigation.STOP_NETWORK);
-
-    // Turn off JavaScript and auth dialogs for security and other things
-    // to reduce network load.
-    // XXX We should also turn off CSS.
-    this._iframe.docShell.allowJavascript = false;
-    this._iframe.docShell.allowAuth = false;
-    this._iframe.docShell.allowPlugins = false;
-    this._iframe.docShell.allowMetaRedirects = false;
-    this._iframe.docShell.allowSubframes = false;
-    this._iframe.docShell.allowImages = false;
-    this._iframe.docShell.allowDNSPrefetch = false;
-  
-    var parseHandler = {
-      _self: this,
-      handleEvent: function MSR_parseHandler_handleEvent(event) {
-        event.target.removeEventListener("DOMContentLoaded", this, false);
-        try {
-          this._self._handleParse(event);
-        }
-        finally {
-          this._self = null;
-        }
-      }
-    };
- 
-    // Convert the HTML text into an input stream.
-    var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
-                    createInstance(Ci.nsIScriptableUnicodeConverter);
-    converter.charset = "UTF-8";
-    var stream = converter.convertToInputStream(htmlText);
-
-    // Set up a channel to load the input stream.
-    var channel = Cc["@mozilla.org/network/input-stream-channel;1"].
-                  createInstance(Ci.nsIInputStreamChannel);
-    channel.setURI(this._uri);
-    channel.contentStream = stream;
-
-    // Load in the background so we don't trigger web progress listeners.
-    var request = channel.QueryInterface(Ci.nsIRequest);
-    request.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND;
-
-    // Specify the content type since we're not loading content from a server,
-    // so it won't get specified for us, and if we don't specify it ourselves,
-    // then Firefox will prompt the user to download content of "unknown type".
-    var baseChannel = channel.QueryInterface(Ci.nsIChannel);
-    baseChannel.contentType = "text/html";
-
-    // Load as UTF-8, which it'll always be, because XMLHttpRequest converts
-    // the text (i.e. XMLHTTPRequest.responseText) from its original charset
-    // to UTF-16, then the string input stream component converts it to UTF-8.
-    baseChannel.contentCharset = "UTF-8";
-
-    // Register the parse handler as a load event listener and start the load.
-    // Listen for "DOMContentLoaded" instead of "load" because background loads
-    // don't fire "load" events.
-    this._iframe.addEventListener("DOMContentLoaded", parseHandler, true);
-    var uriLoader = Cc["@mozilla.org/uriloader;1"].getService(Ci.nsIURILoader);
-    uriLoader.openURI(channel, true, this._iframe.docShell);
-  },
-
-  /**
-   * Handle a load event for the iframe-based parser.
-   * 
-   * @param   event
-   *          the event object representing the load event
-   */
-  _handleParse: function MSR__handleParse(event) {
-    // XXX Make sure the parse was successful?
-
-    this._content = this._iframe.contentDocument;
-    this._contentType = this._iframe.contentDocument.contentType;
-    this._loadCallback(this);
-  }
-
-};
-
-/**
- * Get a resource currently loaded into a browser window.  Checks windows
- * one at a time, starting with the frontmost (a.k.a. most recent) one.
- *
- * @param   uri
- *          the URI of the resource
- *
- * @returns a Resource object, if the resource is currently loaded
- *          into a browser window; otherwise null
- */
-function getLoadedMicrosummaryResource(uri) {
-
-  // Apparently the Z order enumerator is broken on Linux per bug 156333.
-  //var windows = mediator.getZOrderDOMWindowEnumerator("navigator:browser", true);
-  var windows = Services.wm.getEnumerator("navigator:browser");
-  while (windows.hasMoreElements()) {
-    var win = windows.getNext();
-    if (win.closed)
-      continue;
-    var tabBrowser = win.document.getElementById("content");
-    for (let i = 0; i < tabBrowser.browsers.length; i++) {
-      var browser = tabBrowser.browsers[i];
-      if (uri.equals(browser.currentURI)) {
-        var resource = new MicrosummaryResource(uri);
-        resource.initFromDocument(browser.contentDocument);
-        return resource;
-      }
-    }
-  }
-
-  return null;
-}
-
-/**
- * Get a value from a pref or a default value if the pref doesn't exist.
- *
- * @param   prefName
- * @param   defaultValue
- * @returns the pref's value or the default (if it is missing)
- */
-function getPref(prefName, defaultValue) {
-  try {
-    var type = Services.prefs.getPrefType(prefName);
-    switch (type) {
-      case Services.prefs.PREF_BOOL:
-        return Services.prefs.getBoolPref(prefName);
-      case Services.prefs.PREF_INT:
-        return Services.prefs.getIntPref(prefName);
-    }
-  }
-  catch (ex) {}
-
-  return defaultValue;
-}
-
-
-/**
- * Generates a sanitized name to be used as a filename, or a random name if a
- * sanitized name cannot be obtained (if aName contains no valid characters).
- */
-function sanitizeName(aName) {
-  var name = aName.toLowerCase();
-  name = name.replace(/\s+/g, "-");
-  name = name.replace(/[^-a-z0-9]/g, "");
-
-  // If our input had not enough valid characters, use a random name.
-  if (name.length < MIN_GENERATOR_NAME_LENGTH)
-    name = Math.random().toString(36).replace(/^.*\./, '');
-
-  // Force max length.
-  return name.substring(0, MAX_GENERATOR_NAME_LENGTH);
-}
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([MicrosummaryService]);