home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / lib / firefox / components / nsMicrosummaryService.js < prev    next >
Encoding:
Text File  |  2006-08-18  |  58.3 KB  |  1,740 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Microsummarizer.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla.
  17.  * Portions created by the Initial Developer are Copyright (C) 2006
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Myk Melez <myk@mozilla.org> (Original Author)
  22.  *
  23.  * Alternatively, the contents of this file may be used under the terms of
  24.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  25.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  26.  * in which case the provisions of the GPL or the LGPL are applicable instead
  27.  * of those above. If you wish to allow use of your version of this file only
  28.  * under the terms of either the GPL or the LGPL, and not to allow others to
  29.  * use your version of this file under the terms of the MPL, indicate your
  30.  * decision by deleting the provisions above and replace them with the notice
  31.  * and other provisions required by the GPL or the LGPL. If you do not delete
  32.  * the provisions above, a recipient may use your version of this file under
  33.  * the terms of any one of the MPL, the GPL or the LGPL.
  34.  *
  35.  * ***** END LICENSE BLOCK ***** */
  36.  
  37. const Cc = Components.classes;
  38. const Ci = Components.interfaces;
  39.  
  40. const PERMS_FILE    = 0644;
  41. const MODE_WRONLY   = 0x02;
  42. const MODE_TRUNCATE = 0x20;
  43.  
  44. const NS_ERROR_MODULE_DOM = 2152923136;
  45. const NS_ERROR_DOM_BAD_URI = NS_ERROR_MODULE_DOM + 1012;
  46.  
  47. // How often to update microsummaries, in milliseconds.
  48. // XXX Make this a hidden pref so power users can modify it.
  49. const UPDATE_INTERVAL = 30 * 60 * 1000; // 30 minutes
  50.  
  51. // How often to check for microsummaries that need updating, in milliseconds.
  52. const CHECK_INTERVAL = 15 * 1000; // 15 seconds
  53.  
  54. const MICSUM_NS = new Namespace("http://www.mozilla.org/microsummaries/0.1");
  55. const XSLT_NS = new Namespace("http://www.w3.org/1999/XSL/Transform");
  56.  
  57. //@line 63 "/build/buildd/firefox-1.99+2.0b1+dfsg/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  58. const NC_NS                   = "http://home.netscape.com/NC-rdf#";
  59. const RDF_NS                  = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
  60. const FIELD_RDF_TYPE          = RDF_NS + "type";
  61. const VALUE_MICSUM_BOOKMARK   = NC_NS + "MicsumBookmark";
  62. const VALUE_NORMAL_BOOKMARK   = NC_NS + "Bookmark";
  63. const FIELD_MICSUM_GEN_URI    = NC_NS + "MicsumGenURI";
  64. const FIELD_MICSUM_EXPIRATION = NC_NS + "MicsumExpiration";
  65. const FIELD_GENERATED_TITLE   = NC_NS + "GeneratedTitle";
  66. const FIELD_CONTENT_TYPE      = NC_NS + "ContentType";
  67. const FIELD_BOOKMARK_URL      = NC_NS + "URL";
  68. //@line 74 "/build/buildd/firefox-1.99+2.0b1+dfsg/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  69.  
  70. function MicrosummaryService() {}
  71.  
  72. MicrosummaryService.prototype = {
  73.  
  74. //@line 98 "/build/buildd/firefox-1.99+2.0b1+dfsg/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  75.   // RDF Service
  76.   __rdf: null,
  77.   get _rdf() {
  78.     if (!this.__rdf)
  79.       this.__rdf = Cc["@mozilla.org/rdf/rdf-service;1"].
  80.                    getService(Ci.nsIRDFService);
  81.     return this.__rdf;
  82.   },
  83.  
  84.   // Bookmarks Data Source
  85.   __bmds: null,
  86.   get _bmds() {
  87.     if (!this.__bmds)
  88.       this.__bmds = this._rdf.GetDataSource("rdf:bookmarks");
  89.     return this.__bmds;
  90.   },
  91.  
  92.   // Old Bookmarks Service
  93.   __bms: null,
  94.   get _bms() {
  95.     if (!this.__bms)
  96.       this.__bms = this._bmds.QueryInterface(Ci.nsIBookmarksService);
  97.     return this.__bms;
  98.   },
  99. //@line 123 "/build/buildd/firefox-1.99+2.0b1+dfsg/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  100.  
  101.   // IO Service
  102.   __ios: null,
  103.   get _ios() {
  104.     if (!this.__ios)
  105.       this.__ios = Cc["@mozilla.org/network/io-service;1"].
  106.                    getService(Ci.nsIIOService);
  107.     return this.__ios;
  108.   },
  109.  
  110.   // Observer Service
  111.   __obs: null,
  112.   get _obs() {
  113.     if (!this.__obs)
  114.       this.__obs = Cc["@mozilla.org/observer-service;1"].
  115.                    getService(Ci.nsIObserverService);
  116.     return this.__obs;
  117.   },
  118.  
  119.   /**
  120.    * Make a URI from a spec.
  121.    * @param   spec
  122.    *          The string spec of the URI.
  123.    * @returns An nsIURI object.
  124.    */
  125.   _uri: function MSS__uri(spec) {
  126.     return this._ios.newURI(spec, null, null);
  127.   },
  128.  
  129. //@line 153 "/build/buildd/firefox-1.99+2.0b1+dfsg/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  130.   /**
  131.    * Make an RDF resource from a URI spec.
  132.    * @param   uriSpec
  133.    *          The URI spec to convert into a resource.
  134.    * @returns An nsIRDFResource object.
  135.    */
  136.   _resource: function MSS__resource(uriSpec) {
  137.     return this._rdf.GetResource(uriSpec);
  138.   },
  139.  
  140.   /**
  141.    * Make an RDF literal from a string.
  142.    * @param   str
  143.    *          The string from which to construct the literal.
  144.    * @returns An nsIRDFLiteral object
  145.    */
  146.   _literal: function MSS__literal(str) {
  147.     return this._rdf.GetLiteral(str);
  148.   },
  149. //@line 173 "/build/buildd/firefox-1.99+2.0b1+dfsg/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  150.  
  151.   // Directory Locator
  152.   __dirs: null,
  153.   get _dirs() {
  154.     if (!this.__dirs)
  155.       this.__dirs = Cc["@mozilla.org/file/directory_service;1"].
  156.                    getService(Ci.nsIProperties);
  157.     return this.__dirs;
  158.   },
  159.  
  160.   // A cache of local microsummary generators.  This gets built on startup
  161.   // by the _cacheLocalGenerators() method.
  162.   _localGenerators: {},
  163.  
  164.   // The timer that periodically checks for microsummaries needing updating.
  165.   _timer: null,
  166.  
  167.   // Interfaces this component implements.
  168.   interfaces: [Ci.nsIMicrosummaryService, Ci.nsIObserver, Ci.nsISupports],
  169.  
  170.   // nsISupports
  171.  
  172.   QueryInterface: function MSS_QueryInterface(iid) {
  173.     //if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
  174.     if (!iid.equals(Ci.nsIMicrosummaryService) &&
  175.         !iid.equals(Ci.nsIObserver) &&
  176.         !iid.equals(Ci.nsISupportsWeakReference) &&
  177.         !iid.equals(Ci.nsISupports))
  178.       throw Components.results.NS_ERROR_NO_INTERFACE;
  179.     return this;
  180.   },
  181.  
  182.   // nsIObserver
  183.  
  184.   observe: function MSS_observe(subject, topic, data) {
  185.     switch (topic) {
  186.       case "xpcom-shutdown":
  187.         this._destroy();
  188.         break;
  189.     }
  190.   },
  191.  
  192.   _init: function MSS__init() {
  193.     this._obs.addObserver(this, "xpcom-shutdown", true);
  194.  
  195.     // Periodically update microsummaries that need updating.
  196.     this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  197.     var callback = {
  198.       _svc: this,
  199.       notify: function(timer) { this._svc._updateMicrosummaries() }
  200.     };
  201.     this._timer.initWithCallback(callback,
  202.                                  CHECK_INTERVAL,
  203.                                  this._timer.TYPE_REPEATING_SLACK);
  204.  
  205.     this._cacheLocalGenerators();
  206.   },
  207.   
  208.   _destroy: function MSS__destroy() {
  209.     this._timer.cancel();
  210.     this._timer = null;
  211.   },
  212.  
  213.   _updateMicrosummaries: function MSS__updateMicrosummaries() {
  214. //@line 247 "/build/buildd/firefox-1.99+2.0b1+dfsg/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  215.     var bookmarks = [];
  216.  
  217.     var resources = this._bmds.GetSources(this._resource(FIELD_RDF_TYPE),
  218.                                           this._resource(VALUE_MICSUM_BOOKMARK),
  219.                                           true);
  220.     while (resources.hasMoreElements())
  221.       bookmarks.push(resources.getNext().QueryInterface(Ci.nsIRDFResource));
  222. //@line 255 "/build/buildd/firefox-1.99+2.0b1+dfsg/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  223.  
  224.     var now = new Date().getTime();
  225.  
  226.     for ( var i = 0; i < bookmarks.length; i++ ) {
  227.       var bookmarkID = bookmarks[i];
  228.  
  229.       // Skip this page if its microsummary hasn't expired yet.
  230.       if (this._hasField(bookmarkID, FIELD_MICSUM_EXPIRATION) &&
  231.           this._getField(bookmarkID, FIELD_MICSUM_EXPIRATION) > now)
  232.         continue;
  233.  
  234.       // Reset the expiration time immediately, so if the refresh is failing
  235.       // we don't try it every 15 seconds, potentially overloading the server.
  236.       var now = new Date().getTime();
  237.       this._setField(bookmarkID, FIELD_MICSUM_EXPIRATION, now + UPDATE_INTERVAL);
  238.  
  239.       this.refreshMicrosummary(bookmarkID);
  240.     }
  241.   },
  242.   
  243.   _updateMicrosummary: function MSS__updateMicrosummary(bookmarkID, microsummary) {
  244.     this._setField(bookmarkID, FIELD_GENERATED_TITLE, microsummary.content);
  245.  
  246.     var now = new Date().getTime();
  247.     this._setField(bookmarkID, FIELD_MICSUM_EXPIRATION, now + UPDATE_INTERVAL);
  248.  
  249.     LOG("updated microsummary for page " + microsummary.pageURI.spec +
  250.         " to " + microsummary.content);
  251.   },
  252.  
  253.   /**
  254.    * Load local generators into the cache.
  255.    * 
  256.    */
  257.   _cacheLocalGenerators: function MSS__cacheLocalGenerators() {
  258.     // Load generators from the application directory.
  259.     var appDir = this._dirs.get("MicsumGens", Ci.nsIFile);
  260.     if (appDir.exists())
  261.       this._cacheLocalGeneratorDir(appDir);
  262.  
  263.     // Load generators from the user's profile.
  264.     var profileDir = this._dirs.get("UsrMicsumGens", Ci.nsIFile);
  265.     if (profileDir.exists())
  266.       this._cacheLocalGeneratorDir(profileDir);
  267.   },
  268.  
  269.   /**
  270.    * Load local generators from a directory into the cache.
  271.    *
  272.    * @param   dir
  273.    *          nsIFile object pointing to directory containing generator files
  274.    * 
  275.    */
  276.   _cacheLocalGeneratorDir: function MSS__cacheLocalGeneratorDir(dir) {
  277.     var files = dir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
  278.     var file = files.nextFile;
  279.  
  280.     while (file) {
  281.       // Recursively load generators so support packs containing
  282.       // lots of generators can organize them into multiple directories.
  283.       if (file.isDirectory())
  284.         this._cacheLocalGeneratorDir(file);
  285.       else
  286.         this._cacheLocalGeneratorFile(file);
  287.  
  288.       file = files.nextFile;
  289.     }
  290.     files.close();
  291.   },
  292.  
  293.   /**
  294.    * Load a local generator from a file into the cache.
  295.    * 
  296.    * @param   file
  297.    *          nsIFile object pointing to file from which to load generator
  298.    * 
  299.    */
  300.   _cacheLocalGeneratorFile: function MSS__cacheLocalGeneratorFile(file) {
  301.     var uri = this._ios.newFileURI(file);
  302.  
  303.     var t = this;
  304.     var callback =
  305.       function MSS_cacheLocalGeneratorCallback(resource) {
  306.         try     { t._handleLocalGenerator(resource) }
  307.         finally { resource.destroy() }
  308.       };
  309.  
  310.     var resource = new MicrosummaryResource(uri);
  311.     resource.load(callback);
  312.   },
  313.  
  314.   _handleLocalGenerator: function MSS__handleLocalGenerator(resource) {
  315.     if (!resource.isXML)
  316.       throw(resource.uri.spec + " microsummary generator loaded, but not XML");
  317.   
  318.     var generator = new MicrosummaryGenerator();
  319.     generator.localURI = resource.uri;
  320.     generator.initFromXML(resource.content);
  321.  
  322.     // Add the generator to the local generators cache.
  323.     // XXX Figure out why Firefox crashes on shutdown if we index generators
  324.     // by uri.spec but doesn't crash if we index by uri.spec.split().join().
  325.     //this._localGenerators[generator.uri.spec] = generator;
  326.     this._localGenerators[generator.uri.spec.split().join()] = generator;
  327.  
  328.     LOG("loaded local microsummary generator\n" +
  329.         "   local URI: " + generator.localURI.spec + "\n" +
  330.         "  source URI: " + generator.uri.spec);
  331.   },
  332.  
  333.   // nsIMicrosummaryService
  334.  
  335.   /**
  336.    * Install the microsummary generator from the resource at the supplied URI.
  337.    * Callable by content via the addMicrosummaryGenerator() sidebar method.
  338.    *
  339.    * @param   generatorURI
  340.    *          the URI of the resource providing the generator
  341.    *
  342.    */
  343.   addGenerator: function MSS_addGenerator(generatorURI) {
  344.     var t = this;
  345.     var callback =
  346.       function MSS_addGeneratorCallback(resource) {
  347.         try     { t._handleNewGenerator(resource) }
  348.         finally { resource.destroy() }
  349.       };
  350.  
  351.     var resource = new MicrosummaryResource(generatorURI);
  352.     resource.load(callback);
  353.   },
  354.  
  355.   _handleNewGenerator: function MSS__handleNewGenerator(resource) {
  356.     if (!resource.isXML)
  357.       throw(resource.uri.spec + " microsummary generator loaded, but not XML");
  358.  
  359.     // XXX Make sure it's a valid microsummary generator.
  360.  
  361.     var rootNode = resource.content.documentElement;
  362.  
  363.     // Add a reference to the URI from which we got this generator so we have
  364.     // a unique identifier for the generator and also so we can check back later
  365.     // for updates.
  366.     rootNode.setAttribute("sourceURI", resource.uri.spec);
  367.  
  368.     var generatorName = rootNode.getAttribute("name");
  369.     var fileName = sanitizeName(generatorName) + ".xml";
  370.     var file = this._dirs.get("UsrMicsumGens", Ci.nsIFile);
  371.     file.append(fileName);
  372.     file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
  373.  
  374.     var outputStream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
  375.                        createInstance(Ci.nsIFileOutputStream);
  376.     var localFile = file.QueryInterface(Ci.nsILocalFile);
  377.     outputStream.init(localFile, (MODE_WRONLY | MODE_TRUNCATE), PERMS_FILE, 0);
  378.     var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
  379.                      createInstance(Ci.nsIDOMSerializer);
  380.     serializer.serializeToStream(resource.content, outputStream, null);
  381.     if (outputStream instanceof Ci.nsISafeOutputStream) {
  382.       try       { outputStream.finish() }
  383.       catch (e) { outputStream.close()  }
  384.     }
  385.     else
  386.       outputStream.close();
  387.  
  388.     // Finally, cache the generator in the local generators cache.
  389.     this._cacheLocalGeneratorFile(file);
  390.   },
  391.  
  392.   /**
  393.    * Get the set of microsummaries available for a given page.  The set
  394.    * might change after this method returns, since this method will trigger
  395.    * an asynchronous load of the page in question (if it isn't already loaded)
  396.    * to see if it references any page-specific microsummaries.
  397.    *
  398.    * If the caller passes a bookmark ID, and one of the microsummaries
  399.    * is the current one for the bookmark, this method will retrieve content
  400.    * from the datastore for that microsummary, which is useful when callers
  401.    * want to display a list of microsummaries for a page that isn't loaded,
  402.    * and they want to display the actual content of the selected microsummary
  403.    * immediately (rather than after the content is asynchronously loaded).
  404.    *
  405.    * @param   pageURI
  406.    *          the URI of the page for which to retrieve available microsummaries
  407.    *
  408.    * @param   bookmarkID (optional)
  409.    *          the ID of the bookmark for which this method is being called
  410.    *
  411.    * @returns an nsIMicrosummarySet of nsIMicrosummaries for the given page
  412.    *
  413.    */
  414.   getMicrosummaries: function MSS_getMicrosummaries(pageURI, bookmarkID) {
  415.     var microsummaries = new MicrosummarySet();
  416.  
  417.     // Get microsummaries defined by local generators.
  418.     for (var genURISpec in this._localGenerators) {
  419.       var generator = this._localGenerators[genURISpec];
  420.  
  421.       if (generator.appliesToURI(pageURI)) {
  422.         var microsummary = new Microsummary(pageURI, generator.uri);
  423.         microsummary.generator = generator;
  424.  
  425.         // If this is the current microsummary for this bookmark, load the content
  426.         // from the datastore so it shows up immediately in microsummary picking UI.
  427.         if (bookmarkID && this.isMicrosummary(bookmarkID, microsummary))
  428.           microsummary.content = this._getField(bookmarkID, FIELD_GENERATED_TITLE);
  429.  
  430.         microsummaries.AppendElement(microsummary);
  431.       }
  432.     }
  433.  
  434.     // Get microsummaries defined by the page.  If we don't have the page,
  435.     // download it asynchronously, and then finish populating the set.
  436.     var resource = getLoadedMicrosummaryResource(pageURI);
  437.     if (resource) {
  438.       try     { microsummaries.extractFromPage(resource) }
  439.       finally { resource.destroy() }
  440.     }
  441.     else {
  442.       // Load the page with a callback that will add the page's microsummaries
  443.       // to the set once the page has loaded.
  444.       var callback = function MSS_extractFromPageCallback(resource) {
  445.         try     { microsummaries.extractFromPage(resource) }
  446.         finally { resource.destroy() }
  447.       };
  448.  
  449.       try {
  450.         resource = new MicrosummaryResource(pageURI);
  451.         resource.load(callback);
  452.       }
  453.       catch(e) {
  454.         // We don't have to do anything special if the call fails besides
  455.         // destroying the Resource object.  We can just return the list
  456.         // of microsummaries without including page-defined microsummaries.
  457.         if (resource)
  458.           resource.destroy();
  459.         LOG("error downloading page to extract its microsummaries: " + e);
  460.       }
  461.     }
  462.  
  463.     return microsummaries;
  464.   },
  465.  
  466. //@line 560 "/build/buildd/firefox-1.99+2.0b1+dfsg/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  467.   _getField: function MSS__getField(bookmarkID, fieldName) {
  468.     var bookmarkResource = bookmarkID.QueryInterface(Ci.nsIRDFResource);
  469.     var fieldValue;
  470.  
  471.     var node = this._bmds.GetTarget(bookmarkResource,
  472.                                     this._resource(fieldName),
  473.                                     true);
  474.     if (node) {
  475.       if (fieldName == FIELD_RDF_TYPE)
  476.         fieldValue = node.QueryInterface(Ci.nsIRDFResource).Value;
  477.       else
  478.         fieldValue = node.QueryInterface(Ci.nsIRDFLiteral).Value;
  479.     }
  480.     else
  481.       fieldValue = null;
  482.  
  483.     return fieldValue;
  484.   },
  485.  
  486.   _setField: function MSS__setField(bookmarkID, fieldName, fieldValue) {
  487.     var bookmarkResource = bookmarkID.QueryInterface(Ci.nsIRDFResource);
  488.  
  489.     if (this._hasField(bookmarkID, fieldName)) {
  490.       var oldValue = this._getField(bookmarkID, fieldName);
  491.       this._bmds.Change(bookmarkResource,
  492.                         this._resource(fieldName),
  493.                         this._literal(oldValue),
  494.                         this._literal(fieldValue));
  495.     }
  496.     else {
  497.       this._bmds.Assert(bookmarkResource,
  498.                         this._resource(fieldName),
  499.                         this._literal(fieldValue),
  500.                         true);
  501.     }
  502.  
  503.     // If we're setting the generator URI field, make sure the bookmark's
  504.     // RDF type is set to the microsummary bookmark type.
  505.     if (fieldName == FIELD_MICSUM_GEN_URI &&
  506.         this._getField(bookmarkID, FIELD_RDF_TYPE) != VALUE_MICSUM_BOOKMARK) {
  507.       if (this._hasField(bookmarkID, FIELD_RDF_TYPE)) {
  508.         oldValue = this._getField(bookmarkID, FIELD_RDF_TYPE);
  509.         this._bmds.Change(bookmarkResource,
  510.                           this._resource(FIELD_RDF_TYPE),
  511.                           this._resource(oldValue),
  512.                           this._resource(VALUE_MICSUM_BOOKMARK));
  513.       }
  514.       else {
  515.         this._bmds.Assert(bookmarkResource,
  516.                           this._resource(FIELD_RDF_TYPE),
  517.                           this._resource(VALUE_MICSUM_BOOKMARK),
  518.                           true);
  519.       }
  520.     }
  521.  
  522.     // If we're setting a field that could affect this bookmark's label,
  523.     // then force all bookmark trees to rebuild from scratch.
  524.     if (fieldName == FIELD_MICSUM_GEN_URI || fieldName == FIELD_GENERATED_TITLE)
  525.       this._forceBookmarkTreesRebuild();
  526.   },
  527.  
  528.   _clearField: function MSS__clearField(bookmarkID, fieldName) {
  529.     var bookmarkResource = bookmarkID.QueryInterface(Ci.nsIRDFResource);
  530.  
  531.     var node = this._bmds.GetTarget(bookmarkResource,
  532.                                     this._resource(fieldName),
  533.                                     true);
  534.     if (node) {
  535.       this._bmds.Unassert(bookmarkResource,
  536.                           this._resource(fieldName),
  537.                           node);
  538.     }
  539.  
  540.     // If we're clearing the generator URI field, set the bookmark's RDF type
  541.     // back to the normal bookmark type.
  542.     if (fieldName == FIELD_MICSUM_GEN_URI &&
  543.         this._getField(bookmarkID, FIELD_RDF_TYPE) == VALUE_MICSUM_BOOKMARK)
  544.       this._bmds.Change(bookmarkResource,
  545.                         this._resource(FIELD_RDF_TYPE),
  546.                         this._resource(VALUE_MICSUM_BOOKMARK),
  547.                         this._resource(VALUE_NORMAL_BOOKMARK));
  548.  
  549.     // If we're clearing a field that could affect this bookmark's label,
  550.     // then force all bookmark trees to rebuild from scratch.
  551.     if (fieldName == FIELD_MICSUM_GEN_URI || fieldName == FIELD_GENERATED_TITLE)
  552.       this._forceBookmarkTreesRebuild();
  553.   },
  554.  
  555.   _hasField: function MSS__hasField(bookmarkID, fieldName) {
  556.     var bookmarkResource = bookmarkID.QueryInterface(Ci.nsIRDFResource);
  557.  
  558.     var node = this._bmds.GetTarget(bookmarkResource,
  559.                                     this._resource(fieldName),
  560.                                     true);
  561.     return node ? true : false;
  562.   },
  563.  
  564.   /**
  565.    * Oy vey, a hack!  Force the bookmark trees to rebuild, since they don't
  566.    * seem to be able to do it correctly on their own right after we twiddle
  567.    * something microsummaryish (but they rebuild fine otherwise, incorporating
  568.    * all the microsummary changes upon next full rebuild, f.e. if you open
  569.    * a new window or shut down and restart your browser).
  570.    *
  571.    */
  572.   _forceBookmarkTreesRebuild: function MSS__forceBookmarkTreesRebuild() {
  573.     var mediator = Cc["@mozilla.org/appshell/window-mediator;1"].
  574.                    getService(Ci.nsIWindowMediator);
  575.     var windows = mediator.getEnumerator("navigator:browser");
  576.     while (windows.hasMoreElements()) {
  577.       var win = windows.getNext();
  578.       
  579.       // rebuild the bookmarks toolbar
  580.       var bookmarksToolbar = win.document.getElementById("bookmarks-ptf");
  581.       if (bookmarksToolbar)
  582.         bookmarksToolbar.builder.rebuild();
  583.       
  584.       // rebuild the bookmarks sidebar
  585.       var sidebar = win.document.getElementById("sidebar");
  586.       // sidebar.docShell is null if the sidebar isn't showing, in which case
  587.       // sidebar.contentWindow will throw an exception (because it assumes
  588.       // the existence of sidebar.docShell), so we have to check for .docShell
  589.       // before we check for .contentWindow.
  590.       if (sidebar && sidebar.docShell && sidebar.contentWindow &&
  591.           sidebar.contentWindow.location ==
  592.             "chrome://browser/content/bookmarks/bookmarksPanel.xul") {
  593.         var treeElement = sidebar.contentDocument.getElementById("bookmarks-view");
  594.         treeElement.tree.builder.rebuild();
  595.       }
  596.     }
  597.     
  598.     windows = mediator.getEnumerator("bookmarks:manager");
  599.     while (windows.hasMoreElements()) {
  600.       win = windows.getNext();
  601.       // rebuild the Bookmarks Manager's view
  602.       treeElement = win.document.getElementById("bookmarks-view");
  603.       treeElement.tree.builder.rebuild();
  604.     }
  605.   },
  606.  
  607.   /**
  608.    * Get the URI of the page to which a given bookmark refers.
  609.    *
  610.    * @param   bookmarkResource
  611.    *          an nsIResource uniquely identifying the bookmark
  612.    *
  613.    * @returns an nsIURI object representing the bookmark's page,
  614.    *          or null if the bookmark doesn't exist
  615.    *
  616.    */
  617.   _getPageForBookmark: function MSS__getPageForBookmark(bookmarkID) {
  618.     var bookmarkResource = bookmarkID.QueryInterface(Ci.nsIRDFResource);
  619.  
  620.     var node = this._bmds.GetTarget(bookmarkResource,
  621.                                     this._resource(NC_NS + "URL"),
  622.                                     true);
  623.  
  624.     if (!node)
  625.       return null;
  626.  
  627.     var pageSpec = node.QueryInterface(Ci.nsIRDFLiteral).Value;
  628.     var pageURI = this._uri(pageSpec);
  629.     return pageURI;
  630.   },
  631. //@line 725 "/build/buildd/firefox-1.99+2.0b1+dfsg/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  632.  
  633.   /**
  634.    * Get the current microsummary for the given bookmark.
  635.    *
  636.    * @param   bookmarkID
  637.    *          the bookmark for which to get the current microsummary
  638.    *
  639.    * @returns the current microsummary for the bookmark, or null
  640.    *          if the bookmark does not have a current microsummary
  641.    *
  642.    */
  643.   getMicrosummary: function MSS_getMicrosummary(bookmarkID) {
  644.     if (!this.hasMicrosummary(bookmarkID))
  645.       return null;
  646.  
  647.     var pageURI = this._getPageForBookmark(bookmarkID);
  648.     var generatorURI = this._uri(this._getField(bookmarkID, FIELD_MICSUM_GEN_URI));
  649.     
  650.     var microsummary = new Microsummary(pageURI, generatorURI);
  651.     if (this._localGenerators[generatorURI.spec])
  652.       microsummary.generator = this._localGenerators[generatorURI.spec];
  653.  
  654.     return microsummary;
  655.   },
  656.  
  657.   /**
  658.    * Set the current microsummary for the given bookmark.
  659.    *
  660.    * @param   bookmarkID
  661.    *          the bookmark for which to set the current microsummary
  662.    *
  663.    * @param   microsummary
  664.    *          the microsummary to set as the current one
  665.    *
  666.    */
  667.   setMicrosummary: function MSS_setMicrosummary(bookmarkID, microsummary) {
  668.     this._setField(bookmarkID, FIELD_MICSUM_GEN_URI, microsummary.generatorURI.spec);
  669.     if (microsummary.content)
  670.       this._updateMicrosummary(bookmarkID, microsummary);
  671.     else
  672.       this.refreshMicrosummary(bookmarkID);
  673.   },
  674.  
  675.   /**
  676.    * Remove the current microsummary for the given bookmark.
  677.    *
  678.    * @param   bookmarkID
  679.    *          the bookmark for which to remove the current microsummary
  680.    *
  681.    */
  682.   removeMicrosummary: function MSS_removeMicrosummary(bookmarkID) {
  683.     var fields = [FIELD_MICSUM_GEN_URI,
  684.                   FIELD_MICSUM_EXPIRATION,
  685.                   FIELD_GENERATED_TITLE,
  686.                   FIELD_CONTENT_TYPE];
  687.  
  688.     for ( var i = 0; i < fields.length; i++ ) {
  689.       var field = fields[i];
  690.       if (this._hasField(bookmarkID, field))
  691.         this._clearField(bookmarkID, field);
  692.     }
  693.   },
  694.  
  695.   /**
  696.    * Whether or not the given bookmark has a current microsummary.
  697.    *
  698.    * @param   bookmarkID
  699.    *          the bookmark for which to set the current microsummary
  700.    *
  701.    * @returns a boolean representing whether or not the given bookmark
  702.    *          has a current microsummary
  703.    *
  704.    */
  705.   hasMicrosummary: function MSS_hasMicrosummary(bookmarkID) {
  706.     return this._hasField(bookmarkID, FIELD_MICSUM_GEN_URI);
  707.   },
  708.  
  709.   /**
  710.    * Whether or not the given microsummary is the current microsummary
  711.    * for the given bookmark.
  712.    *
  713.    * @param   bookmarkID
  714.    *          the bookmark to check
  715.    *
  716.    * @param   microsummary
  717.    *          the microsummary to check
  718.    *
  719.    * @returns whether or not the microsummary is the current one
  720.    *          for the bookmark
  721.    *
  722.    */
  723.   isMicrosummary: function MSS_isMicrosummary(bookmarkID, microsummary) {
  724.     if (!this.hasMicrosummary(bookmarkID))
  725.       return false;
  726.  
  727.     var currentGen = this._getField(bookmarkID, FIELD_MICSUM_GEN_URI);
  728.  
  729.     if (microsummary.generatorURI.equals(this._uri(currentGen)))
  730.       return true;
  731.  
  732.     return false
  733.   },
  734.  
  735.   /**
  736.    * Refresh a microsummary, updating its value in the datastore and UI.
  737.    * If this method can refresh the microsummary instantly, it will.
  738.    * Otherwise, it'll asynchronously download the necessary information
  739.    * (the generator and/or page) before refreshing the microsummary.
  740.    *
  741.    * Callers should check the "content" property of the returned microsummary
  742.    * object to distinguish between sync and async refreshes.  If its value
  743.    * is "null", then it's an async refresh, and the caller should register
  744.    * itself as an nsIMicrosummaryObserver via nsIMicrosummary.addObserver()
  745.    * to find out when the refresh completes.
  746.    *
  747.    * @param   bookmarkID
  748.    *          the bookmark whose microsummary is being refreshed
  749.    *
  750.    * @returns the microsummary being refreshed
  751.    *
  752.    */
  753.   refreshMicrosummary: function MSS_refreshMicrosummary(bookmarkID) {
  754.     if (!this.hasMicrosummary(bookmarkID))
  755.       throw "bookmark " + bookmarkID + " does not have a microsummary";
  756.  
  757.     var pageURI = this._getPageForBookmark(bookmarkID);
  758.     var generatorURI = this._uri(this._getField(bookmarkID, FIELD_MICSUM_GEN_URI));
  759.     var microsummary = new Microsummary(pageURI, generatorURI);
  760.     if (this._localGenerators[generatorURI.spec])
  761.       microsummary.generator = this._localGenerators[generatorURI.spec];
  762.  
  763.     // A microsummary observer that calls the microsummary service
  764.     // to update the datastore when the microsummary finishes loading.
  765.     var observer = {
  766.       _svc: this,
  767.       _bookmarkID: bookmarkID,
  768.       onContentLoaded: function MSS_observer_onContentLoaded(microsummary) {
  769.         try {
  770.           this._svc._updateMicrosummary(this._bookmarkID, microsummary);
  771.         }
  772.         finally {
  773.           this._svc = null;
  774.           this._bookmarkID = null;
  775.           microsummary.removeObserver(this);
  776.         }
  777.       }
  778.     };
  779.  
  780.     // Register the observer with the microsummary and trigger the microsummary
  781.     // to update itself.
  782.     microsummary.addObserver(observer);
  783.     microsummary.update();
  784.     
  785.     return microsummary;
  786.   }
  787. };
  788.  
  789.  
  790.  
  791.  
  792.  
  793. function Microsummary(pageURI, generatorURI) {
  794.   this._observers = [];
  795.   this.pageURI = pageURI;
  796.   this.generatorURI = generatorURI;
  797. }
  798.  
  799. Microsummary.prototype = {
  800.   interfaces: [Ci.nsIMicrosummary, Ci.nsISupports],
  801.  
  802.   // nsISupports
  803.  
  804.   QueryInterface: function (iid) {
  805.     //if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
  806.     if (!iid.equals(Ci.nsIMicrosummary) &&
  807.         !iid.equals(Ci.nsISupports))
  808.       throw Components.results.NS_ERROR_NO_INTERFACE;
  809.     return this;
  810.   },
  811.  
  812.   // nsIMicrosummary
  813.  
  814.   _content: null,
  815.   get content() {
  816.     // If we have everything we need to generate the content, generate it.
  817.     if (this._content == null &&
  818.         this.generator &&
  819.         (this.pageContent || !this.generator.needsPageContent))
  820.       this._content = this.generator.generateMicrosummary(this.pageContent);
  821.   
  822.     // Note: we return "null" if the content wasn't already generated and we
  823.     // couldn't retrieve it from the generated title annotation or generate it
  824.     // ourselves.  So callers shouldn't count on getting content; instead,
  825.     // they should call update if the return value of this getter is "null",
  826.     // setting an observer to tell them when content generation is done.
  827.     return this._content;
  828.   },
  829.   set content(newValue) { this._content = newValue },
  830.  
  831.   _generatorURI: null,
  832.   get generatorURI()         { return this._generatorURI },
  833.   set generatorURI(newValue) { this._generatorURI = newValue },
  834.  
  835.   _generator: null,
  836.   get generator()            { return this._generator },
  837.   set generator(newValue)    { this._generator = newValue },
  838.  
  839.   _pageURI: null,
  840.   get pageURI()              { return this._pageURI },
  841.   set pageURI(newValue)      { this._pageURI = newValue },
  842.  
  843.   _pageContent: null,
  844.   get pageContent() {
  845.     if (!this._pageContent) {
  846.       // If the page is currently loaded into a browser window, use that.
  847.       var resource = getLoadedMicrosummaryResource(this.pageURI);
  848.       if (resource) {
  849.         this._pageContent = resource.content;
  850.         resource.destroy();
  851.       }
  852.     }
  853.  
  854.     return this._pageContent;
  855.   },
  856.   set pageContent(newValue) { this._pageContent = newValue },
  857.  
  858.   // nsIMicrosummary
  859.  
  860.   _observers: null,
  861.  
  862.   addObserver: function MS_addObserver(observer) {
  863.     // Register the observer, but only if it isn't already registered,
  864.     // so that we don't call the same observer twice for any given change.
  865.     if (this._observers.indexOf(observer) == -1)
  866.       this._observers.push(observer);
  867.   },
  868.   
  869.   removeObserver: function MS_removeObserver(observer) {
  870.     //NS_ASSERT(this._observers.indexOf(observer) != -1,
  871.     //          "can't remove microsummary observer " + observer + ": not registered");
  872.   
  873.     //this._observers =
  874.     //  this._observers.filter(function(i) { observer != i });
  875.     if (this._observers.indexOf(observer) != -1)
  876.       this._observers.splice(this._observers.indexOf(observer), 1);
  877.   },
  878.  
  879.   /**
  880.    * Regenerates the microsummary, asynchronously downloading its generator
  881.    * and content as needed.
  882.    *
  883.    */
  884.   update: function MS_update() {
  885.     LOG("microsummary.update called for page:\n  " + this.pageURI.spec +
  886.         "\nwith generator:\n  " + this.generatorURI.spec);
  887.  
  888.     var t = this;
  889.  
  890.     // If we don't have the generator, download it now.  After it downloads,
  891.     // we'll re-call this method to continue updating the microsummary.
  892.     if (!this.generator) {
  893.       LOG("generator not yet loaded; downloading it");
  894.       var generatorCallback =
  895.         function MS_generatorCallback(resource) {
  896.           try     { t._handleGeneratorLoad(resource) }
  897.           finally { resource.destroy() }
  898.         };
  899.       var resource = new MicrosummaryResource(this.generatorURI);
  900.       resource.load(generatorCallback);
  901.       return;
  902.     }
  903.  
  904.     // If we need the page content, and we don't have it, download it now.
  905.     // Afterwards we'll re-call this method to continue updating the microsummary.
  906.     if (this.generator.needsPageContent && !this.pageContent) {
  907.       LOG("page content not yet loaded; downloading it");
  908.       var pageCallback =
  909.         function MS_pageCallback(resource) {
  910.           try     { t._handlePageLoad(resource) }
  911.           finally { resource.destroy() }
  912.         };
  913.       var resource = new MicrosummaryResource(this.pageURI);
  914.       resource.load(pageCallback);
  915.       return;
  916.     }
  917.  
  918.     LOG("generator (and page, if needed) both loaded; generating microsummary");
  919.  
  920.     // Now that we have both the generator and (if needed) the page content,
  921.     // generate the microsummary, then let the observers know about it.
  922.     this.content = this.generator.generateMicrosummary(this.pageContent);
  923.     this.pageContent = null;
  924.     for ( var i = 0; i < this._observers.length; i++ )
  925.       this._observers[i].onContentLoaded(this);
  926.  
  927.     LOG("generated microsummary: " + this.content);
  928.   },
  929.  
  930.   _handleGeneratorLoad: function MS__handleGeneratorLoad(resource) {
  931.     var generator = new MicrosummaryGenerator();
  932.     generator.uri = resource.uri;
  933.     LOG(generator.uri.spec + " microsummary generator downloaded");
  934.     if (resource.isXML)
  935.       generator.initFromXML(resource.content);
  936.     else if (resource.contentType == "text/plain")
  937.       generator.initFromText(resource.content);
  938.     else
  939.       throw("generator is neither XML nor plain text");
  940.  
  941.     this.generator = generator;
  942.     this.update();
  943.   },
  944.  
  945.   _handlePageLoad: function MS__handlePageLoad(resource) {
  946.     if (!resource.isXML && !resource.contentType == "text/html")
  947.       throw("page is neither HTML nor XML");
  948.  
  949.     this.pageContent = resource.content;
  950.     this.update();
  951.   }
  952.  
  953. };
  954.  
  955.  
  956.  
  957.  
  958.  
  959. function MicrosummaryGenerator() {}
  960.  
  961. MicrosummaryGenerator.prototype = {
  962.  
  963.   // IO Service
  964.   __ios: null,
  965.   get _ios() {
  966.     if (!this.__ios)
  967.       this.__ios = Cc["@mozilla.org/network/io-service;1"].
  968.                    getService(Ci.nsIIOService);
  969.     return this.__ios;
  970.   },
  971.  
  972.   interfaces: [Ci.nsIMicrosummaryGenerator, Ci.nsISupports],
  973.  
  974.   // nsISupports
  975.  
  976.   QueryInterface: function (iid) {
  977.     //if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
  978.     if (!iid.equals(Ci.nsIMicrosummaryGenerator) &&
  979.         !iid.equals(Ci.nsISupports))
  980.       throw Components.results.NS_ERROR_NO_INTERFACE;
  981.     return this;
  982.   },
  983.  
  984.   // nsIMicrosummaryGenerator
  985.  
  986.   // Normally this is just the URL from which we download the generator,
  987.   // but for generators stored in the app or profile generators directory
  988.   // it's the value of the generator tag's sourceURI attribute (or the
  989.   // generator's local URI should the sourceURI be missing).
  990.   _uri: null,
  991.   get uri() { return this._uri },
  992.   set uri(newValue) { this._uri = newValue },
  993.  
  994.   // For generators bundled with the browser or installed by the user,
  995.   // the local URI is the URI of the local file containing the generator XML.
  996.   _localURI: null,
  997.   get localURI() { return this._localURI },
  998.   set localURI(newValue) { this._localURI = newValue },
  999.  
  1000.   _name: null,
  1001.   get name() { return this._name },
  1002.   set name(newValue) { this._name = newValue },
  1003.  
  1004.   _template: null,
  1005.   get template() { return this._template },
  1006.   set template(newValue) { this._template = newValue },
  1007.  
  1008.   _content: null,
  1009.   get content() { return this._content },
  1010.   set content(newValue) { this._content = newValue },
  1011.  
  1012.   _rules: null,
  1013.  
  1014.   /**
  1015.    * Determines whether or not the generator applies to a given URI.
  1016.    * By default, the generator does not apply to any URI.  In order for it
  1017.    * to apply to a URI, the URI must match one or more of the generator's
  1018.    * "include" rules and not match any of the generator's "exclude" rules.
  1019.    *
  1020.    * @param   uri
  1021.    *          the URI to test to see if this generator applies to it
  1022.    *
  1023.    * @returns boolean
  1024.    *          whether or not the generator applies to the given URI
  1025.    *
  1026.    */
  1027.   appliesToURI: function(uri) {
  1028.     var applies = false;
  1029.  
  1030.     for ( var i = 0 ; i < this._rules.length ; i++ ) {
  1031.       var rule = this._rules[i];
  1032.  
  1033.       switch (rule.type) {
  1034.       case "include":
  1035.         if (rule.regexp.test(uri.spec))
  1036.           applies = true;
  1037.         break;
  1038.       case "exclude":
  1039.         if (rule.regexp.test(uri.spec))
  1040.           return false;
  1041.         break;
  1042.       }
  1043.     }
  1044.  
  1045.     return applies;
  1046.   },
  1047.  
  1048.   get needsPageContent() {
  1049.     if (this.template)
  1050.       return true;
  1051.     else if (this.content)
  1052.       return false;
  1053.     else
  1054.       throw("needsPageContent called on uninitialized microsummary generator");
  1055.   },
  1056.  
  1057.   /**
  1058.    * Initializes a generator from text content.  Generators initialized
  1059.    * from text content merely return that content when their generate() method
  1060.    * gets called.
  1061.    *
  1062.    * @param   text
  1063.    *          the text content
  1064.    *
  1065.    */
  1066.   initFromText: function(text) {
  1067.     this.content = text;
  1068.   },
  1069.  
  1070.   /**
  1071.    * Initializes a generator from an XML description of it.
  1072.    * 
  1073.    * @param   xmlDocument
  1074.    *          An XMLDocument object describing a microsummary generator.
  1075.    *
  1076.    */
  1077.   initFromXML: function(xmlDocument) {
  1078.     // XXX Make sure the argument is a valid generator XML document.
  1079.  
  1080.     // XXX I would have wanted to retrieve the info from the XML via E4X,
  1081.     // but we'll need to pass the XSLT transform sheet to the XSLT processor,
  1082.     // and the processor can't deal with an E4X-wrapped template node.
  1083.  
  1084.     // XXX Right now the code retrieves the first "generator" element
  1085.     // in the microsummaries namespace, regardless of whether or not
  1086.     // it's the root element.  Should it matter?
  1087.     var generatorNode = xmlDocument.getElementsByTagNameNS(MICSUM_NS, "generator")[0];
  1088.     // XXX We should throw instead of returning if the file doesn't contain
  1089.     // a generator.
  1090.     if (!generatorNode)
  1091.       return;
  1092.  
  1093.     this.name = generatorNode.getAttribute("name");
  1094.  
  1095.     // Only set the source URI from the XML if we have a local URI, i.e.
  1096.     // if this is a locally-installed generator, since for remote generators
  1097.     // the source URI of the generator is the URI from which we downloaded it.
  1098.     if (this.localURI) {
  1099.       this.uri = generatorNode.hasAttribute("sourceURI") ?
  1100.                  this._ios.newURI(generatorNode.getAttribute("sourceURI"), null, null) :
  1101.                  this.localURI; // locally created generator without sourceURI
  1102.     }
  1103.  
  1104.     // Slurp the include/exclude rules that determine the pages to which
  1105.     // this generator applies.  Order is important, so we add the rules
  1106.     // in the order in which they appear in the XML.
  1107.     this._rules = [];
  1108.     var pages = generatorNode.getElementsByTagNameNS(MICSUM_NS, "pages")[0];
  1109.     if (pages) {
  1110.       // XXX Make sure the pages tag exists.
  1111.       for ( var i = 0; i < pages.childNodes.length ; i++ ) {
  1112.         var node = pages.childNodes[i];
  1113.         if (node.nodeType != node.ELEMENT_NODE ||
  1114.             node.namespaceURI != MICSUM_NS ||
  1115.             (node.nodeName != "include" && node.nodeName != "exclude"))
  1116.           continue;
  1117.         this._rules.push({ type: node.nodeName, regexp: new RegExp(node.textContent) });
  1118.       }
  1119.     }
  1120.  
  1121.     var templateNode = generatorNode.getElementsByTagNameNS(MICSUM_NS, "template")[0];
  1122.     if (templateNode) {
  1123.       this.template = templateNode.getElementsByTagNameNS(XSLT_NS, "transform")[0]
  1124.                       || templateNode.getElementsByTagNameNS(XSLT_NS, "stylesheet")[0];
  1125.     }
  1126.       // XXX Make sure the template is a valid XSL transform sheet.
  1127.   },
  1128.  
  1129.   generateMicrosummary: function MSD_generateMicrosummary(pageContent) {
  1130.     if (this.content)
  1131.       return this.content.replace(/^\s+|\s+$/g, "");
  1132.     else if (this.template)
  1133.       return this._processTemplate(pageContent);
  1134.     else
  1135.       throw("generateMicrosummary called on uninitialized microsummary generator");
  1136.   },
  1137.   
  1138.   _processTemplate: function MSD__processTemplate(doc) {
  1139.     LOG("processing template " + this.template + " against document " + doc);
  1140.  
  1141.     // XXX Should we just have one global instance of the processor?
  1142.     var processor = Cc["@mozilla.org/document-transformer;1?type=xslt"].
  1143.                     createInstance(Ci.nsIXSLTProcessor);
  1144.  
  1145.     // Turn off document loading of all kinds (document(), <include>, <import>)
  1146.     // for security (otherwise local generators would be able to load local files).
  1147.     processor.flags |= Ci.nsIXSLTProcessorPrivate.DISABLE_ALL_LOADS;
  1148.  
  1149.     processor.importStylesheet(this.template);
  1150.     var fragment = processor.transformToFragment(doc, doc);
  1151.  
  1152.     LOG("template processing result: " + fragment.textContent);
  1153.  
  1154.     // XXX When we support HTML microsummaries we'll need to do something
  1155.     // more sophisticated than just returning the text content of the fragment.
  1156.     return fragment.textContent;
  1157.   }
  1158. };
  1159.  
  1160.  
  1161.  
  1162.  
  1163.  
  1164. // Microsummary sets are collections of microsummaries.  They allow callers
  1165. // to register themselves as observers of the set, and when any microsummary
  1166. // in the set changes, the observers get notified.  Thus a caller can observe
  1167. // the set instead of each individual microsummary.
  1168.  
  1169. function MicrosummarySet() {
  1170.   this._observers = [];
  1171.   this._elements = [];
  1172. }
  1173.  
  1174. MicrosummarySet.prototype = {
  1175.  
  1176.   // IO Service
  1177.   __ios: null,
  1178.   get _ios() {
  1179.     if (!this.__ios)
  1180.       this.__ios = Cc["@mozilla.org/network/io-service;1"].
  1181.                    getService(Ci.nsIIOService);
  1182.     return this.__ios;
  1183.   },
  1184.  
  1185.   interfaces: [Ci.nsIMicrosummarySet,
  1186.                Ci.nsIMicrosummaryObserver,
  1187.                Ci.nsISupports],
  1188.  
  1189.   QueryInterface: function (iid) {
  1190.     //if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
  1191.     if (!iid.equals(Ci.nsIMicrosummarySet) &&
  1192.         !iid.equals(Ci.nsIMicrosummaryObserver) &&
  1193.         !iid.equals(Ci.nsISupports))
  1194.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1195.     return this;
  1196.   },
  1197.  
  1198.   _observers: null,
  1199.   _elements: null,
  1200.  
  1201.   // nsIMicrosummaryObserver
  1202.  
  1203.   onContentLoaded: function MSSet_onContentLoaded(microsummary) {
  1204.     for ( var i = 0; i < this._observers.length; i++ )
  1205.       this._observers[i].onContentLoaded(microsummary);
  1206.   },
  1207.  
  1208.   // nsIMicrosummarySet
  1209.  
  1210.   addObserver: function MSSet_addObserver(observer) {
  1211.     if (this._observers.length == 0) {
  1212.       for ( var i = 0 ; i < this._elements.length ; i++ )
  1213.         this._elements[i].addObserver(this);
  1214.     }
  1215.  
  1216.     // Register the observer, but only if it isn't already registered,
  1217.     // so that we don't call the same observer twice for any given change.
  1218.     if (this._observers.indexOf(observer) == -1)
  1219.       this._observers.push(observer);
  1220.   },
  1221.   
  1222.   removeObserver: function MSSet_removeObserver(observer) {
  1223.     //NS_ASSERT(this._observers.indexOf(observer) != -1,
  1224.     //          "can't remove microsummary observer " + observer + ": not registered");
  1225.   
  1226.     //this._observers =
  1227.     //  this._observers.filter(function(i) { observer != i });
  1228.     if (this._observers.indexOf(observer) != -1)
  1229.       this._observers.splice(this._observers.indexOf(observer), 1);
  1230.     
  1231.     if (this._observers.length == 0) {
  1232.       for ( var i = 0 ; i < this._elements.length ; i++ )
  1233.         this._elements[i].removeObserver(this);
  1234.     }
  1235.   },
  1236.  
  1237.   extractFromPage: function MSSet_extractFromPage(resource) {
  1238.     if (!resource.isXML && resource.contentType != "text/html")
  1239.       throw("page is neither HTML nor XML");
  1240.  
  1241.     // XXX Handle XML documents, whose microsummaries are specified
  1242.     // via processing instructions.
  1243.  
  1244.     var links = resource.content.getElementsByTagName("LINK");
  1245.     for ( var i = 0; i < links.length; i++ ) {
  1246.       var link = links[i];
  1247.  
  1248.       if (link.getAttribute("rel") != "microsummary")
  1249.         continue;
  1250.  
  1251.       // Unlike the "href" attribute, the "href" property contains
  1252.       // an absolute URI spec, so we use it here to create the URI.
  1253.       var generatorURI = this._ios.newURI(link.href,
  1254.                                           resource.content.characterSet,
  1255.                                           null);
  1256.  
  1257.       try {
  1258.         const securityManager = Cc["@mozilla.org/scriptsecuritymanager;1"].
  1259.                                 getService(Ci.nsIScriptSecurityManager);
  1260.         securityManager.checkLoadURI(resource.uri,
  1261.                                      generatorURI,
  1262.                                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
  1263.       }
  1264.       catch(e) {
  1265.         LOG("can't load generator " + generatorURI.spec + " from page " +
  1266.             resource.uri.spec + ": " + e);
  1267.         continue;
  1268.       }
  1269.  
  1270.       var microsummary = new Microsummary(resource.uri, generatorURI);
  1271.       this.AppendElement(microsummary);
  1272.     }
  1273.   },
  1274.  
  1275.   // XXX Turn this into a complete implementation of nsICollection?
  1276.   AppendElement: function MSSet_AppendElement(element) {
  1277.     // Query the element to a microsummary.
  1278.     // XXX Should we NS_ASSERT if this fails?
  1279.     element = element.QueryInterface(Ci.nsIMicrosummary);
  1280.  
  1281.     if (this._elements.indexOf(element) == -1) {
  1282.       this._elements.push(element);
  1283.       element.addObserver(this);
  1284.     }
  1285.  
  1286.     // Notify observers that an element has been appended.
  1287.     for ( var i = 0; i < this._observers.length; i++ )
  1288.       this._observers[i].onElementAppended(element);
  1289.   },
  1290.  
  1291.   Enumerate: function MSSet_Enumerate() {
  1292.     return new ArrayEnumerator(this._elements);
  1293.   }
  1294. };
  1295.  
  1296.  
  1297.  
  1298.  
  1299.  
  1300. /**
  1301.  * An enumeration of items in a JS array.
  1302.  * @constructor
  1303.  */
  1304. function ArrayEnumerator(aItems) {
  1305.   this._index = 0;
  1306.   if (aItems) {
  1307.     for (var i = 0; i < aItems.length; ++i) {
  1308.       if (!aItems[i])
  1309.         aItems.splice(i, 1);      
  1310.     }
  1311.   }
  1312.   this._contents = aItems;
  1313. }
  1314.  
  1315. ArrayEnumerator.prototype = {
  1316.   interfaces: [Ci.nsISimpleEnumerator, Ci.nsISupports],
  1317.  
  1318.   QueryInterface: function (iid) {
  1319.     //if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
  1320.     if (!iid.equals(Ci.nsISimpleEnumerator) &&
  1321.         !iid.equals(Ci.nsISupports))
  1322.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1323.     return this;
  1324.   },
  1325.  
  1326.   _index: 0,
  1327.   _contents: [],
  1328.   
  1329.   hasMoreElements: function() {
  1330.     return this._index < this._contents.length;
  1331.   },
  1332.   
  1333.   getNext: function() {
  1334.     return this._contents[this._index++];      
  1335.   }
  1336. };
  1337.  
  1338.  
  1339.  
  1340.  
  1341.  
  1342. function LOG(str) {
  1343.   dump(str + "\n");
  1344.   //var css = Cc['@mozilla.org/consoleservice;1'].
  1345.   //          getService(Ci.nsIConsoleService);
  1346.   //css.logStringMessage("MsS: " + str)
  1347. }
  1348.  
  1349.  
  1350.  
  1351.  
  1352.  
  1353. /**
  1354.  * A resource (page, microsummary, generator, etc.) identifiable by URI.
  1355.  * This object abstracts away much of the code for loading resources
  1356.  * and parsing their content if they are XML or HTML.
  1357.  * 
  1358.  * @constructor
  1359.  * 
  1360.  * @param   uri
  1361.  *          the location of the resource
  1362.  *
  1363.  */
  1364. function MicrosummaryResource(uri) {
  1365.   // Make sure we're not loading javascript: or data: URLs, which could
  1366.   // take advantage of the load to run code with chrome: privileges.
  1367.   // XXX Perhaps use nsIScriptSecurityManager.checkLoadURI instead.
  1368.   if (uri.scheme != "http" && uri.scheme != "https" && uri.scheme != "file")
  1369.     throw NS_ERROR_DOM_BAD_URI;
  1370.  
  1371.   this._uri = uri;
  1372. }
  1373.  
  1374. MicrosummaryResource.prototype = {
  1375.   // IO Service
  1376.   __ios: null,
  1377.   get _ios() {
  1378.     if (!this.__ios)
  1379.       this.__ios = Cc["@mozilla.org/network/io-service;1"].
  1380.                    getService(Ci.nsIIOService);
  1381.     return this.__ios;
  1382.   },
  1383.  
  1384.   _uri: null,
  1385.   get uri() {
  1386.     return this._uri;
  1387.   },
  1388.  
  1389.   _content: null,
  1390.   get content() {
  1391.     return this._content;
  1392.   },
  1393.  
  1394.   _contentType: null,
  1395.   get contentType() {
  1396.     return this._contentType;
  1397.   },
  1398.  
  1399.   _isXML: false,
  1400.   get isXML() {
  1401.     return this._isXML;
  1402.   },
  1403.  
  1404.   // A function to call when we finish loading/parsing the resource.
  1405.   _callback: null,
  1406.  
  1407.   // A hidden iframe to parse HTML content.
  1408.   _iframe: null,
  1409.  
  1410.   /**
  1411.    * Initialize the resource from an existing DOM document object.
  1412.    * 
  1413.    * @param   document
  1414.    *          a DOM document object
  1415.    *
  1416.    */
  1417.   initFromDocument: function MSR_initFromDocument(document) {
  1418.     this._content = document;
  1419.     this._contentType = document.contentType;
  1420.  
  1421.     // Normally we set this property based on whether or not
  1422.     // XMLHttpRequest parsed the content into an XML document object,
  1423.     // but since we already have the content, we have to analyze
  1424.     // its content type ourselves to see if it is XML.
  1425.     this._isXML = (this.contentType == "text/xml" ||
  1426.                    this.contentType == "application/xml" ||
  1427.                    /^.+\/.+\+xml$/.test(this.contentType));
  1428.   },
  1429.  
  1430.   /**
  1431.    * Destroy references to avoid leak-causing cycles.  Instantiators must call
  1432.    * this method on all instances they instantiate once they're done with them.
  1433.    *
  1434.    */
  1435.   destroy: function MSR_destroy() {
  1436.     this._uri = null;
  1437.     this._content = null;
  1438.     this._callback = null;
  1439.     if (this._iframe) {
  1440.       if (this._iframe && this._iframe.parentNode)
  1441.         this._iframe.parentNode.removeChild(this._iframe);
  1442.       this._iframe = null;
  1443.     }
  1444.   },
  1445.  
  1446.   /**
  1447.    * Load the resource.
  1448.    * 
  1449.    * @param   callback
  1450.    *          a function to invoke when the resource finishes loading
  1451.    *
  1452.    */
  1453.   load: function MSR_load(callback) {
  1454.     LOG(this.uri.spec + " loading");
  1455.   
  1456.     this._callback = callback;
  1457.  
  1458.     var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
  1459.   
  1460.     var loadHandler = {
  1461.       _self: this,
  1462.       handleEvent: function MSR_loadHandler_handleEvent(event) {
  1463.         event.target.removeEventListener("load", this, false);
  1464.         try     { this._self._handleLoad(event) }
  1465.         finally { this._self = null }
  1466.       }
  1467.     };
  1468.  
  1469.     request = request.QueryInterface(Ci.nsIDOMEventTarget);
  1470.     request.addEventListener("load", loadHandler, false);
  1471.     
  1472.     request = request.QueryInterface(Ci.nsIXMLHttpRequest);
  1473.     request.open("GET", this.uri.spec, true);
  1474.     request.send(null);
  1475.   },
  1476.  
  1477.   _handleLoad: function MSR__handleLoad(event) {
  1478.     var request = event.target;
  1479.  
  1480.     if (request.responseXML) {
  1481.       this._isXML = true;
  1482.       // XXX Figure out the parsererror format and log a specific error.
  1483.       if (request.responseXML.documentElement.nodeName == "parsererror")
  1484.         throw(request.channel.originalURI.spec + " contains invalid XML");
  1485.       this._content = request.responseXML;
  1486.       this._contentType = request.channel.contentType;
  1487.       this._callback(this);
  1488.     }
  1489.  
  1490.     else if (request.channel.contentType == "text/html") {
  1491.       this._parse(request.responseText);
  1492.     }
  1493.  
  1494.     else {
  1495.       // This catches text/plain as well as any other content types
  1496.       // not accounted for by the content type-specific code above.
  1497.       this._content = request.responseText;
  1498.       this._contentType = request.channel.contentType;
  1499.       this._callback(this);
  1500.     }
  1501.   },
  1502.   
  1503.   /**
  1504.    * Parse a string of HTML text.  Used by _load() when it retrieves HTML.
  1505.    * We do this via hidden XUL iframes, which according to bz is the best way
  1506.    * to do it currently, since bug 102699 is hard to fix.
  1507.    * 
  1508.    * @param   htmlText
  1509.    *          a string containing the HTML content
  1510.    *
  1511.    */
  1512.   _parse: function MSR__parse(htmlText) {
  1513.     // Find a window to stick our hidden iframe into.
  1514.     var windowMediator = Cc['@mozilla.org/appshell/window-mediator;1'].
  1515.                          getService(Ci.nsIWindowMediator);
  1516.     var window = windowMediator.getMostRecentWindow("navigator:browser");
  1517.     // XXX We can use other windows, too, so perhaps we should try to get
  1518.     // some other window if there's no browser window open.  Perhaps we should
  1519.     // even prefer other windows, since there's less chance of any browser
  1520.     // window machinery like throbbers treating our load like one initiated
  1521.     // by the user.
  1522.     if (!window)
  1523.       throw(this._uri.spec + " can't parse; no browser window");
  1524.     var document = window.document;
  1525.     var rootElement = document.documentElement;
  1526.   
  1527.     // Create an iframe, make it hidden, and secure it against untrusted content.
  1528.     this._iframe = document.createElement('iframe');
  1529.     this._iframe.setAttribute("collapsed", true);
  1530.     this._iframe.setAttribute("type", "content");
  1531.   
  1532.     // Insert the iframe into the window, creating the doc shell, then turn off
  1533.     // JavaScript and auth dialogs for security and turn off other things
  1534.     // to reduce network load.
  1535.     // XXX We should also turn off CSS.
  1536.     rootElement.appendChild(this._iframe);
  1537.     this._iframe.docShell.allowJavascript = false;
  1538.     this._iframe.docShell.allowAuth = false;
  1539.     this._iframe.docShell.allowPlugins = false;
  1540.     this._iframe.docShell.allowMetaRedirects = false;
  1541.     this._iframe.docShell.allowSubframes = false;
  1542.     this._iframe.docShell.allowImages = false;
  1543.   
  1544.     var parseHandler = {
  1545.       _self: this,
  1546.       handleEvent: function MSR_parseHandler_handleEvent(event) {
  1547.         // Appending a new iframe to a XUL window makes it immediately start
  1548.         // loading "about:blank", and we'll probably get notified about that
  1549.         // first, so make sure we check for that and drop it on the floor.
  1550.         if (event.originalTarget.location == "about:blank")
  1551.           return;
  1552.  
  1553.         event.target.removeEventListener("DOMContentLoaded", this, false);
  1554.         try     { this._self._handleParse(event) }
  1555.         finally { this._self = null }
  1556.       }
  1557.     };
  1558.  
  1559.     // Convert the HTML text into an input stream.
  1560.     var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  1561.                     createInstance(Ci.nsIScriptableUnicodeConverter);
  1562.     converter.charset = "UTF-8";
  1563.     var stream = converter.convertToInputStream(htmlText);
  1564.  
  1565.     // Set up a channel to load the input stream.
  1566.     var channel = Cc["@mozilla.org/network/input-stream-channel;1"].
  1567.                   createInstance(Ci.nsIInputStreamChannel);
  1568.     channel.setURI(this._uri);
  1569.     channel.contentStream = stream;
  1570.  
  1571.     // Load in the background so we don't trigger web progress listeners.
  1572.     var request = channel.QueryInterface(Ci.nsIRequest);
  1573.     request.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND;
  1574.  
  1575.     // Specify the content type since we're not loading content from a server,
  1576.     // so it won't get specified for us, and if we don't specify it ourselves,
  1577.     // then Firefox will prompt the user to download content of "unknown type".
  1578.     var baseChannel = channel.QueryInterface(Ci.nsIChannel);
  1579.     baseChannel.contentType = "text/html";
  1580.  
  1581.     // Load as UTF-8, which it'll always be, because XMLHttpRequest converts
  1582.     // the text (i.e. XMLHTTPRequest.responseText) from its original charset
  1583.     // to UTF-16, then the string input stream component converts it to UTF-8.
  1584.     baseChannel.contentCharset = "UTF-8";
  1585.  
  1586.     // Register the parse handler as a load event listener and start the load.
  1587.     // Listen for "DOMContentLoaded" instead of "load" because background loads
  1588.     // don't fire "load" events.
  1589.     this._iframe.addEventListener("DOMContentLoaded", parseHandler, true);
  1590.     var uriLoader = Cc["@mozilla.org/uriloader;1"].getService(Ci.nsIURILoader);
  1591.     uriLoader.openURI(channel, true, this._iframe.docShell);
  1592.   },
  1593.  
  1594.   /**
  1595.    * Handle a load event for the iframe-based parser.
  1596.    * 
  1597.    * @param   event
  1598.    *          the event object representing the load event
  1599.    *
  1600.    */
  1601.   _handleParse: function MSR__handleParse(event) {
  1602.     // XXX Make sure the parse was successful?
  1603.  
  1604.     this._content = this._iframe.contentDocument;
  1605.     this._contentType = this._iframe.contentDocument.contentType;
  1606.     this._callback(this);
  1607.   }
  1608.  
  1609. };
  1610.  
  1611. /**
  1612.  * Get a resource currently loaded into a browser window.  Checks windows
  1613.  * one at a time, starting with the frontmost (a.k.a. most recent) one.
  1614.  * 
  1615.  * @param   uri
  1616.  *          the URI of the resource
  1617.  *
  1618.  * @returns a Resource object, if the resource is currently loaded
  1619.  *          into a browser window; otherwise null
  1620.  *
  1621.  */
  1622. function getLoadedMicrosummaryResource(uri) {
  1623.   var mediator = Cc["@mozilla.org/appshell/window-mediator;1"].
  1624.                  getService(Ci.nsIWindowMediator);
  1625.  
  1626.   // Apparently the Z order enumerator is broken on Linux per bug 156333.
  1627.   //var windows = mediator.getZOrderDOMWindowEnumerator("navigator:browser", true);
  1628.   var windows = mediator.getEnumerator("navigator:browser");
  1629.  
  1630.   while (windows.hasMoreElements()) {
  1631.     var win = windows.getNext();
  1632.     var tabBrowser = win.document.getElementById("content");
  1633.     for ( var i = 0; i < tabBrowser.browsers.length; i++ ) {
  1634.       var browser = tabBrowser.browsers[i];
  1635.       if (uri.equals(browser.currentURI)) {
  1636.         var resource = new MicrosummaryResource(uri);
  1637.         resource.initFromDocument(browser.contentDocument);
  1638.         return resource;
  1639.       }
  1640.     }
  1641.   }
  1642.  
  1643.   return null;
  1644. }
  1645.  
  1646.  
  1647.  
  1648.  
  1649.  
  1650. // From http://lxr.mozilla.org/mozilla/source/browser/components/search/nsSearchService.js
  1651.  
  1652. /**
  1653.  * Removes all characters not in the "chars" string from aName.
  1654.  *
  1655.  * @returns a sanitized name to be used as a filename, or a random name
  1656.  *          if a sanitized name cannot be obtained (if aName contains
  1657.  *          no valid characters).
  1658.  */
  1659. function sanitizeName(aName) {
  1660.   const chars = "-abcdefghijklmnopqrstuvwxyz0123456789";
  1661.  
  1662.   var name = aName.toLowerCase();
  1663.   name = name.replace(/ /g, "-");
  1664.   //name = name.split("").filter(function (el) {
  1665.   //                               return chars.indexOf(el) != -1;
  1666.   //                             }).join("");
  1667.   var filteredName = "";
  1668.   for ( var i = 0 ; i < name.length ; i++ )
  1669.     if (chars.indexOf(name[i]) != -1)
  1670.       filteredName += name[i];
  1671.   name = filteredName;
  1672.  
  1673.   if (!name) {
  1674.     // Our input had no valid characters - use a random name
  1675.     for (var i = 0; i < 8; ++i)
  1676.       name += chars.charAt(Math.round(Math.random() * (chars.length - 1)));
  1677.   }
  1678.  
  1679.   return name;
  1680. }
  1681.  
  1682.  
  1683.  
  1684.  
  1685.  
  1686. var gModule = {
  1687.   registerSelf: function(componentManager, fileSpec, location, type) {
  1688.     componentManager = componentManager.QueryInterface(Ci.nsIComponentRegistrar);
  1689.     
  1690.     for (var key in this._objects) {
  1691.       var obj = this._objects[key];
  1692.       componentManager.registerFactoryLocation(obj.CID,
  1693.                                                obj.className,
  1694.                                                obj.contractID,
  1695.                                                fileSpec,
  1696.                                                location,
  1697.                                                type);
  1698.     }
  1699.   },
  1700.   
  1701.   unregisterSelf: function(componentManager, fileSpec, location) {},
  1702.  
  1703.   getClassObject: function(componentManager, cid, iid) {
  1704.     if (!iid.equals(Components.interfaces.nsIFactory))
  1705.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  1706.   
  1707.     for (var key in this._objects) {
  1708.       if (cid.equals(this._objects[key].CID))
  1709.       return this._objects[key].factory;
  1710.     }
  1711.     
  1712.     throw Components.results.NS_ERROR_NO_INTERFACE;
  1713.   },
  1714.   
  1715.   _objects: {
  1716.     service: {
  1717.       CID        : Components.ID("{460a9792-b154-4f26-a922-0f653e2c8f91}"),
  1718.       contractID : "@mozilla.org/microsummary/service;1",
  1719.       className  : "Microsummary Service",
  1720.       factory    : MicrosummaryServiceFactory = {
  1721.                      createInstance: function(aOuter, aIID) {
  1722.                        if (aOuter != null)
  1723.                          throw Components.results.NS_ERROR_NO_AGGREGATION;
  1724.                        var svc = new MicrosummaryService();
  1725.                        svc._init();
  1726.                       return svc.QueryInterface(aIID);
  1727.                      }
  1728.                    }
  1729.     }
  1730.   },
  1731.   
  1732.   canUnload: function(componentManager) {
  1733.     return true;
  1734.   }
  1735. };
  1736.  
  1737. function NSGetModule(compMgr, fileSpec) {
  1738.   return gModule;
  1739. }
  1740.