home *** CD-ROM | disk | FTP | other *** search
/ PC World 2007 December / PCWorld_2007-12_cd.bin / audio-video / songbird / Songbird_0.3_windows-i686.exe / xulrunner / components / nsLivemarkService.js < prev    next >
Text File  |  2007-10-26  |  33KB  |  1,040 lines

  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  * ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License
  6.  * Version 1.1 (the "License"); you may not use this file except in
  7.  * compliance with the License. You may obtain a copy of the License
  8.  * at http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS"
  11.  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  12.  * the License for the specific language governing rights and
  13.  * limitations under the License.
  14.  *
  15.  * The Original Code is the Places JS Livemark Service.
  16.  *
  17.  * The Initial Developer of the Original Code is Mozilla Corporation.
  18.  * Portions created by the Initial Developer are Copyright (C) 2006
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Annie Sullivan <annie.sullivan@gmail.com> (C++ author)
  23.  *   Joe Hughes <joe@retrovirus.com>
  24.  *   Vladimir Vukicevic <vladimir@pobox.com>
  25.  *   Masayuki Nakano <masayuki@d-toybox.com>
  26.  *   Robert Sayre <sayrer@gmail.com> (JS port)
  27.  *
  28.  * Alternatively, the contents of this file may be used under the
  29.  * terms of either the GNU General Public License Version 2 or later
  30.  * (the "GPL"), or the GNU Lesser General Public License Version 2.1
  31.  * or later (the "LGPL"), in which case the provisions of the GPL or
  32.  * the LGPL are applicable instead of those above. If you wish to
  33.  * allow use of your version of this file only under the terms of
  34.  * either the GPL or the LGPL, and not to allow others to use your
  35.  * version of this file under the terms of the MPL, indicate your
  36.  * decision by deleting the provisions above and replace them with the
  37.  * notice and other provisions required by the GPL or the LGPL. If you
  38.  * do not delete the provisions above, a recipient may use your
  39.  * version of this file under the terms of any one of the MPL, the GPL
  40.  * or the LGPL.
  41.  *
  42.  * ***** END LICENSE BLOCK ***** */
  43.  
  44. const Cc = Components.classes;
  45. const Ci = Components.interfaces;
  46. const Cr = Components.results;
  47.  
  48. //@line 36 "c:\builds\xulrunner\xr_trunk_dubya\mozilla\toolkit\components\url-classifier\content\moz\lang.js"
  49.  
  50.  
  51. /**
  52.  * lang.js - Some missing JavaScript language features
  53.  */
  54.  
  55. /**
  56.  * Partially applies a function to a particular "this object" and zero or
  57.  * more arguments. The result is a new function with some arguments of the first
  58.  * function pre-filled and the value of |this| "pre-specified".
  59.  *
  60.  * Remaining arguments specified at call-time are appended to the pre-
  61.  * specified ones.
  62.  *
  63.  * Usage:
  64.  * var barMethBound = BindToObject(myFunction, myObj, "arg1", "arg2");
  65.  * barMethBound("arg3", "arg4");
  66.  *
  67.  * @param fn {string} Reference to the function to be bound
  68.  *
  69.  * @param self {object} Specifies the object which |this| should point to
  70.  * when the function is run. If the value is null or undefined, it will default
  71.  * to the global object.
  72.  *
  73.  * @returns {function} A partially-applied form of the speficied function.
  74.  */
  75. function BindToObject(fn, self, opt_args) {
  76.   var boundargs = fn.boundArgs_ || [];
  77.   boundargs = boundargs.concat(Array.slice(arguments, 2, arguments.length));
  78.  
  79.   if (fn.boundSelf_)
  80.     self = fn.boundSelf_;
  81.   if (fn.boundFn_)
  82.     fn = fn.boundFn_;
  83.  
  84.   var newfn = function() {
  85.     // Combine the static args and the new args into one big array
  86.     var args = boundargs.concat(Array.slice(arguments));
  87.     return fn.apply(self, args);
  88.   }
  89.  
  90.   newfn.boundArgs_ = boundargs;
  91.   newfn.boundSelf_ = self;
  92.   newfn.boundFn_ = fn;
  93.  
  94.   return newfn;
  95. }
  96.  
  97. /**
  98.  * Inherit the prototype methods from one constructor into another.
  99.  *
  100.  * Usage:
  101.  *
  102.  * function ParentClass(a, b) { }
  103.  * ParentClass.prototype.foo = function(a) { }
  104.  *
  105.  * function ChildClass(a, b, c) {
  106.  *   ParentClass.call(this, a, b);
  107.  * }
  108.  *
  109.  * ChildClass.inherits(ParentClass);
  110.  *
  111.  * var child = new ChildClass("a", "b", "see");
  112.  * child.foo(); // works
  113.  *
  114.  * In addition, a superclass' implementation of a method can be invoked
  115.  * as follows:
  116.  *
  117.  * ChildClass.prototype.foo = function(a) {
  118.  *   ChildClass.superClass_.foo.call(this, a);
  119.  *   // other code
  120.  * };
  121.  */
  122. Function.prototype.inherits = function(parentCtor) {
  123.   var tempCtor = function(){};
  124.   tempCtor.prototype = parentCtor.prototype;
  125.   this.superClass_ = parentCtor.prototype;
  126.   this.prototype = new tempCtor();
  127. }
  128. //@line 36 "c:\builds\xulrunner\xr_trunk_dubya\mozilla\toolkit\components\url-classifier\content\moz\observer.js"
  129.  
  130.  
  131. // A couple of classes to simplify creating observers. 
  132. //
  133. // // Example1:
  134. //
  135. // function doSomething() { ... }
  136. // var observer = new G_ObserverWrapper(topic, doSomething);
  137. // someObj.addObserver(topic, observer);
  138. //
  139. // // Example2: 
  140. //
  141. // function doSomething() { ... }
  142. // new G_ObserverServiceObserver("profile-after-change", 
  143. //                               doSomething,
  144. //                               true /* run only once */);
  145.  
  146.  
  147. /**
  148.  * This class abstracts the admittedly simple boilerplate required of
  149.  * an nsIObserver. It saves you the trouble of implementing the
  150.  * indirection of your own observe() function.
  151.  *
  152.  * @param topic String containing the topic the observer will filter for
  153.  *
  154.  * @param observeFunction Reference to the function to call when the 
  155.  *                        observer fires
  156.  *
  157.  * @constructor
  158.  */
  159. function G_ObserverWrapper(topic, observeFunction) {
  160.   this.debugZone = "observer";
  161.   this.topic_ = topic;
  162.   this.observeFunction_ = observeFunction;
  163. }
  164.  
  165. /**
  166.  * XPCOM
  167.  */
  168. G_ObserverWrapper.prototype.QueryInterface = function(iid) {
  169.   if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIObserver))
  170.     return this;
  171.   throw Components.results.NS_ERROR_NO_INTERFACE;
  172. }
  173.  
  174. /**
  175.  * Invoked by the thingy being observed
  176.  */
  177. G_ObserverWrapper.prototype.observe = function(subject, topic, data) {
  178.   if (topic == this.topic_)
  179.     this.observeFunction_(subject, topic, data);
  180. }
  181.  
  182.  
  183. /**
  184.  * This class abstracts the admittedly simple boilerplate required of
  185.  * observing an observerservice topic. It implements the indirection
  186.  * required, and automatically registers to hear the topic.
  187.  *
  188.  * @param topic String containing the topic the observer will filter for
  189.  *
  190.  * @param observeFunction Reference to the function to call when the 
  191.  *                        observer fires
  192.  *
  193.  * @param opt_onlyOnce Boolean indicating if the observer should unregister
  194.  *                     after it has fired
  195.  *
  196.  * @constructor
  197.  */
  198. function G_ObserverServiceObserver(topic, observeFunction, opt_onlyOnce) {
  199.   this.debugZone = "observerserviceobserver";
  200.   this.topic_ = topic;
  201.   this.observeFunction_ = observeFunction;
  202.   this.onlyOnce_ = !!opt_onlyOnce;
  203.   
  204.   this.observer_ = new G_ObserverWrapper(this.topic_, 
  205.                                          BindToObject(this.observe_, this));
  206.   this.observerService_ = Cc["@mozilla.org/observer-service;1"]
  207.                           .getService(Ci.nsIObserverService);
  208.   this.observerService_.addObserver(this.observer_, this.topic_, false);
  209. }
  210.  
  211. /**
  212.  * Unregister the observer from the observerservice
  213.  */
  214. G_ObserverServiceObserver.prototype.unregister = function() {
  215.   this.observerService_.removeObserver(this.observer_, this.topic_);
  216.   this.observerService_ = null;
  217. }
  218.  
  219. /**
  220.  * Invoked by the observerservice
  221.  */
  222. G_ObserverServiceObserver.prototype.observe_ = function(subject, topic, data) {
  223.   this.observeFunction_(subject, topic, data);
  224.   if (this.onlyOnce_)
  225.     this.unregister();
  226. }
  227.  
  228. //@line 36 "c:\builds\xulrunner\xr_trunk_dubya\mozilla\toolkit\components\url-classifier\content\moz\alarm.js"
  229.  
  230.  
  231. // An Alarm fires a callback after a certain amount of time, or at
  232. // regular intervals. It's a convenient replacement for
  233. // setTimeout/Interval when you don't want to bind to a specific
  234. // window.
  235. //
  236. // The ConditionalAlarm is an Alarm that cancels itself if its callback 
  237. // returns a value that type-converts to true.
  238. //
  239. // Example:
  240. //
  241. //  function foo() { dump('hi'); };
  242. //  new G_Alarm(foo, 10*1000);                   // Fire foo in 10 seconds
  243. //  new G_Alarm(foo, 10*1000, true /*repeat*/);  // Fire foo every 10 seconds
  244. //  new G_Alarm(foo, 10*1000, true, 7);          // Fire foo every 10 seconds
  245. //                                               // seven times
  246. //  new G_ConditionalAlarm(foo, 1000, true); // Fire every sec until foo()==true
  247. //
  248. //  // Fire foo every 10 seconds until foo returns true or until it fires seven
  249. //  // times, whichever happens first.
  250. //  new G_ConditionalAlarm(foo, 10*1000, true /*repeating*/, 7);
  251. //
  252. // TODO: maybe pass an isFinal flag to the callback if they opted to
  253. // set maxTimes and this is the last iteration?
  254.  
  255.  
  256. /**
  257.  * Set an alarm to fire after a given amount of time, or at specific 
  258.  * intervals.
  259.  *
  260.  * @param callback Function to call when the alarm fires
  261.  * @param delayMS Number indicating the length of the alarm period in ms
  262.  * @param opt_repeating Boolean indicating whether this should fire 
  263.  *                      periodically
  264.  * @param opt_maxTimes Number indicating a maximum number of times to 
  265.  *                     repeat (obviously only useful when opt_repeating==true)
  266.  */
  267. function G_Alarm(callback, delayMS, opt_repeating, opt_maxTimes) {
  268.   this.debugZone = "alarm";
  269.   this.callback_ = callback;
  270.   this.repeating_ = !!opt_repeating;
  271.   this.timer_ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  272.   var type = opt_repeating ? 
  273.              this.timer_.TYPE_REPEATING_SLACK : 
  274.              this.timer_.TYPE_ONE_SHOT;
  275.   this.maxTimes_ = opt_maxTimes ? opt_maxTimes : null;
  276.   this.nTimes_ = 0;
  277.  
  278.   this.observerServiceObserver_ = new G_ObserverServiceObserver(
  279.                                         'xpcom-shutdown',
  280.                                         BindToObject(this.cancel, this));
  281.  
  282.   // Ask the timer to use nsITimerCallback (.notify()) when ready
  283.   this.timer_.initWithCallback(this, delayMS, type);
  284. }
  285.  
  286. /**
  287.  * Cancel this timer 
  288.  */
  289. G_Alarm.prototype.cancel = function() {
  290.   if (!this.timer_) {
  291.     return;
  292.   }
  293.  
  294.   this.timer_.cancel();
  295.   // Break circular reference created between this.timer_ and the G_Alarm
  296.   // instance (this)
  297.   this.timer_ = null;
  298.   this.callback_ = null;
  299.  
  300.   // We don't need the shutdown observer anymore
  301.   this.observerServiceObserver_.unregister();
  302. }
  303.  
  304. /**
  305.  * Invoked by the timer when it fires
  306.  * 
  307.  * @param timer Reference to the nsITimer which fired (not currently 
  308.  *              passed along)
  309.  */
  310. G_Alarm.prototype.notify = function(timer) {
  311.   // fire callback and save results
  312.   var ret = this.callback_();
  313.   
  314.   // If they've given us a max number of times to fire, enforce it
  315.   this.nTimes_++;
  316.   if (this.repeating_ && 
  317.       typeof this.maxTimes_ == "number" 
  318.       && this.nTimes_ >= this.maxTimes_) {
  319.     this.cancel();
  320.   } else if (!this.repeating_) {
  321.     // Clear out the callback closure for TYPE_ONE_SHOT timers
  322.     this.cancel();
  323.   }
  324.   // We don't cancel/cleanup timers that repeat forever until either
  325.   // xpcom-shutdown occurs or cancel() is called explicitly.
  326.  
  327.   return ret;
  328. }
  329.  
  330. G_Alarm.prototype.setDelay = function(delay) {
  331.   this.timer_.delay = delay;
  332. }
  333.  
  334. /**
  335.  * XPCOM cruft
  336.  */
  337. G_Alarm.prototype.QueryInterface = function(iid) {
  338.   if (iid.equals(Components.interfaces.nsISupports) ||
  339.       iid.equals(Components.interfaces.nsITimerCallback))
  340.     return this;
  341.  
  342.   throw Components.results.NS_ERROR_NO_INTERFACE;
  343. }
  344.  
  345.  
  346. /**
  347.  * An alarm with the additional property that it cancels itself if its 
  348.  * callback returns true.
  349.  *
  350.  * For parameter documentation, see G_Alarm
  351.  */
  352. function G_ConditionalAlarm(callback, delayMS, opt_repeating, opt_maxTimes) {
  353.   G_Alarm.call(this, callback, delayMS, opt_repeating, opt_maxTimes);
  354.   this.debugZone = "conditionalalarm";
  355. }
  356.  
  357. G_ConditionalAlarm.inherits(G_Alarm);
  358.  
  359. /**
  360.  * Invoked by the timer when it fires
  361.  * 
  362.  * @param timer Reference to the nsITimer which fired (not currently 
  363.  *              passed along)
  364.  */
  365. G_ConditionalAlarm.prototype.notify = function(timer) {
  366.   // Call G_Alarm::notify
  367.   var rv = G_Alarm.prototype.notify.call(this, timer);
  368.  
  369.   if (this.repeating_ && rv) {
  370.     G_Debug(this, "Callback of a repeating alarm returned true; cancelling.");
  371.     this.cancel();
  372.   }
  373. }
  374. //@line 51 "c:\builds\xulrunner\xr_trunk_dubya\mozilla\toolkit\components\places\src\nsLivemarkService.js"
  375.  
  376. function LOG(str) {
  377.   dump("*** " + str + "\n");
  378. }
  379.  
  380. const LS_CLASSID = Components.ID("{dca61eb5-c7cd-4df1-b0fb-d0722baba251}");
  381. const LS_CLASSNAME = "Livemark Service";
  382. const LS_CONTRACTID = "@mozilla.org/browser/livemark-service;2";
  383.  
  384. const LIVEMARK_TIMEOUT = 15000; // fire every 15 seconds
  385. const LIVEMARK_ICON_URI = "chrome://browser/skin/places/livemarkItem.png";
  386. const PLACES_BUNDLE_URI = 
  387.   "chrome://browser/locale/places/places.properties";
  388. const DEFAULT_LOAD_MSG = "Live Bookmark loading...";
  389. const DEFAULT_FAIL_MSG = "Live Bookmark feed failed to load.";
  390. const LMANNO_FEEDURI = "livemark/feedURI";
  391. const LMANNO_SITEURI = "livemark/siteURI";
  392. const LMANNO_EXPIRATION = "livemark/expiration";
  393.  
  394. const PS_CONTRACTID = "@mozilla.org/preferences-service;1";
  395. const NH_CONTRACTID = "@mozilla.org/browser/nav-history-service;1";
  396. const AS_CONTRACTID = "@mozilla.org/browser/annotation-service;1";
  397. const OS_CONTRACTID = "@mozilla.org/observer-service;1";
  398. const SB_CONTRACTID = "@mozilla.org/intl/stringbundle;1";
  399. const IO_CONTRACTID = "@mozilla.org/network/io-service;1";
  400. const BMS_CONTRACTID = "@mozilla.org/browser/nav-bookmarks-service;1";
  401. const FAV_CONTRACTID = "@mozilla.org/browser/favicon-service;1";
  402. const LG_CONTRACTID = "@mozilla.org/network/load-group;1";
  403. const FP_CONTRACTID = "@mozilla.org/feed-processor;1";
  404. const SEC_CONTRACTID = "@mozilla.org/scriptsecuritymanager;1";
  405. const IS_CONTRACTID = "@mozilla.org/widget/idleservice;1";
  406. const SEC_FLAGS = Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL;
  407.  
  408. // Check every hour by default
  409. var gExpiration = 3600000;
  410.  
  411. // Check every 10 minutes on error
  412. const ERROR_EXPIRATION = 600000;
  413.  
  414. // Don't check when the user is idle for longer than half an hour:
  415. const IDLE_TIMELIMIT = 1800000;
  416.  
  417. var gIoService = Cc[IO_CONTRACTID].getService(Ci.nsIIOService);
  418. var gStringBundle;
  419. function GetString(name)
  420. {
  421.   try {
  422.     if (!gStringBundle) {
  423.       var bundleService = Cc[SB_CONTRACTID].getService(); 
  424.       bundleService = bundleService.QueryInterface(Ci.nsIStringBundleService);
  425.       gStringBundle = bundleService.createBundle(PLACES_BUNDLE_URI);
  426.     }
  427.  
  428.     if (gStringBundle)
  429.       return gStringBundle.GetStringFromName(name);
  430.   } catch (ex) {
  431.     LOG("Exception loading string bundle: " + ex.message);
  432.   }
  433.  
  434.   return null;
  435. }
  436.  
  437. var gLivemarkService;
  438. function LivemarkService() {
  439.  
  440.   try {
  441.     var prefs = Cc[PS_CONTRACTID].getService(Ci.nsIPrefBranch);
  442.     var livemarkRefresh = 
  443.       prefs.getIntPref("browser.bookmarks.livemark_refresh_seconds");
  444.     // Reset global expiration variable to reflect hidden pref (in ms)
  445.     // with a lower limit of 1 minute (60000 ms)
  446.     gExpiration = Math.max(livemarkRefresh * 1000, 60000);
  447.   } 
  448.   catch (ex) { }
  449.  
  450.   // [ {folderId:, folderURI:, feedURI:, loadGroup:, locked: } ];
  451.   this._livemarks = [];
  452.  
  453.   this._iconURI = gIoService.newURI(LIVEMARK_ICON_URI, null, null);
  454.   this._loading = GetString("bookmarksLivemarkLoading") || DEFAULT_LOAD_MSG;
  455.   this._observerServiceObserver =
  456.     new G_ObserverServiceObserver('xpcom-shutdown',
  457.                                   BindToObject(this._shutdown, this),
  458.                                   true /*only once*/);
  459.   new G_Alarm(BindToObject(this._fireTimer, this), LIVEMARK_TIMEOUT, 
  460.               true /* repeat */);
  461.  
  462.   if (IS_CONTRACTID in Cc)
  463.     this._idleService = Cc[IS_CONTRACTID].getService(Ci.nsIIdleService);
  464.  
  465.   // this is giving a reentrant getService warning in XPCShell. bug 194568.
  466.   this._ans = Cc[AS_CONTRACTID].getService(Ci.nsIAnnotationService);
  467.  
  468.   var livemarks = this._ans.getItemsWithAnnotation(LMANNO_FEEDURI, {});
  469.   for (var i = 0; i < livemarks.length; i++) {
  470.     var feedURI =
  471.       gIoService.newURI(
  472.         this._ans.getItemAnnotation(livemarks[i], LMANNO_FEEDURI),
  473.         null, null
  474.       );
  475.     this._pushLivemark(livemarks[i], feedURI);
  476.   }
  477.  
  478.   this._bms.addObserver(this, false);
  479. }
  480.  
  481. LivemarkService.prototype = {
  482.  
  483.   get _bms() {
  484.     if (!this.__bms)
  485.       this.__bms = Cc[BMS_CONTRACTID].getService(Ci.nsINavBookmarksService);
  486.     return this.__bms;
  487.   },
  488.  
  489.   get _history() {
  490.     if (!this.__history)
  491.       this.__history = Cc[NH_CONTRACTID].getService(Ci.nsINavHistoryService);
  492.     return this.__history;
  493.   },
  494.  
  495.   // returns new length of _livemarks
  496.   _pushLivemark: function LS__pushLivemark(folderId, feedURI) {
  497.     return this._livemarks.push({folderId: folderId, feedURI: feedURI});
  498.   },
  499.  
  500.   _getLivemarkIndex: function LS__getLivemarkIndex(folderId) {
  501.     for (var i=0; i < this._livemarks.length; ++i) {
  502.       if (this._livemarks[i].folderId == folderId)
  503.         return i;
  504.     }
  505.     throw Cr.NS_ERROR_INVALID_ARG;
  506.   },
  507.  
  508.   _shutdown: function LS__shutdown() {
  509.     // remove bookmarks observer
  510.     this._bms.removeObserver(this);
  511.  
  512.     for (var livemark in this._livemarks) {
  513.       if (livemark.loadGroup) 
  514.         livemark.loadGroup.cancel(Cr.NS_BINDING_ABORTED);
  515.     }
  516.   },
  517.  
  518.   _fireTimer: function LS__fireTimer() {
  519.     for (var i=0; i < this._livemarks.length; ++i) {
  520.       this._updateLivemarkChildren(i, false);
  521.     }
  522.   },
  523.  
  524.   deleteLivemarkChildren: function LS_deleteLivemarkChildren(folderId) {
  525.     this._bms.removeFolderChildren(folderId);
  526.   },
  527.  
  528.   insertLivemarkLoadingItem: function LS_insertLivemarkLoading(bms, livemark) {
  529.     var loadingURI = gIoService.newURI("about:livemark-loading", null, null);
  530.     if (!livemark.loadingId || livemark.loadingId == -1)
  531.       livemark.loadingId = bms.insertBookmark(livemark.folderId, loadingURI,
  532.                                               0, this._loading);
  533.   },
  534.  
  535.   _updateLivemarkChildren:
  536.   function LS__updateLivemarkChildren(index, forceUpdate) {
  537.     if (this._livemarks[index].locked)
  538.       return;
  539.     
  540.     var livemark = this._livemarks[index];
  541.     livemark.locked = true;
  542.     try {
  543.       // Check the TTL/expiration on this.  If there isn't one,
  544.       // then we assume it's never been loaded.  We perform this
  545.       // check even when the update is being forced, in case the
  546.       // livemark has somehow never been loaded.
  547.       var exprTime = this._ans.getPageAnnotation(livemark.feedURI,
  548.                                                  LMANNO_EXPIRATION);
  549.       if (!forceUpdate && exprTime > Date.now()) {
  550.         // no need to refresh
  551.         livemark.locked = false;
  552.         return;
  553.       }
  554.  
  555.       // Check the user idle time. If the user isn't using their computer, don't
  556.       // bother updating - save the internet some bandwidth. If we can't
  557.       // get the idle time, assume the user isn't idle.
  558.       var idleTime = 0;
  559.       try {
  560.         idleTime = this._idleService.idleTime;
  561.       } catch (ex) { /* We don't care */ }
  562.       if (idleTime > IDLE_TIMELIMIT)
  563.       {
  564.         livemark.locked = false;
  565.         return;
  566.       }
  567.     }
  568.     catch (ex) {
  569.       // This livemark has never been loaded, since it has no expire time.
  570.       this.insertLivemarkLoadingItem(this._bms, livemark);
  571.     }
  572.  
  573.     var loadgroup;
  574.     try {
  575.       // Create a load group for the request.  This will allow us to
  576.       // automatically keep track of redirects, so we can always
  577.       // cancel the channel.
  578.       loadgroup = Cc[LG_CONTRACTID].createInstance(Ci.nsILoadGroup);
  579.       var uriChannel = gIoService.newChannel(livemark.feedURI.spec, null, null);
  580.       uriChannel.loadGroup = loadgroup;
  581.       uriChannel.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND | 
  582.                               Ci.nsIRequest.VALIDATE_ALWAYS;
  583.       var httpChannel = uriChannel.QueryInterface(Ci.nsIHttpChannel);
  584.       httpChannel.requestMethod = "GET";
  585.       httpChannel.setRequestHeader("X-Moz", "livebookmarks", false);
  586.  
  587.       this.insertLivemarkLoadingItem(this._bms, livemark);
  588.  
  589.       // Stream the result to the feed parser with this listener
  590.       var listener = new LivemarkLoadListener(livemark);
  591.       httpChannel.asyncOpen(listener, null);
  592.     }
  593.     catch (ex) {
  594.       livemark.locked = false;
  595.       LOG("exception: " + ex);
  596.       throw ex;
  597.     }
  598.     livemark.loadGroup = loadgroup;
  599.   },
  600.  
  601.   createLivemark: function LS_createLivemark(folder, name, siteURI,
  602.                                              feedURI, index) {
  603.     // Don't add livemarks to livemarks
  604.     if (this.isLivemark(folder))
  605.       throw Cr.NS_ERROR_INVALID_ARG;
  606.     var livemarkID = this._createFolder(this._bms, folder, name, siteURI,
  607.                                         feedURI, index);
  608.   
  609.     // kick off http fetch
  610.     this._updateLivemarkChildren(
  611.       this._pushLivemark(livemarkID, feedURI) - 1,
  612.       false
  613.     );
  614.  
  615.     return livemarkID;
  616.   },
  617.  
  618.   createLivemarkFolderOnly:
  619.   function LS_createLivemarkFolderOnly(bms, folder, name, siteURI,
  620.                                        feedURI, index) {
  621.     var livemarkID = this._createFolder(bms, folder, name, siteURI, feedURI,
  622.                                         index);
  623.     this._pushLivemark(livemarkID, feedURI);
  624.     var livemarkIndex = this._getLivemarkIndex(livemarkID);
  625.     var livemark = this._livemarks[livemarkIndex];
  626.     this.insertLivemarkLoadingItem(bms, livemark);
  627.     
  628.     return livemarkID;
  629.   },
  630.  
  631.   _createFolder:
  632.   function LS__createFolder(bms, folder, name, siteURI, feedURI, index) {
  633.     var livemarkID = bms.createFolder(folder, name, index);
  634.     this._bms.setFolderReadonly(livemarkID, true);
  635.  
  636.     // Add an annotation to map the folder URI to the livemark feed URI
  637.     this._ans.setItemAnnotation(livemarkID, LMANNO_FEEDURI, feedURI.spec, 0,
  638.                                 this._ans.EXPIRE_NEVER);
  639.     // Set the favicon
  640.     var faviconService = Cc[FAV_CONTRACTID].getService(Ci.nsIFaviconService);
  641.     var livemarkURI = bms.getFolderURI(livemarkID);
  642.     faviconService.setFaviconUrlForPage(livemarkURI, this._iconURI);
  643.  
  644.     if (siteURI) {
  645.       // Add an annotation to map the folder URI to the livemark site URI
  646.       this._ans.setItemAnnotation(livemarkID, LMANNO_SITEURI, siteURI.spec,
  647.                                   0, this._ans.EXPIRE_NEVER);
  648.     }
  649.  
  650.     return livemarkID;
  651.   },
  652.  
  653.   isLivemark: function LS_isLivemark(folder) {
  654.     return this._ans.itemHasAnnotation(folder, LMANNO_FEEDURI);
  655.   },
  656.  
  657.   _ensureLivemark: function LS__ensureLivemark(container) {
  658.     if (!this.isLivemark(container)) 
  659.       throw Cr.NS_ERROR_INVALID_ARG;
  660.   },
  661.  
  662.   /**
  663.   * n.b. -- the body of this method is duplicated in 
  664.   *         /browser/components/places/content/toolbar.xml
  665.   *         to avoid instantiating the livemark service on
  666.   *         startup.
  667.   */
  668.   getSiteURI: function LS_getSiteURI(container) {
  669.     this._ensureLivemark(container);
  670.  
  671.     if (this._ans.itemHasAnnotation(container, LMANNO_SITEURI)) {
  672.       var siteURIString =
  673.         this._ans.getItemAnnotation(container, LMANNO_SITEURI);
  674.  
  675.       return gIoService.newURI(siteURIString, null, null);
  676.     }
  677.     return null;
  678.   },
  679.  
  680.   setSiteURI: function LS_setSiteURI(container, siteURI) {
  681.     this._ensureLivemark(container);
  682.     
  683.     if (!siteURI) {
  684.       this._ans.removeItemAnnotation(container, LMANNO_SITEURI);
  685.       return;
  686.     }
  687.  
  688.     this._ans.setItemAnnotation(container, LMANNO_SITEURI, siteURI.spec,
  689.                                       0, this._ans.EXPIRE_NEVER);
  690.   },
  691.  
  692.   getFeedURI: function LS_getFeedURI(container) {
  693.     try {
  694.       // getItemAnnotation() can throw if there is no annotation
  695.       var feedURIString = this._ans.getItemAnnotation(container,
  696.                                                       LMANNO_FEEDURI);
  697.        
  698.       return gIoService.newURI(feedURIString, null, null);
  699.     }
  700.     catch (ex) {
  701.       // temporary logging, for bug #381894
  702.       LOG("getFeedURI failed: " + ex);
  703.       LOG("feedURIString: " + feedURIString);
  704.     }
  705.     return null;
  706.   },
  707.  
  708.   setFeedURI: function LS_setFeedURI(container, feedURI) {
  709.     if (!feedURI)
  710.       throw Cr.NS_ERROR_INVALID_ARG;
  711.  
  712.     this._ans.setItemAnnotation(container, LMANNO_FEEDURI, feedURI.spec, 0,
  713.                                 this._ans.EXPIRE_NEVER);
  714.  
  715.     // now update our internal table
  716.     var livemarkIndex = this._getLivemarkIndex(container);  
  717.     this._livemarks[livemarkIndex].feedURI = feedURI;
  718.   },
  719.  
  720.   reloadAllLivemarks: function LS_reloadAllLivemarks() {
  721.     for (var i = 0; i < this._livemarks.length; ++i) {
  722.       this._updateLivemarkChildren(i, true);
  723.     }
  724.   },
  725.  
  726.   reloadLivemarkFolder: function LS_reloadLivemarkFolder(folderID) {
  727.     var livemarkIndex = this._getLivemarkIndex(folderID);  
  728.     this._updateLivemarkChildren(livemarkIndex, true);
  729.   },
  730.  
  731.   // nsINavBookmarkObserver
  732.   onBeginUpdateBatch: function() { },
  733.   onEndUpdateBatch: function() { },
  734.   onItemAdded: function() { },
  735.   onItemChanged: function() { },
  736.   onItemVisited: function() { },
  737.   onItemMoved: function() { },
  738.  
  739.   onItemRemoved: function(aItemId, aParentFolder, aIndex) {
  740.     try {
  741.       var livemarkIndex = this._getLivemarkIndex(aItemId);
  742.     }
  743.     catch(ex) {
  744.       // not a livemark
  745.       return;
  746.     }
  747.     var livemark = this._livemarks[livemarkIndex];
  748.  
  749.     var stillInUse = false;
  750.     stillInUse = this._livemarks.some(
  751.                  function(mark) { return mark.feedURI.equals(livemark.feedURI) } 
  752.                  );
  753.     if (!stillInUse) {
  754.       // ??? the code in the C++ had "livemark_expiration" as
  755.       // the second arg... that must be wrong
  756.       this._ans.removePageAnnotation(livemark.feedURI, LMANNO_EXPIRATION);
  757.     }
  758.  
  759.     if (livemark.loadGroup) 
  760.       livemark.loadGroup.cancel(Cr.NS_BINDING_ABORTED);
  761.     this._livemarks.splice(livemarkIndex, 1);
  762.   },
  763.  
  764.   createInstance: function LS_createInstance(outer, iid) {
  765.     if (outer != null)
  766.       throw Cr.NS_ERROR_NO_AGGREGATION;
  767.     return this.QueryInterface(iid);
  768.   },
  769.   
  770.   QueryInterface: function LS_QueryInterface(iid) {
  771.     if (iid.equals(Ci.nsILivemarkService) ||
  772.         iid.equals(Ci.nsIFactory) ||
  773.         iid.equals(Ci.nsINavBookmarkObserver) ||
  774.         iid.equals(Ci.nsISupports))
  775.       return this;
  776.     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  777.   }
  778. };
  779.  
  780. function LivemarkLoadListener(livemark) {
  781.   this._livemark = livemark;
  782.   this._livemark.loadingId = -1;
  783.   this._processor = null;
  784.   this._isAborted = false;
  785.   this._ttl = gExpiration;
  786.   this._ans = Cc[AS_CONTRACTID].getService(Ci.nsIAnnotationService);
  787. }
  788.  
  789. LivemarkLoadListener.prototype = {
  790.  
  791.   get _failed() {
  792.     return GetString("bookmarksLivemarkFailed") || DEFAULT_FAIL_MSG;
  793.   },
  794.  
  795.   abort: function LLL_abort() {
  796.     this._isAborted = true;
  797.   },
  798.  
  799.   get _bms() {
  800.     if (!this.__bms)
  801.       this.__bms = Cc[BMS_CONTRACTID].getService(Ci.nsINavBookmarksService);
  802.     return this.__bms;
  803.   },
  804.  
  805.   get _history() {
  806.     if (!this.__history)
  807.       this.__history = Cc[NH_CONTRACTID].getService(Ci.nsINavHistoryService);
  808.     return this.__history;
  809.   },
  810.  
  811.   // called back from handleResult
  812.   runBatched: function LLL_runBatched(aUserData) {
  813.     var result = aUserData.QueryInterface(Ci.nsIFeedResult);
  814.  
  815.     // We need this to make sure the item links are safe
  816.     var secMan = Cc[SEC_CONTRACTID].getService(Ci.nsIScriptSecurityManager);
  817.       
  818.     // Clear out any child nodes of the livemark folder, since
  819.     // they're about to be replaced.
  820.     var lmService = Cc[LS_CONTRACTID].getService(Ci.nsILivemarkService);
  821.  
  822.     // Enforce well-formedness because the existing code does
  823.     if (!result || !result.doc || result.bozo) {
  824.       if (this._livemark.loadingId != -1) {
  825.         this._bms.removeItem(this._livemark.loadingId);
  826.         this._livemark.loadingId = -1;
  827.       }
  828.  
  829.       this.insertLivemarkFailedItem(this._livemark.folderId);
  830.       this._ttl = gExpiration;
  831.       throw Cr.NS_ERROR_FAILURE;
  832.     }
  833.  
  834.     this.deleteLivemarkChildren(this._livemark.folderId);
  835.     this._livemark.loadingId = -1;
  836.     var feed = result.doc.QueryInterface(Ci.nsIFeed);
  837.     // Loop through and check for a link and a title
  838.     // as the old code did
  839.     for (var i = 0; i < feed.items.length; ++i) {
  840.       let entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
  841.       let href = entry.link;
  842.       if (!href)
  843.         continue;
  844.  
  845.       let title = entry.title ? entry.title.plainText() : entry.updated;
  846.       if (!title)
  847.         continue;
  848.  
  849.       try {
  850.         secMan.checkLoadURIStr(this._livemark.feedURI.spec, href.spec,
  851.                                SEC_FLAGS);
  852.       }
  853.       catch(ex) {
  854.         continue;
  855.       }
  856.  
  857.       this.insertLivemarkChild(this._livemark.folderId, href, title);
  858.     }
  859.   },
  860.  
  861.   /**
  862.    * See nsIFeedResultListener.idl
  863.    */
  864.   handleResult: function LLL_handleResult(result) {
  865.     if (this._isAborted) {
  866.       this._livemark.locked = false;
  867.       return;
  868.     }
  869.     try {
  870.       // The actual work is done in runBatched, see above.
  871.       this._bms.runInBatchMode(this, result);
  872.     }
  873.     finally {
  874.       this._processor.listener = null;
  875.       this._processor = null;
  876.       this._livemark.locked = false;
  877.     }
  878.   },
  879.   
  880.   deleteLivemarkChildren: LivemarkService.prototype.deleteLivemarkChildren,
  881.  
  882.   insertLivemarkFailedItem: function LS_insertLivemarkFailed(folderId) {
  883.     var failedURI = gIoService.newURI("about:livemark-failed", null, null);
  884.     var id = this._bms.insertBookmark(folderId, failedURI, 0, this._failed);
  885.   },
  886.  
  887.   insertLivemarkChild:
  888.   function LS_insertLivemarkChild(folderId, uri, title) {
  889.     var id = this._bms.insertBookmark(folderId, uri, this._bms.DEFAULT_INDEX,
  890.                                       title);
  891.   },
  892.  
  893.   /**
  894.    * See nsIStreamListener.idl
  895.    */
  896.   onDataAvailable: function LLL_onDataAvailable(request, context, inputStream, 
  897.                                                 sourceOffset, count) {
  898.     this._processor.onDataAvailable(request, context, inputStream,
  899.                                     sourceOffset, count);
  900.   },
  901.   
  902.   /**
  903.    * See nsIRequestObserver.idl
  904.    */
  905.   onStartRequest: function LLL_onStartRequest(request, context) {
  906.     if (this._isAborted)
  907.       throw Cr.NS_ERROR_UNEXPECTED;
  908.  
  909.     var channel = request.QueryInterface(Ci.nsIChannel);
  910.  
  911.     // Parse feed data as it comes in
  912.     this._processor = Cc[FP_CONTRACTID].createInstance(Ci.nsIFeedProcessor);
  913.     this._processor.listener = this;
  914.     this._processor.parseAsync(null, channel.URI);
  915.     
  916.     this._processor.onStartRequest(request, context);
  917.   },
  918.   
  919.   /**
  920.    * See nsIRequestObserver.idl
  921.    */
  922.   onStopRequest: function LLL_onStopRequest(request, context, status) {
  923.     if (!Components.isSuccessCode(status)) {
  924.       // Something went wrong; try to load again in a bit
  925.       this._setResourceTTL(ERROR_EXPIRATION);
  926.       this._isAborted = true;
  927.       return;
  928.     }
  929.     // Set an expiration on the livemark, for reloading the data
  930.     try { 
  931.       this._processor.onStopRequest(request, context, status);
  932.  
  933.       // Calculate a new ttl
  934.       var channel = request.QueryInterface(Ci.nsICachingChannel);
  935.       if (channel) {
  936.         var entryInfo = channel.cacheToken.QueryInterface(Ci.nsICacheEntryInfo);
  937.         if (entryInfo) {
  938.           // nsICacheEntryInfo returns value as seconds, 
  939.           // expiresTime stores as ms
  940.           var expiresTime = entryInfo.expirationTime * 1000;
  941.           var nowTime = Date.now();
  942.           
  943.           // note, expiresTime can be 0, see bug #383538
  944.           if (expiresTime > nowTime) {
  945.             this._setResourceTTL(Math.max((expiresTime - nowTime),
  946.                                  gExpiration));
  947.             return;
  948.           }
  949.         }
  950.       }
  951.     }
  952.     catch (ex) { }
  953.     this._setResourceTTL(this._ttl);
  954.   },
  955.  
  956.   _setResourceTTL: function LLL__setResourceTTL(milliseconds) {
  957.     var exptime = Date.now() + milliseconds;
  958.     this._ans.setPageAnnotation(this._livemark.feedURI, LMANNO_EXPIRATION,
  959.                                 exptime, 0,
  960.                                 Ci.nsIAnnotationService.EXPIRE_NEVER);
  961.   },
  962.   
  963.   /**
  964.    * See nsISupports.idl
  965.    */
  966.   QueryInterface: function LLL_QueryInterface(iid) {
  967.     if (iid.equals(Ci.nsIFeedResultListener) ||
  968.         iid.equals(Ci.nsIStreamListener) ||
  969.         iid.equals(Ci.nsIRequestObserver)||
  970.         iid.equals(Ci.nsINavHistoryBatchCallback) ||
  971.         iid.equals(Ci.nsISupports))
  972.       return this;
  973.     throw Cr.NS_ERROR_NO_INTERFACE;
  974.   },
  975. }
  976.  
  977. function GenericComponentFactory(ctor) {
  978.   this._ctor = ctor;
  979. }
  980.  
  981. GenericComponentFactory.prototype = {
  982.  
  983.   _ctor: null,
  984.  
  985.   // nsIFactory
  986.   createInstance: function(outer, iid) {
  987.     if (outer != null)
  988.       throw Cr.NS_ERROR_NO_AGGREGATION;
  989.     return (new this._ctor()).QueryInterface(iid);
  990.   },
  991.  
  992.   // nsISupports
  993.   QueryInterface: function(iid) {
  994.     if (iid.equals(Ci.nsIFactory) ||
  995.         iid.equals(Ci.nsISupports))
  996.       return this;
  997.     throw Cr.NS_ERROR_NO_INTERFACE;
  998.   },
  999.  
  1000. };
  1001.  
  1002. var Module = {
  1003.   QueryInterface: function(iid) {
  1004.     if (iid.equals(Ci.nsIModule) || 
  1005.         iid.equals(Ci.nsISupports))
  1006.       return this;
  1007.  
  1008.     throw Cr.NS_ERROR_NO_INTERFACE;
  1009.   },
  1010.  
  1011.   getClassObject: function M_getClassObject(cm, cid, iid) {
  1012.     if (!iid.equals(Ci.nsIFactory))
  1013.       throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1014.     if (cid.equals(LS_CLASSID))
  1015.       return new GenericComponentFactory(LivemarkService);
  1016.  
  1017.     throw Cr.NS_ERROR_NO_INTERFACE;
  1018.   },
  1019.  
  1020.   registerSelf: function(cm, file, location, type) {
  1021.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  1022.  
  1023.     cr.registerFactoryLocation(LS_CLASSID, LS_CLASSNAME,
  1024.       LS_CONTRACTID, file, location, type);    
  1025.   },
  1026.  
  1027.   unregisterSelf: function M_unregisterSelf(cm, location, type) {
  1028.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  1029.     cr.unregisterFactoryLocation(LS_CLASSID, location);
  1030.   },
  1031.   
  1032.   canUnload: function M_canUnload(cm) {
  1033.     return true;
  1034.   }
  1035. };
  1036.  
  1037. function NSGetModule(cm, file) {
  1038.   return Module;
  1039. }
  1040.