home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February / PCWorld_2008-02_cd.bin / temacd / songbird / Songbird_0.4_windows-i686.exe / xulrunner / components / nsLivemarkService.js < prev    next >
Text File  |  2007-12-19  |  34KB  |  1,057 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. const LMANNO_LOADFAILED = "livemark/loadfailed";
  394.  
  395. const PS_CONTRACTID = "@mozilla.org/preferences-service;1";
  396. const NH_CONTRACTID = "@mozilla.org/browser/nav-history-service;1";
  397. const AS_CONTRACTID = "@mozilla.org/browser/annotation-service;1";
  398. const OS_CONTRACTID = "@mozilla.org/observer-service;1";
  399. const SB_CONTRACTID = "@mozilla.org/intl/stringbundle;1";
  400. const IO_CONTRACTID = "@mozilla.org/network/io-service;1";
  401. const BMS_CONTRACTID = "@mozilla.org/browser/nav-bookmarks-service;1";
  402. const FAV_CONTRACTID = "@mozilla.org/browser/favicon-service;1";
  403. const LG_CONTRACTID = "@mozilla.org/network/load-group;1";
  404. const FP_CONTRACTID = "@mozilla.org/feed-processor;1";
  405. const SEC_CONTRACTID = "@mozilla.org/scriptsecuritymanager;1";
  406. const IS_CONTRACTID = "@mozilla.org/widget/idleservice;1";
  407. const SEC_FLAGS = Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL;
  408.  
  409. // Check every hour by default
  410. var gExpiration = 3600000;
  411.  
  412. // Check every 10 minutes on error
  413. const ERROR_EXPIRATION = 600000;
  414.  
  415. // Don't check when the user is idle for longer than half an hour:
  416. const IDLE_TIMELIMIT = 1800000;
  417.  
  418. var gIoService = Cc[IO_CONTRACTID].getService(Ci.nsIIOService);
  419. var gStringBundle;
  420. function GetString(name)
  421. {
  422.   try {
  423.     if (!gStringBundle) {
  424.       var bundleService = Cc[SB_CONTRACTID].getService(); 
  425.       bundleService = bundleService.QueryInterface(Ci.nsIStringBundleService);
  426.       gStringBundle = bundleService.createBundle(PLACES_BUNDLE_URI);
  427.     }
  428.  
  429.     if (gStringBundle)
  430.       return gStringBundle.GetStringFromName(name);
  431.   } catch (ex) {
  432.     LOG("Exception loading string bundle: " + ex.message);
  433.   }
  434.  
  435.   return null;
  436. }
  437.  
  438. function MarkLivemarkLoadFailed(aFolderId) {
  439.   // if it failed before, too, nothing more to do
  440.   var ans = Cc[AS_CONTRACTID].getService(Ci.nsIAnnotationService);
  441.   if (ans.itemHasAnnotation(aFolderId, LMANNO_LOADFAILED))
  442.     return;
  443.  
  444.   var failedMsg = GetString("bookmarksLivemarkFailed") || DEFAULT_FAIL_MSG;
  445.   var failedURI = gIoService.newURI("about:livemark-failed", null, null);
  446.   var bms = Cc[BMS_CONTRACTID].getService(Ci.nsINavBookmarksService);
  447.   bms.insertBookmark(aFolderId, failedURI, 0, failedMsg);
  448.   ans.setItemAnnotation(aFolderId, LMANNO_LOADFAILED, true, 0,
  449.                         ans.EXPIRE_NEVER);
  450.  
  451. var gLivemarkService;
  452. function LivemarkService() {
  453.  
  454.   try {
  455.     var prefs = Cc[PS_CONTRACTID].getService(Ci.nsIPrefBranch);
  456.     var livemarkRefresh = 
  457.       prefs.getIntPref("browser.bookmarks.livemark_refresh_seconds");
  458.     // Reset global expiration variable to reflect hidden pref (in ms)
  459.     // with a lower limit of 1 minute (60000 ms)
  460.     gExpiration = Math.max(livemarkRefresh * 1000, 60000);
  461.   } 
  462.   catch (ex) { }
  463.  
  464.   // [ {folderId:, folderURI:, feedURI:, loadGroup:, locked: } ];
  465.   this._livemarks = [];
  466.  
  467.   this._iconURI = gIoService.newURI(LIVEMARK_ICON_URI, null, null);
  468.   this._loading = GetString("bookmarksLivemarkLoading") || DEFAULT_LOAD_MSG;
  469.   this._observerServiceObserver =
  470.     new G_ObserverServiceObserver('xpcom-shutdown',
  471.                                   BindToObject(this._shutdown, this),
  472.                                   true /*only once*/);
  473.   new G_Alarm(BindToObject(this._fireTimer, this), LIVEMARK_TIMEOUT, 
  474.               true /* repeat */);
  475.  
  476.   if (IS_CONTRACTID in Cc)
  477.     this._idleService = Cc[IS_CONTRACTID].getService(Ci.nsIIdleService);
  478.  
  479.   // this is giving a reentrant getService warning in XPCShell. bug 194568.
  480.   this._ans = Cc[AS_CONTRACTID].getService(Ci.nsIAnnotationService);
  481.  
  482.   var livemarks = this._ans.getItemsWithAnnotation(LMANNO_FEEDURI, {});
  483.   for (var i = 0; i < livemarks.length; i++) {
  484.     var feedURI =
  485.       gIoService.newURI(
  486.         this._ans.getItemAnnotation(livemarks[i], LMANNO_FEEDURI),
  487.         null, null
  488.       );
  489.     this._pushLivemark(livemarks[i], feedURI);
  490.   }
  491.  
  492.   this._bms.addObserver(this, false);
  493. }
  494.  
  495. LivemarkService.prototype = {
  496.  
  497.   get _bms() {
  498.     if (!this.__bms)
  499.       this.__bms = Cc[BMS_CONTRACTID].getService(Ci.nsINavBookmarksService);
  500.     return this.__bms;
  501.   },
  502.  
  503.   get _history() {
  504.     if (!this.__history)
  505.       this.__history = Cc[NH_CONTRACTID].getService(Ci.nsINavHistoryService);
  506.     return this.__history;
  507.   },
  508.  
  509.   // returns new length of _livemarks
  510.   _pushLivemark: function LS__pushLivemark(folderId, feedURI) {
  511.     return this._livemarks.push({folderId: folderId, feedURI: feedURI});
  512.   },
  513.  
  514.   _getLivemarkIndex: function LS__getLivemarkIndex(folderId) {
  515.     for (var i=0; i < this._livemarks.length; ++i) {
  516.       if (this._livemarks[i].folderId == folderId)
  517.         return i;
  518.     }
  519.     throw Cr.NS_ERROR_INVALID_ARG;
  520.   },
  521.  
  522.   _shutdown: function LS__shutdown() {
  523.     // remove bookmarks observer
  524.     this._bms.removeObserver(this);
  525.  
  526.     for (var livemark in this._livemarks) {
  527.       if (livemark.loadGroup) 
  528.         livemark.loadGroup.cancel(Cr.NS_BINDING_ABORTED);
  529.     }
  530.   },
  531.  
  532.   _fireTimer: function LS__fireTimer() {
  533.     for (var i=0; i < this._livemarks.length; ++i) {
  534.       this._updateLivemarkChildren(i, false);
  535.     }
  536.   },
  537.  
  538.   deleteLivemarkChildren: function LS_deleteLivemarkChildren(folderId) {
  539.     this._bms.removeFolderChildren(folderId);
  540.   },
  541.  
  542.   insertLivemarkLoadingItem: function LS_insertLivemarkLoading(bms, livemark) {
  543.     var loadingURI = gIoService.newURI("about:livemark-loading", null, null);
  544.     if (!livemark.loadingId || livemark.loadingId == -1)
  545.       livemark.loadingId = bms.insertBookmark(livemark.folderId, loadingURI,
  546.                                               0, this._loading);
  547.   },
  548.  
  549.   _updateLivemarkChildren:
  550.   function LS__updateLivemarkChildren(index, forceUpdate) {
  551.     if (this._livemarks[index].locked)
  552.       return;
  553.  
  554.     var livemark = this._livemarks[index];
  555.     livemark.locked = true;
  556.     try {
  557.       // Check the TTL/expiration on this.  If there isn't one,
  558.       // then we assume it's never been loaded.  We perform this
  559.       // check even when the update is being forced, in case the
  560.       // livemark has somehow never been loaded.
  561.       var exprTime = this._ans.getPageAnnotation(livemark.feedURI,
  562.                                                  LMANNO_EXPIRATION);
  563.       if (!forceUpdate && exprTime > Date.now()) {
  564.         // no need to refresh
  565.         livemark.locked = false;
  566.         return;
  567.       }
  568.  
  569.       // Check the user idle time. If the user isn't using their computer, don't
  570.       // bother updating - save the internet some bandwidth. If we can't
  571.       // get the idle time, assume the user isn't idle.
  572.       var idleTime = 0;
  573.       try {
  574.         idleTime = this._idleService.idleTime;
  575.       } catch (ex) { /* We don't care */ }
  576.       if (idleTime > IDLE_TIMELIMIT)
  577.       {
  578.         livemark.locked = false;
  579.         return;
  580.       }
  581.     }
  582.     catch (ex) {
  583.       // This livemark has never been loaded, since it has no expire time.
  584.     }
  585.  
  586.     var loadgroup;
  587.     try {
  588.       // Create a load group for the request.  This will allow us to
  589.       // automatically keep track of redirects, so we can always
  590.       // cancel the channel.
  591.       loadgroup = Cc[LG_CONTRACTID].createInstance(Ci.nsILoadGroup);
  592.       var uriChannel = gIoService.newChannel(livemark.feedURI.spec, null, null);
  593.       uriChannel.loadGroup = loadgroup;
  594.       uriChannel.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND | 
  595.                               Ci.nsIRequest.VALIDATE_ALWAYS;
  596.       var httpChannel = uriChannel.QueryInterface(Ci.nsIHttpChannel);
  597.       httpChannel.requestMethod = "GET";
  598.       httpChannel.setRequestHeader("X-Moz", "livebookmarks", false);
  599.  
  600.       // Stream the result to the feed parser with this listener
  601.       var listener = new LivemarkLoadListener(livemark);
  602.       this.insertLivemarkLoadingItem(this._bms, livemark);
  603.       httpChannel.asyncOpen(listener, null);
  604.     }
  605.     catch (ex) {
  606.       if (livemark.loadingId != -1) {
  607.         this._bms.removeItem(livemark.loadingId);
  608.         livemark.loadingId = -1;
  609.       }
  610.       MarkLivemarkLoadFailed(livemark.folderId);      
  611.       livemark.locked = false;
  612.       LOG("exception: " + ex);
  613.       throw ex;
  614.     }
  615.     livemark.loadGroup = loadgroup;
  616.   },
  617.  
  618.   createLivemark: function LS_createLivemark(folder, name, siteURI,
  619.                                              feedURI, index) {
  620.     // Don't add livemarks to livemarks
  621.     if (this.isLivemark(folder))
  622.       throw Cr.NS_ERROR_INVALID_ARG;
  623.     var livemarkID = this._createFolder(this._bms, folder, name, siteURI,
  624.                                         feedURI, index);
  625.   
  626.     // kick off http fetch
  627.     this._updateLivemarkChildren(
  628.       this._pushLivemark(livemarkID, feedURI) - 1,
  629.       false
  630.     );
  631.  
  632.     return livemarkID;
  633.   },
  634.  
  635.   createLivemarkFolderOnly:
  636.   function LS_createLivemarkFolderOnly(bms, folder, name, siteURI,
  637.                                        feedURI, index) {
  638.     var livemarkID = this._createFolder(bms, folder, name, siteURI, feedURI,
  639.                                         index);
  640.     this._pushLivemark(livemarkID, feedURI);
  641.     var livemarkIndex = this._getLivemarkIndex(livemarkID);
  642.     var livemark = this._livemarks[livemarkIndex];
  643.     this.insertLivemarkLoadingItem(bms, livemark);
  644.     
  645.     return livemarkID;
  646.   },
  647.  
  648.   _createFolder:
  649.   function LS__createFolder(bms, folder, name, siteURI, feedURI, index) {
  650.     var livemarkID = bms.createFolder(folder, name, index);
  651.     this._bms.setFolderReadonly(livemarkID, true);
  652.  
  653.     // Add an annotation to map the folder id to the livemark feed URI
  654.     this._ans.setItemAnnotation(livemarkID, LMANNO_FEEDURI, feedURI.spec, 0,
  655.                                 this._ans.EXPIRE_NEVER);
  656.  
  657.     if (siteURI) {
  658.       // Add an annotation to map the folder URI to the livemark site URI
  659.       this._ans.setItemAnnotation(livemarkID, LMANNO_SITEURI, siteURI.spec,
  660.                                   0, this._ans.EXPIRE_NEVER);
  661.     }
  662.  
  663.     return livemarkID;
  664.   },
  665.  
  666.   isLivemark: function LS_isLivemark(folder) {
  667.     return this._ans.itemHasAnnotation(folder, LMANNO_FEEDURI);
  668.   },
  669.  
  670.   _ensureLivemark: function LS__ensureLivemark(container) {
  671.     if (!this.isLivemark(container)) 
  672.       throw Cr.NS_ERROR_INVALID_ARG;
  673.   },
  674.  
  675.   /**
  676.   * n.b. -- the body of this method is duplicated in 
  677.   *         /browser/components/places/content/toolbar.xml
  678.   *         to avoid instantiating the livemark service on
  679.   *         startup.
  680.   */
  681.   getSiteURI: function LS_getSiteURI(container) {
  682.     this._ensureLivemark(container);
  683.  
  684.     if (this._ans.itemHasAnnotation(container, LMANNO_SITEURI)) {
  685.       var siteURIString =
  686.         this._ans.getItemAnnotation(container, LMANNO_SITEURI);
  687.  
  688.       return gIoService.newURI(siteURIString, null, null);
  689.     }
  690.     return null;
  691.   },
  692.  
  693.   setSiteURI: function LS_setSiteURI(container, siteURI) {
  694.     this._ensureLivemark(container);
  695.     
  696.     if (!siteURI) {
  697.       this._ans.removeItemAnnotation(container, LMANNO_SITEURI);
  698.       return;
  699.     }
  700.  
  701.     this._ans.setItemAnnotation(container, LMANNO_SITEURI, siteURI.spec,
  702.                                       0, this._ans.EXPIRE_NEVER);
  703.   },
  704.  
  705.   getFeedURI: function LS_getFeedURI(container) {
  706.     try {
  707.       // getItemAnnotation() can throw if there is no annotation
  708.       var feedURIString = this._ans.getItemAnnotation(container,
  709.                                                       LMANNO_FEEDURI);
  710.        
  711.       return gIoService.newURI(feedURIString, null, null);
  712.     }
  713.     catch (ex) {
  714.       // temporary logging, for bug #381894
  715.       LOG("getFeedURI failed: " + ex);
  716.       LOG("feedURIString: " + feedURIString);
  717.     }
  718.     return null;
  719.   },
  720.  
  721.   setFeedURI: function LS_setFeedURI(container, feedURI) {
  722.     if (!feedURI)
  723.       throw Cr.NS_ERROR_INVALID_ARG;
  724.  
  725.     this._ans.setItemAnnotation(container, LMANNO_FEEDURI, feedURI.spec, 0,
  726.                                 this._ans.EXPIRE_NEVER);
  727.  
  728.     // now update our internal table
  729.     var livemarkIndex = this._getLivemarkIndex(container);  
  730.     this._livemarks[livemarkIndex].feedURI = feedURI;
  731.   },
  732.  
  733.   reloadAllLivemarks: function LS_reloadAllLivemarks() {
  734.     for (var i = 0; i < this._livemarks.length; ++i) {
  735.       this._updateLivemarkChildren(i, true);
  736.     }
  737.   },
  738.  
  739.   reloadLivemarkFolder: function LS_reloadLivemarkFolder(folderID) {
  740.     var livemarkIndex = this._getLivemarkIndex(folderID);  
  741.     this._updateLivemarkChildren(livemarkIndex, true);
  742.   },
  743.  
  744.   // nsINavBookmarkObserver
  745.   onBeginUpdateBatch: function() { },
  746.   onEndUpdateBatch: function() { },
  747.   onItemAdded: function() { },
  748.   onItemChanged: function() { },
  749.   onItemVisited: function() { },
  750.   onItemMoved: function() { },
  751.  
  752.   onItemRemoved: function(aItemId, aParentFolder, aIndex) {
  753.     try {
  754.       var livemarkIndex = this._getLivemarkIndex(aItemId);
  755.     }
  756.     catch(ex) {
  757.       // not a livemark
  758.       return;
  759.     }
  760.     var livemark = this._livemarks[livemarkIndex];
  761.  
  762.     var stillInUse = false;
  763.     stillInUse = this._livemarks.some(
  764.                  function(mark) { return mark.feedURI.equals(livemark.feedURI) } 
  765.                  );
  766.     if (!stillInUse) {
  767.       // ??? the code in the C++ had "livemark_expiration" as
  768.       // the second arg... that must be wrong
  769.       this._ans.removePageAnnotation(livemark.feedURI, LMANNO_EXPIRATION);
  770.     }
  771.  
  772.     if (livemark.loadGroup) 
  773.       livemark.loadGroup.cancel(Cr.NS_BINDING_ABORTED);
  774.     this._livemarks.splice(livemarkIndex, 1);
  775.   },
  776.  
  777.   createInstance: function LS_createInstance(outer, iid) {
  778.     if (outer != null)
  779.       throw Cr.NS_ERROR_NO_AGGREGATION;
  780.     return this.QueryInterface(iid);
  781.   },
  782.   
  783.   QueryInterface: function LS_QueryInterface(iid) {
  784.     if (iid.equals(Ci.nsILivemarkService) ||
  785.         iid.equals(Ci.nsIFactory) ||
  786.         iid.equals(Ci.nsINavBookmarkObserver) ||
  787.         iid.equals(Ci.nsISupports))
  788.       return this;
  789.     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  790.   }
  791. };
  792.  
  793. function LivemarkLoadListener(livemark) {
  794.   this._livemark = livemark;
  795.   this._livemark.loadingId = -1;
  796.   this._processor = null;
  797.   this._isAborted = false;
  798.   this._ttl = gExpiration;
  799.   this._ans = Cc[AS_CONTRACTID].getService(Ci.nsIAnnotationService);
  800. }
  801.  
  802. LivemarkLoadListener.prototype = {
  803.  
  804.   abort: function LLL_abort() {
  805.     this._isAborted = true;
  806.   },
  807.  
  808.   get _bms() {
  809.     if (!this.__bms)
  810.       this.__bms = Cc[BMS_CONTRACTID].getService(Ci.nsINavBookmarksService);
  811.     return this.__bms;
  812.   },
  813.  
  814.   get _history() {
  815.     if (!this.__history)
  816.       this.__history = Cc[NH_CONTRACTID].getService(Ci.nsINavHistoryService);
  817.     return this.__history;
  818.   },
  819.  
  820.   // called back from handleResult
  821.   runBatched: function LLL_runBatched(aUserData) {
  822.     var result = aUserData.QueryInterface(Ci.nsIFeedResult);
  823.  
  824.     // We need this to make sure the item links are safe
  825.     var secMan = Cc[SEC_CONTRACTID].getService(Ci.nsIScriptSecurityManager);
  826.       
  827.     // Clear out any child nodes of the livemark folder, since
  828.     // they're about to be replaced.
  829.     var lmService = Cc[LS_CONTRACTID].getService(Ci.nsILivemarkService);
  830.  
  831.     // Enforce well-formedness because the existing code does
  832.     if (!result || !result.doc || result.bozo) {
  833.       if (this._livemark.loadingId != -1) {
  834.         this._bms.removeItem(this._livemark.loadingId);
  835.         this._livemark.loadingId = -1;
  836.       }
  837.       MarkLivemarkLoadFailed(this._livemark.folderId);
  838.       this._ttl = gExpiration;
  839.       throw Cr.NS_ERROR_FAILURE;
  840.     }
  841.  
  842.     this.deleteLivemarkChildren(this._livemark.folderId);
  843.     this._livemark.loadingId = -1;
  844.     // removeItemAnnotation can safely be used even when the anno isn't set
  845.     this._ans.removeItemAnnotation(this._livemark.folderId, LMANNO_LOADFAILED);
  846.     var feed = result.doc.QueryInterface(Ci.nsIFeed);
  847.     // Loop through and check for a link and a title
  848.     // as the old code did
  849.     for (var i = 0; i < feed.items.length; ++i) {
  850.       let entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
  851.       let href = entry.link;
  852.       if (!href)
  853.         continue;
  854.  
  855.       let title = entry.title ? entry.title.plainText() : entry.updated;
  856.       if (!title)
  857.         continue;
  858.  
  859.       try {
  860.         secMan.checkLoadURIStr(this._livemark.feedURI.spec, href.spec,
  861.                                SEC_FLAGS);
  862.       }
  863.       catch(ex) {
  864.         continue;
  865.       }
  866.  
  867.       this.insertLivemarkChild(this._livemark.folderId, href, title);
  868.     }
  869.   },
  870.  
  871.   /**
  872.    * See nsIFeedResultListener.idl
  873.    */
  874.   handleResult: function LLL_handleResult(result) {
  875.     if (this._isAborted) {
  876.       if (this._livemark.loadingId != -1) {
  877.         this._bms.removeItem(this._livemark.loadingId);
  878.         this._livemark.loadingId = -1;
  879.       }
  880.       MarkLivemarkLoadFailed(this._livemark.folderId);
  881.       this._livemark.locked = false;
  882.       return;
  883.     }
  884.     try {
  885.       // The actual work is done in runBatched, see above.
  886.       this._bms.runInBatchMode(this, result);
  887.     }
  888.     finally {
  889.       this._processor.listener = null;
  890.       this._processor = null;
  891.       this._livemark.locked = false;
  892.     }
  893.   },
  894.   
  895.   deleteLivemarkChildren: LivemarkService.prototype.deleteLivemarkChildren,
  896.  
  897.   insertLivemarkChild:
  898.   function LS_insertLivemarkChild(folderId, uri, title) {
  899.     var id = this._bms.insertBookmark(folderId, uri, this._bms.DEFAULT_INDEX,
  900.                                       title);
  901.   },
  902.  
  903.   /**
  904.    * See nsIStreamListener.idl
  905.    */
  906.   onDataAvailable: function LLL_onDataAvailable(request, context, inputStream, 
  907.                                                 sourceOffset, count) {
  908.     this._processor.onDataAvailable(request, context, inputStream,
  909.                                     sourceOffset, count);
  910.   },
  911.   
  912.   /**
  913.    * See nsIRequestObserver.idl
  914.    */
  915.   onStartRequest: function LLL_onStartRequest(request, context) {
  916.     if (this._isAborted)
  917.       throw Cr.NS_ERROR_UNEXPECTED;
  918.  
  919.     var channel = request.QueryInterface(Ci.nsIChannel);
  920.  
  921.     // Parse feed data as it comes in
  922.     this._processor = Cc[FP_CONTRACTID].createInstance(Ci.nsIFeedProcessor);
  923.     this._processor.listener = this;
  924.     this._processor.parseAsync(null, channel.URI);
  925.     
  926.     this._processor.onStartRequest(request, context);
  927.   },
  928.   
  929.   /**
  930.    * See nsIRequestObserver.idl
  931.    */
  932.   onStopRequest: function LLL_onStopRequest(request, context, status) {
  933.     if (!Components.isSuccessCode(status)) {
  934.       // Something went wrong; try to load again in a bit
  935.       this._setResourceTTL(ERROR_EXPIRATION);
  936.       this._isAborted = true;
  937.       if (this._livemark.loadingId != -1) {
  938.         this._bms.removeItem(this._livemark.loadingId);
  939.         this._livemark.loadingId = -1;
  940.       }
  941.       MarkLivemarkLoadFailed(this._livemark.folderId);
  942.       this._livemark.locked = false;
  943.       return;
  944.     }
  945.     // Set an expiration on the livemark, for reloading the data
  946.     try { 
  947.       this._processor.onStopRequest(request, context, status);
  948.  
  949.       // Calculate a new ttl
  950.       var channel = request.QueryInterface(Ci.nsICachingChannel);
  951.       if (channel) {
  952.         var entryInfo = channel.cacheToken.QueryInterface(Ci.nsICacheEntryInfo);
  953.         if (entryInfo) {
  954.           // nsICacheEntryInfo returns value as seconds, 
  955.           // expiresTime stores as ms
  956.           var expiresTime = entryInfo.expirationTime * 1000;
  957.           var nowTime = Date.now();
  958.           
  959.           // note, expiresTime can be 0, see bug #383538
  960.           if (expiresTime > nowTime) {
  961.             this._setResourceTTL(Math.max((expiresTime - nowTime),
  962.                                  gExpiration));
  963.             return;
  964.           }
  965.         }
  966.       }
  967.     }
  968.     catch (ex) { }
  969.     this._setResourceTTL(this._ttl);
  970.   },
  971.  
  972.   _setResourceTTL: function LLL__setResourceTTL(milliseconds) {
  973.     var exptime = Date.now() + milliseconds;
  974.     this._ans.setPageAnnotation(this._livemark.feedURI, LMANNO_EXPIRATION,
  975.                                 exptime, 0,
  976.                                 Ci.nsIAnnotationService.EXPIRE_NEVER);
  977.   },
  978.   
  979.   /**
  980.    * See nsISupports.idl
  981.    */
  982.   QueryInterface: function LLL_QueryInterface(iid) {
  983.     if (iid.equals(Ci.nsIFeedResultListener) ||
  984.         iid.equals(Ci.nsIStreamListener) ||
  985.         iid.equals(Ci.nsIRequestObserver)||
  986.         iid.equals(Ci.nsINavHistoryBatchCallback) ||
  987.         iid.equals(Ci.nsISupports))
  988.       return this;
  989.     throw Cr.NS_ERROR_NO_INTERFACE;
  990.   },
  991. }
  992.  
  993. function GenericComponentFactory(ctor) {
  994.   this._ctor = ctor;
  995. }
  996.  
  997. GenericComponentFactory.prototype = {
  998.  
  999.   _ctor: null,
  1000.  
  1001.   // nsIFactory
  1002.   createInstance: function(outer, iid) {
  1003.     if (outer != null)
  1004.       throw Cr.NS_ERROR_NO_AGGREGATION;
  1005.     return (new this._ctor()).QueryInterface(iid);
  1006.   },
  1007.  
  1008.   // nsISupports
  1009.   QueryInterface: function(iid) {
  1010.     if (iid.equals(Ci.nsIFactory) ||
  1011.         iid.equals(Ci.nsISupports))
  1012.       return this;
  1013.     throw Cr.NS_ERROR_NO_INTERFACE;
  1014.   },
  1015.  
  1016. };
  1017.  
  1018. var Module = {
  1019.   QueryInterface: function(iid) {
  1020.     if (iid.equals(Ci.nsIModule) || 
  1021.         iid.equals(Ci.nsISupports))
  1022.       return this;
  1023.  
  1024.     throw Cr.NS_ERROR_NO_INTERFACE;
  1025.   },
  1026.  
  1027.   getClassObject: function M_getClassObject(cm, cid, iid) {
  1028.     if (!iid.equals(Ci.nsIFactory))
  1029.       throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1030.     if (cid.equals(LS_CLASSID))
  1031.       return new GenericComponentFactory(LivemarkService);
  1032.  
  1033.     throw Cr.NS_ERROR_NO_INTERFACE;
  1034.   },
  1035.  
  1036.   registerSelf: function(cm, file, location, type) {
  1037.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  1038.  
  1039.     cr.registerFactoryLocation(LS_CLASSID, LS_CLASSNAME,
  1040.       LS_CONTRACTID, file, location, type);    
  1041.   },
  1042.  
  1043.   unregisterSelf: function M_unregisterSelf(cm, location, type) {
  1044.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  1045.     cr.unregisterFactoryLocation(LS_CLASSID, location);
  1046.   },
  1047.   
  1048.   canUnload: function M_canUnload(cm) {
  1049.     return true;
  1050.   }
  1051. };
  1052.  
  1053. function NSGetModule(cm, file) {
  1054.   return Module;
  1055. }
  1056.