diff options
Diffstat (limited to 'tools/xo_bundle/components/nsLivemarkService.js')
-rwxr-xr-x | tools/xo_bundle/components/nsLivemarkService.js | 1140 |
1 files changed, 1140 insertions, 0 deletions
diff --git a/tools/xo_bundle/components/nsLivemarkService.js b/tools/xo_bundle/components/nsLivemarkService.js new file mode 100755 index 0000000..62d906d --- /dev/null +++ b/tools/xo_bundle/components/nsLivemarkService.js @@ -0,0 +1,1140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The Original Code is the Places JS Livemark Service. + * + * The Initial Developer of the Original Code is Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Annie Sullivan <annie.sullivan@gmail.com> (C++ author) + * Joe Hughes <joe@retrovirus.com> + * Vladimir Vukicevic <vladimir@pobox.com> + * Masayuki Nakano <masayuki@d-toybox.com> + * Robert Sayre <sayrer@gmail.com> (JS port) + * Phil Ringnalda <philringnalda@gmail.com> + * Marco Bonardo <mak77@bonardo.net> + * Takeshi Ichimaru <ayakawa.m@gmail.com> + * + * Alternatively, the contents of this file may be used under the + * terms of either the GNU General Public License Version 2 or later + * (the "GPL"), or the GNU Lesser General Public License Version 2.1 + * or later (the "LGPL"), in which case the provisions of the GPL or + * the LGPL are applicable instead of those above. If you wish to + * allow use of your version of this file only under the terms of + * either the GPL or the LGPL, and not to allow others to use your + * version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the + * notice and other provisions required by the GPL or the LGPL. If you + * do not delete the provisions above, a recipient may use your + * version of this file under the terms of any one of the MPL, the GPL + * or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; + +//@line 36 "/builds/moz2_slave/linux_build/build/toolkit/components/url-classifier/content/moz/lang.js" + + +/** + * lang.js - Some missing JavaScript language features + */ + +/** + * Partially applies a function to a particular "this object" and zero or + * more arguments. The result is a new function with some arguments of the first + * function pre-filled and the value of |this| "pre-specified". + * + * Remaining arguments specified at call-time are appended to the pre- + * specified ones. + * + * Usage: + * var barMethBound = BindToObject(myFunction, myObj, "arg1", "arg2"); + * barMethBound("arg3", "arg4"); + * + * @param fn {string} Reference to the function to be bound + * + * @param self {object} Specifies the object which |this| should point to + * when the function is run. If the value is null or undefined, it will default + * to the global object. + * + * @returns {function} A partially-applied form of the speficied function. + */ +function BindToObject(fn, self, opt_args) { + var boundargs = fn.boundArgs_ || []; + boundargs = boundargs.concat(Array.slice(arguments, 2, arguments.length)); + + if (fn.boundSelf_) + self = fn.boundSelf_; + if (fn.boundFn_) + fn = fn.boundFn_; + + var newfn = function() { + // Combine the static args and the new args into one big array + var args = boundargs.concat(Array.slice(arguments)); + return fn.apply(self, args); + } + + newfn.boundArgs_ = boundargs; + newfn.boundSelf_ = self; + newfn.boundFn_ = fn; + + return newfn; +} + +/** + * Inherit the prototype methods from one constructor into another. + * + * Usage: + * + * function ParentClass(a, b) { } + * ParentClass.prototype.foo = function(a) { } + * + * function ChildClass(a, b, c) { + * ParentClass.call(this, a, b); + * } + * + * ChildClass.inherits(ParentClass); + * + * var child = new ChildClass("a", "b", "see"); + * child.foo(); // works + * + * In addition, a superclass' implementation of a method can be invoked + * as follows: + * + * ChildClass.prototype.foo = function(a) { + * ChildClass.superClass_.foo.call(this, a); + * // other code + * }; + */ +Function.prototype.inherits = function(parentCtor) { + var tempCtor = function(){}; + tempCtor.prototype = parentCtor.prototype; + this.superClass_ = parentCtor.prototype; + this.prototype = new tempCtor(); +} +//@line 36 "/builds/moz2_slave/linux_build/build/toolkit/components/url-classifier/content/moz/observer.js" + + +// A couple of classes to simplify creating observers. +// +// // Example1: +// +// function doSomething() { ... } +// var observer = new G_ObserverWrapper(topic, doSomething); +// someObj.addObserver(topic, observer); +// +// // Example2: +// +// function doSomething() { ... } +// new G_ObserverServiceObserver("profile-after-change", +// doSomething, +// true /* run only once */); + + +/** + * This class abstracts the admittedly simple boilerplate required of + * an nsIObserver. It saves you the trouble of implementing the + * indirection of your own observe() function. + * + * @param topic String containing the topic the observer will filter for + * + * @param observeFunction Reference to the function to call when the + * observer fires + * + * @constructor + */ +function G_ObserverWrapper(topic, observeFunction) { + this.debugZone = "observer"; + this.topic_ = topic; + this.observeFunction_ = observeFunction; +} + +/** + * XPCOM + */ +G_ObserverWrapper.prototype.QueryInterface = function(iid) { + if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIObserver)) + return this; + throw Components.results.NS_ERROR_NO_INTERFACE; +} + +/** + * Invoked by the thingy being observed + */ +G_ObserverWrapper.prototype.observe = function(subject, topic, data) { + if (topic == this.topic_) + this.observeFunction_(subject, topic, data); +} + + +/** + * This class abstracts the admittedly simple boilerplate required of + * observing an observerservice topic. It implements the indirection + * required, and automatically registers to hear the topic. + * + * @param topic String containing the topic the observer will filter for + * + * @param observeFunction Reference to the function to call when the + * observer fires + * + * @param opt_onlyOnce Boolean indicating if the observer should unregister + * after it has fired + * + * @constructor + */ +function G_ObserverServiceObserver(topic, observeFunction, opt_onlyOnce) { + this.debugZone = "observerserviceobserver"; + this.topic_ = topic; + this.observeFunction_ = observeFunction; + this.onlyOnce_ = !!opt_onlyOnce; + + this.observer_ = new G_ObserverWrapper(this.topic_, + BindToObject(this.observe_, this)); + this.observerService_ = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + this.observerService_.addObserver(this.observer_, this.topic_, false); +} + +/** + * Unregister the observer from the observerservice + */ +G_ObserverServiceObserver.prototype.unregister = function() { + this.observerService_.removeObserver(this.observer_, this.topic_); + this.observerService_ = null; +} + +/** + * Invoked by the observerservice + */ +G_ObserverServiceObserver.prototype.observe_ = function(subject, topic, data) { + this.observeFunction_(subject, topic, data); + if (this.onlyOnce_) + this.unregister(); +} + +//@line 36 "/builds/moz2_slave/linux_build/build/toolkit/components/url-classifier/content/moz/alarm.js" + + +// An Alarm fires a callback after a certain amount of time, or at +// regular intervals. It's a convenient replacement for +// setTimeout/Interval when you don't want to bind to a specific +// window. +// +// The ConditionalAlarm is an Alarm that cancels itself if its callback +// returns a value that type-converts to true. +// +// Example: +// +// function foo() { dump('hi'); }; +// new G_Alarm(foo, 10*1000); // Fire foo in 10 seconds +// new G_Alarm(foo, 10*1000, true /*repeat*/); // Fire foo every 10 seconds +// new G_Alarm(foo, 10*1000, true, 7); // Fire foo every 10 seconds +// // seven times +// new G_ConditionalAlarm(foo, 1000, true); // Fire every sec until foo()==true +// +// // Fire foo every 10 seconds until foo returns true or until it fires seven +// // times, whichever happens first. +// new G_ConditionalAlarm(foo, 10*1000, true /*repeating*/, 7); +// +// TODO: maybe pass an isFinal flag to the callback if they opted to +// set maxTimes and this is the last iteration? + + +/** + * Set an alarm to fire after a given amount of time, or at specific + * intervals. + * + * @param callback Function to call when the alarm fires + * @param delayMS Number indicating the length of the alarm period in ms + * @param opt_repeating Boolean indicating whether this should fire + * periodically + * @param opt_maxTimes Number indicating a maximum number of times to + * repeat (obviously only useful when opt_repeating==true) + */ +function G_Alarm(callback, delayMS, opt_repeating, opt_maxTimes) { + this.debugZone = "alarm"; + this.callback_ = callback; + this.repeating_ = !!opt_repeating; + this.timer_ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + var type = opt_repeating ? + this.timer_.TYPE_REPEATING_SLACK : + this.timer_.TYPE_ONE_SHOT; + this.maxTimes_ = opt_maxTimes ? opt_maxTimes : null; + this.nTimes_ = 0; + + this.observerServiceObserver_ = new G_ObserverServiceObserver( + 'xpcom-shutdown', + BindToObject(this.cancel, this)); + + // Ask the timer to use nsITimerCallback (.notify()) when ready + this.timer_.initWithCallback(this, delayMS, type); +} + +/** + * Cancel this timer + */ +G_Alarm.prototype.cancel = function() { + if (!this.timer_) { + return; + } + + this.timer_.cancel(); + // Break circular reference created between this.timer_ and the G_Alarm + // instance (this) + this.timer_ = null; + this.callback_ = null; + + // We don't need the shutdown observer anymore + this.observerServiceObserver_.unregister(); +} + +/** + * Invoked by the timer when it fires + * + * @param timer Reference to the nsITimer which fired (not currently + * passed along) + */ +G_Alarm.prototype.notify = function(timer) { + // fire callback and save results + var ret = this.callback_(); + + // If they've given us a max number of times to fire, enforce it + this.nTimes_++; + if (this.repeating_ && + typeof this.maxTimes_ == "number" + && this.nTimes_ >= this.maxTimes_) { + this.cancel(); + } else if (!this.repeating_) { + // Clear out the callback closure for TYPE_ONE_SHOT timers + this.cancel(); + } + // We don't cancel/cleanup timers that repeat forever until either + // xpcom-shutdown occurs or cancel() is called explicitly. + + return ret; +} + +G_Alarm.prototype.setDelay = function(delay) { + this.timer_.delay = delay; +} + +/** + * XPCOM cruft + */ +G_Alarm.prototype.QueryInterface = function(iid) { + if (iid.equals(Components.interfaces.nsISupports) || + iid.equals(Components.interfaces.nsITimerCallback)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; +} + + +/** + * An alarm with the additional property that it cancels itself if its + * callback returns true. + * + * For parameter documentation, see G_Alarm + */ +function G_ConditionalAlarm(callback, delayMS, opt_repeating, opt_maxTimes) { + G_Alarm.call(this, callback, delayMS, opt_repeating, opt_maxTimes); + this.debugZone = "conditionalalarm"; +} + +G_ConditionalAlarm.inherits(G_Alarm); + +/** + * Invoked by the timer when it fires + * + * @param timer Reference to the nsITimer which fired (not currently + * passed along) + */ +G_ConditionalAlarm.prototype.notify = function(timer) { + // Call G_Alarm::notify + var rv = G_Alarm.prototype.notify.call(this, timer); + + if (this.repeating_ && rv) { + G_Debug(this, "Callback of a repeating alarm returned true; cancelling."); + this.cancel(); + } +} +//@line 54 "/builds/moz2_slave/linux_build/build/toolkit/components/places/src/nsLivemarkService.js" + +const LS_CLASSID = Components.ID("{dca61eb5-c7cd-4df1-b0fb-d0722baba251}"); +const LS_CLASSNAME = "Livemark Service"; +const LS_CONTRACTID = "@mozilla.org/browser/livemark-service;2"; + +const LMANNO_FEEDURI = "livemark/feedURI"; +const LMANNO_SITEURI = "livemark/siteURI"; +const LMANNO_EXPIRATION = "livemark/expiration"; +const LMANNO_LOADFAILED = "livemark/loadfailed"; +const LMANNO_LOADING = "livemark/loading"; + +const PS_CONTRACTID = "@mozilla.org/preferences-service;1"; +const NH_CONTRACTID = "@mozilla.org/browser/nav-history-service;1"; +const AS_CONTRACTID = "@mozilla.org/browser/annotation-service;1"; +const OS_CONTRACTID = "@mozilla.org/observer-service;1"; +const SB_CONTRACTID = "@mozilla.org/intl/stringbundle;1"; +const IO_CONTRACTID = "@mozilla.org/network/io-service;1"; +const BMS_CONTRACTID = "@mozilla.org/browser/nav-bookmarks-service;1"; +const FAV_CONTRACTID = "@mozilla.org/browser/favicon-service;1"; +const LG_CONTRACTID = "@mozilla.org/network/load-group;1"; +const FP_CONTRACTID = "@mozilla.org/feed-processor;1"; +const SEC_CONTRACTID = "@mozilla.org/scriptsecuritymanager;1"; +const IS_CONTRACTID = "@mozilla.org/widget/idleservice;1"; +const SEC_FLAGS = Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL; + +// Expire livemarks after 1 hour by default +var gExpiration = 3600000; + +// Number of livemarks that are read at once +var gLimitCount = 1; + +// Interval when livemarks are loaded +var gDelayTime = 3; + +// Expire livemarks after 10 minutes on error +const ERROR_EXPIRATION = 600000; + +// Don't check when the user is idle for longer than half an hour +const IDLE_TIMELIMIT = 1800000; + +// We should check for expiration _at least_ every hour +// This cap is used only if the user sets a very high expiration time (>4h) +const MAX_REFRESH_TIME = 3600000; + +/* We don't have strings, so this is currently not used. +const PLACES_BUNDLE_URI = "chrome://places/locale/places.properties"; + +function LOG(str) { + dump("*** " + str + "\n"); +} + +var gStringBundle; +function GetString(name) +{ + try { + if (!gStringBundle) { + var bundleService = Cc[SB_CONTRACTID].getService(); + bundleService = bundleService.QueryInterface(Ci.nsIStringBundleService); + gStringBundle = bundleService.createBundle(PLACES_BUNDLE_URI); + } + + if (gStringBundle) + return gStringBundle.GetStringFromName(name); + } catch (ex) { + LOG("Exception loading string bundle: " + ex.message); + } + + return null; +} +*/ + +function MarkLivemarkLoadFailed(aFolderId) { + var ans = Cc[AS_CONTRACTID].getService(Ci.nsIAnnotationService); + // if it failed before, nothing more to do + if (ans.itemHasAnnotation(aFolderId, LMANNO_LOADFAILED)) + return; + + // removeItemAnnotation can safely be used even when the anno isn't set + ans.removeItemAnnotation(aFolderId, LMANNO_LOADING); + ans.setItemAnnotation(aFolderId, LMANNO_LOADFAILED, true, + 0, ans.EXPIRE_NEVER); +} + +function LivemarkService() { + + try { + var prefs = Cc[PS_CONTRACTID].getService(Ci.nsIPrefBranch); + var livemarkRefresh = + prefs.getIntPref("browser.bookmarks.livemark_refresh_seconds"); + // Reset global expiration variable to reflect hidden pref (in ms) + // with a lower limit of 1 minute (60000 ms) + gExpiration = Math.max(livemarkRefresh * 1000, 60000); + } + catch (ex) { } + + try { + gLimitCount = prefs.getIntPref("browser.bookmarks.livemark_refresh_limit_count"); + if ( gLimitCount < 1 ) gLimitCount = 1; + } + catch (ex) { } + + try { + gDelayTime = prefs.getIntPref("browser.bookmarks.livemark_refresh_delay_time"); + if ( gDelayTime < 1 ) gDelayTime = 1; + } + catch (ex) { } + + // [ {folderId:, folderURI:, feedURI:, loadGroup:, locked: } ]; + this._livemarks = []; + + this._observerServiceObserver = + new G_ObserverServiceObserver('xpcom-shutdown', + BindToObject(this._shutdown, this), + true /*only once*/); + + var livemarks = this._ans.getItemsWithAnnotation(LMANNO_FEEDURI, {}); + for (var i = 0; i < livemarks.length; i++) { + var feedURI = this._ios.newURI(this._ans.getItemAnnotation(livemarks[i], + LMANNO_FEEDURI), + null, null); + this._pushLivemark(livemarks[i], feedURI); + } + + this._bms.addObserver(this, false); +} + +LivemarkService.prototype = { + + get _bms() { + if (!this.__bms) + this.__bms = Cc[BMS_CONTRACTID].getService(Ci.nsINavBookmarksService); + return this.__bms; + }, + + get _history() { + if (!this.__history) + this.__history = Cc[NH_CONTRACTID].getService(Ci.nsINavHistoryService); + return this.__history; + }, + + get _ans() { + if (!this.__ans) + this.__ans = Cc[AS_CONTRACTID].getService(Ci.nsIAnnotationService); + return this.__ans; + }, + + get _ios() { + if (!this.__ios) + this.__ios = Cc[IO_CONTRACTID].getService(Ci.nsIIOService); + return this.__ios; + }, + + get _idleService() { + if (!(IS_CONTRACTID in Cc)) + return null; + if (!this.__idleService) + this.__idleService = Cc[IS_CONTRACTID].getService(Ci.nsIIdleService); + return this.__idleService; + }, + + _updateTimer: null, + start: function LS_start() { + if (this._updateTimer) + return; + // start is called in delayed startup, 5s after browser startup + // we do a first check of the livemarks here, next checks will be on timer + // browser start => 5s => this.start() => check => refresh_time => check + this._checkAllLivemarks(); + }, + + stopUpdateLivemarks: function LS_stopUpdateLivemarks() { + for (var livemark in this._livemarks) { + if (livemark.loadGroup) + livemark.loadGroup.cancel(Components.results.NS_BINDING_ABORTED); + } + // kill timer + if (this._updateTimer) { + this._updateTimer.cancel(); + this._updateTimer = null; + } + }, + + _pushLivemark: function LS__pushLivemark(aFolderId, aFeedURI) { + // returns new length of _livemarks + return this._livemarks.push({folderId: aFolderId, feedURI: aFeedURI}); + }, + + _getLivemarkIndex: function LS__getLivemarkIndex(aFolderId) { + for (var i = 0; i < this._livemarks.length; ++i) { + if (this._livemarks[i].folderId == aFolderId) + return i; + } + throw Cr.NS_ERROR_INVALID_ARG; + }, + + _shutdown: function LS__shutdown() { + // remove bookmarks observer + this._bms.removeObserver(this); + + // stop to update livemarks + this.stopUpdateLivemarks(); + }, + + // We try to distribute the load of the livemark update. + // load gLimitCount Livemarks per gDelayTime sec. + _nextUpdateStartIndex : 0, + _checkAllLivemarks: function LS__checkAllLivemarks() { + var startNo = this._nextUpdateStartIndex; + var count = 0; + for (var i = startNo; (i < this._livemarks.length) && (count < gLimitCount); ++i ) { + // check if livemarks are expired, update if needed + try { + if (this._updateLivemarkChildren(i, false)) count++; + } + catch (ex) { } + this._nextUpdateStartIndex = i+1; + } + if ( this._nextUpdateStartIndex >= this._livemarks.length ) { + // all livemarks are checked, sleeping until next period + this._nextUpdateStartIndex = 0; + var refresh_time = Math.min(Math.floor(gExpiration / 4), MAX_REFRESH_TIME); + this._updateTimer = new G_Alarm(BindToObject(this._checkAllLivemarks, this), + refresh_time); + } else { + // wait gDelayTime sec. + this._updateTimer = new G_Alarm(BindToObject(this._checkAllLivemarks, this), + gDelayTime*1000); + } + }, + + deleteLivemarkChildren: function LS_deleteLivemarkChildren(aFolderId) { + this._bms.removeFolderChildren(aFolderId); + }, + + _updateLivemarkChildren: + function LS__updateLivemarkChildren(aIndex, aForceUpdate) { + if (this._livemarks[aIndex].locked) + return false; + + var livemark = this._livemarks[aIndex]; + livemark.locked = true; + try { + // Check the TTL/expiration on this. If there isn't one, + // then we assume it's never been loaded. We perform this + // check even when the update is being forced, in case the + // livemark has somehow never been loaded. + var expireTime = this._ans.getItemAnnotation(livemark.folderId, + LMANNO_EXPIRATION); + if (!aForceUpdate && expireTime > Date.now()) { + // no need to refresh + livemark.locked = false; + return false; + } + + // Check the user idle time. + // If the user is away from the computer, don't bother updating, + // so we save some bandwidth. + // If we can't get the idle time, assume the user is not idle. + var idleTime = 0; + try { + idleTime = this._idleService.idleTime; + } + catch (ex) { /* We don't care */ } + if (idleTime > IDLE_TIMELIMIT) { + livemark.locked = false; + return false; + } + } + catch (ex) { + // This livemark has never been loaded, since it has no expire time. + } + + var loadgroup; + try { + // Create a load group for the request. This will allow us to + // automatically keep track of redirects, so we can always + // cancel the channel. + loadgroup = Cc[LG_CONTRACTID].createInstance(Ci.nsILoadGroup); + var uriChannel = this._ios.newChannel(livemark.feedURI.spec, null, null); + uriChannel.loadGroup = loadgroup; + uriChannel.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND | + Ci.nsIRequest.VALIDATE_ALWAYS; + var httpChannel = uriChannel.QueryInterface(Ci.nsIHttpChannel); + httpChannel.requestMethod = "GET"; + httpChannel.setRequestHeader("X-Moz", "livebookmarks", false); + + // Stream the result to the feed parser with this listener + var listener = new LivemarkLoadListener(livemark); + // removeItemAnnotation can safely be used even when the anno isn't set + this._ans.removeItemAnnotation(livemark.folderId, LMANNO_LOADFAILED); + this._ans.setItemAnnotation(livemark.folderId, LMANNO_LOADING, true, + 0, this._ans.EXPIRE_NEVER); + httpChannel.notificationCallbacks = listener; + httpChannel.asyncOpen(listener, null); + } + catch (ex) { + MarkLivemarkLoadFailed(livemark.folderId); + livemark.locked = false; + return false; + } + livemark.loadGroup = loadgroup; + return true; + }, + + createLivemark: function LS_createLivemark(aParentId, aName, aSiteURI, + aFeedURI, aIndex) { + if (!aParentId || !aFeedURI) + throw Cr.NS_ERROR_INVALID_ARG; + + // Don't add livemarks to livemarks + if (this.isLivemark(aParentId)) + throw Cr.NS_ERROR_INVALID_ARG; + + var folderId = this._createFolder(aParentId, aName, aSiteURI, + aFeedURI, aIndex); + + // do a first update of the livemark children + this._updateLivemarkChildren(this._pushLivemark(folderId, aFeedURI) - 1, + false); + + return folderId; + }, + + createLivemarkFolderOnly: + function LS_createLivemarkFolderOnly(aParentId, aName, aSiteURI, + aFeedURI, aIndex) { + if (aParentId < 1 || !aFeedURI) + throw Cr.NS_ERROR_INVALID_ARG; + + // Don't add livemarks to livemarks + if (this.isLivemark(aParentId)) + throw Cr.NS_ERROR_INVALID_ARG; + + var folderId = this._createFolder(aParentId, aName, aSiteURI, + aFeedURI, aIndex); + + var livemarkIndex = this._pushLivemark(folderId, aFeedURI) - 1; + var livemark = this._livemarks[livemarkIndex]; + return folderId; + }, + + _createFolder: + function LS__createFolder(aParentId, aName, aSiteURI, aFeedURI, aIndex) { + var folderId = this._bms.createFolder(aParentId, aName, aIndex); + this._bms.setFolderReadonly(folderId, true); + + // Add an annotation to map the folder id to the livemark feed URI + this._ans.setItemAnnotation(folderId, LMANNO_FEEDURI, aFeedURI.spec, + 0, this._ans.EXPIRE_NEVER); + + if (aSiteURI) { + // Add an annotation to map the folder URI to the livemark site URI + this._setSiteURISecure(folderId, aFeedURI, aSiteURI); + } + + return folderId; + }, + + isLivemark: function LS_isLivemark(aFolderId) { + if (aFolderId < 1) + throw Cr.NS_ERROR_INVALID_ARG; + return this._ans.itemHasAnnotation(aFolderId, LMANNO_FEEDURI); + }, + + _ensureLivemark: function LS__ensureLivemark(aFolderId) { + if (!this.isLivemark(aFolderId)) + throw Cr.NS_ERROR_INVALID_ARG; + }, + + getSiteURI: function LS_getSiteURI(aFolderId) { + this._ensureLivemark(aFolderId); + + if (this._ans.itemHasAnnotation(aFolderId, LMANNO_SITEURI)) { + var siteURIString = + this._ans.getItemAnnotation(aFolderId, LMANNO_SITEURI); + + return this._ios.newURI(siteURIString, null, null); + } + return null; + }, + + setSiteURI: function LS_setSiteURI(aFolderId, aSiteURI) { + this._ensureLivemark(aFolderId); + + if (!aSiteURI) { + this._ans.removeItemAnnotation(aFolderId, LMANNO_SITEURI); + return; + } + + var livemarkIndex = this._getLivemarkIndex(aFolderId); + var livemark = this._livemarks[livemarkIndex]; + this._setSiteURISecure(aFolderId, livemark.feedURI, aSiteURI); + }, + + _setSiteURISecure: + function LS__setSiteURISecure(aFolderId, aFeedURI, aSiteURI) { + var secMan = Cc[SEC_CONTRACTID].getService(Ci.nsIScriptSecurityManager); + var feedPrincipal = secMan.getCodebasePrincipal(aFeedURI); + try { + secMan.checkLoadURIWithPrincipal(feedPrincipal, aSiteURI, SEC_FLAGS); + } + catch (e) { + return; + } + this._ans.setItemAnnotation(aFolderId, LMANNO_SITEURI, aSiteURI.spec, + 0, this._ans.EXPIRE_NEVER); + }, + + getFeedURI: function LS_getFeedURI(aFolderId) { + if (this._ans.itemHasAnnotation(aFolderId, LMANNO_FEEDURI)) + return this._ios.newURI(this._ans.getItemAnnotation(aFolderId, + LMANNO_FEEDURI), + null, null); + return null; + }, + + setFeedURI: function LS_setFeedURI(aFolderId, aFeedURI) { + if (!aFeedURI) + throw Cr.NS_ERROR_INVALID_ARG; + + this._ans.setItemAnnotation(aFolderId, LMANNO_FEEDURI, aFeedURI.spec, 0, + this._ans.EXPIRE_NEVER); + + // now update our internal table + var livemarkIndex = this._getLivemarkIndex(aFolderId); + this._livemarks[livemarkIndex].feedURI = aFeedURI; + }, + + reloadAllLivemarks: function LS_reloadAllLivemarks() { + for (var i = 0; i < this._livemarks.length; ++i) { + this._updateLivemarkChildren(i, true); + } + }, + + reloadLivemarkFolder: function LS_reloadLivemarkFolder(aFolderId) { + var livemarkIndex = this._getLivemarkIndex(aFolderId); + this._updateLivemarkChildren(livemarkIndex, true); + }, + + // nsINavBookmarkObserver + onBeginUpdateBatch: function() { }, + onEndUpdateBatch: function() { }, + onItemAdded: function() { }, + onItemChanged: function() { }, + onItemVisited: function() { }, + onItemMoved: function() { }, + + onItemRemoved: function(aItemId, aParentId, aIndex) { + // we don't need to remove annotations since itemAnnotations + // are already removed with the bookmark + try { + var livemarkIndex = this._getLivemarkIndex(aItemId); + } + catch(ex) { + // not a livemark + return; + } + var livemark = this._livemarks[livemarkIndex]; + + // remove the livemark from the update array + this._livemarks.splice(livemarkIndex, 1); + + if (livemark.loadGroup) + livemark.loadGroup.cancel(Components.results.NS_BINDING_ABORTED); + }, + + createInstance: function LS_createInstance(aOuter, aIID) { + if (aOuter != null) + throw Cr.NS_ERROR_NO_AGGREGATION; + return this.QueryInterface(aIID); + }, + + QueryInterface: function LS_QueryInterface(aIID) { + if (aIID.equals(Ci.nsILivemarkService) || + aIID.equals(Ci.nsIFactory) || + aIID.equals(Ci.nsINavBookmarkObserver) || + aIID.equals(Ci.nsISupports)) + return this; + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + } +}; + +function LivemarkLoadListener(aLivemark) { + this._livemark = aLivemark; + this._processor = null; + this._isAborted = false; + this._ttl = gExpiration; +} + +LivemarkLoadListener.prototype = { + + abort: function LLL_abort() { + this._isAborted = true; + }, + + get _bms() { + if (!this.__bms) + this.__bms = Cc[BMS_CONTRACTID].getService(Ci.nsINavBookmarksService); + return this.__bms; + }, + + get _history() { + if (!this.__history) + this.__history = Cc[NH_CONTRACTID].getService(Ci.nsINavHistoryService); + return this.__history; + }, + + get _ans() { + if (!this.__ans) + this.__ans = Cc[AS_CONTRACTID].getService(Ci.nsIAnnotationService); + return this.__ans; + }, + + // called back from handleResult + runBatched: function LLL_runBatched(aUserData) { + var result = aUserData.QueryInterface(Ci.nsIFeedResult); + + // We need this to make sure the item links are safe + var secMan = Cc[SEC_CONTRACTID].getService(Ci.nsIScriptSecurityManager); + var feedPrincipal = secMan.getCodebasePrincipal(this._livemark.feedURI); + + var lmService = Cc[LS_CONTRACTID].getService(Ci.nsILivemarkService); + + // Enforce well-formedness because the existing code does + if (!result || !result.doc || result.bozo) { + MarkLivemarkLoadFailed(this._livemark.folderId); + this._ttl = gExpiration; + throw Cr.NS_ERROR_FAILURE; + } + + // Clear out any child nodes of the livemark folder, since + // they're about to be replaced. + this.deleteLivemarkChildren(this._livemark.folderId); + var feed = result.doc.QueryInterface(Ci.nsIFeed); + if (feed.link) { + var oldSiteURI = lmService.getSiteURI(this._livemark.folderId); + if (!oldSiteURI || !feed.link.equals(oldSiteURI)) + lmService.setSiteURI(this._livemark.folderId, feed.link); + } + // Loop through and check for a link and a title + // as the old code did + for (var i = 0; i < feed.items.length; ++i) { + let entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry); + let href = entry.link; + if (!href) + continue; + + let title = entry.title ? entry.title.plainText() : ""; + + try { + secMan.checkLoadURIWithPrincipal(feedPrincipal, href, SEC_FLAGS); + } + catch(ex) { + continue; + } + + this.insertLivemarkChild(this._livemark.folderId, href, title); + } + }, + + /** + * See nsIFeedResultListener.idl + */ + handleResult: function LLL_handleResult(aResult) { + if (this._isAborted) { + MarkLivemarkLoadFailed(this._livemark.folderId); + this._livemark.locked = false; + return; + } + try { + // The actual work is done in runBatched, see above. + this._bms.runInBatchMode(this, aResult); + } + finally { + this._processor.listener = null; + this._processor = null; + this._livemark.locked = false; + this._ans.removeItemAnnotation(this._livemark.folderId, LMANNO_LOADING); + } + }, + + deleteLivemarkChildren: LivemarkService.prototype.deleteLivemarkChildren, + + insertLivemarkChild: + function LS_insertLivemarkChild(aFolderId, aUri, aTitle) { + this._bms.insertBookmark(aFolderId, aUri, this._bms.DEFAULT_INDEX, aTitle); + }, + + /** + * See nsIStreamListener.idl + */ + onDataAvailable: function LLL_onDataAvailable(aRequest, aContext, aInputStream, + aSourceOffset, aCount) { + if (this._processor) + this._processor.onDataAvailable(aRequest, aContext, aInputStream, + aSourceOffset, aCount); + }, + + /** + * See nsIRequestObserver.idl + */ + onStartRequest: function LLL_onStartRequest(aRequest, aContext) { + if (this._isAborted) + throw Cr.NS_ERROR_UNEXPECTED; + + var channel = aRequest.QueryInterface(Ci.nsIChannel); + + // Parse feed data as it comes in + this._processor = Cc[FP_CONTRACTID].createInstance(Ci.nsIFeedProcessor); + this._processor.listener = this; + this._processor.parseAsync(null, channel.URI); + + this._processor.onStartRequest(aRequest, aContext); + }, + + /** + * See nsIRequestObserver.idl + */ + onStopRequest: function LLL_onStopRequest(aRequest, aContext, aStatus) { + if (!Components.isSuccessCode(aStatus)) { + // Something went wrong, try to load again in a bit + this._setResourceTTL(ERROR_EXPIRATION); + this._isAborted = true; + MarkLivemarkLoadFailed(this._livemark.folderId); + this._livemark.locked = false; + return; + } + // Set an expiration on the livemark, for reloading the data + try { + if (this._processor) + this._processor.onStopRequest(aRequest, aContext, aStatus); + + // Calculate a new ttl + var channel = aRequest.QueryInterface(Ci.nsICachingChannel); + if (channel) { + var entryInfo = channel.cacheToken.QueryInterface(Ci.nsICacheEntryInfo); + if (entryInfo) { + // nsICacheEntryInfo returns value as seconds, + // expireTime stores as milliseconds + var expireTime = entryInfo.expirationTime * 1000; + var nowTime = Date.now(); + + // note, expireTime can be 0, see bug 383538 + if (expireTime > nowTime) { + this._setResourceTTL(Math.max((expireTime - nowTime), + gExpiration)); + return; + } + } + } + } + catch (ex) { } + this._setResourceTTL(this._ttl); + }, + + _setResourceTTL: function LLL__setResourceTTL(aMilliseconds) { + var expireTime = Date.now() + aMilliseconds; + this._ans.setItemAnnotation(this._livemark.folderId, LMANNO_EXPIRATION, + expireTime, 0, + Ci.nsIAnnotationService.EXPIRE_NEVER); + }, + + /** + * See nsIBadCertListener2 + */ + notifyCertProblem: function LLL_certProblem(aSocketInfo, aStatus, aTargetSite) { + return true; + }, + + /** + * See nsISSLErrorListener + */ + notifySSLError: function LLL_SSLError(aSocketInfo, aError, aTargetSite) { + return true; + }, + + /** + * See nsIInterfaceRequestor + */ + getInterface: function LLL_getInterface(aIID) { + return this.QueryInterface(aIID); + }, + + /** + * See nsISupports.idl + */ + QueryInterface: function LLL_QueryInterface(aIID) { + if (aIID.equals(Ci.nsIFeedResultListener) || + aIID.equals(Ci.nsIStreamListener) || + aIID.equals(Ci.nsIRequestObserver)|| + aIID.equals(Ci.nsINavHistoryBatchCallback) || + aIID.equals(Ci.nsIBadCertListener2) || + aIID.equals(Ci.nsISSLErrorListener) || + aIID.equals(Ci.nsIInterfaceRequestor) || + aIID.equals(Ci.nsISupports)) + return this; + throw Cr.NS_ERROR_NO_INTERFACE; + }, +} + +function GenericComponentFactory(aCtor) { + this._ctor = aCtor; +} + +GenericComponentFactory.prototype = { + + _ctor: null, + + // nsIFactory + createInstance: function(aOuter, aIID) { + if (aOuter != null) + throw Cr.NS_ERROR_NO_AGGREGATION; + return (new this._ctor()).QueryInterface(aIID); + }, + + // nsISupports + QueryInterface: function(aIID) { + if (aIID.equals(Ci.nsIFactory) || + aIID.equals(Ci.nsISupports)) + return this; + throw Cr.NS_ERROR_NO_INTERFACE; + }, + +}; + +var Module = { + QueryInterface: function(aIID) { + if (aIID.equals(Ci.nsIModule) || + aIID.equals(Ci.nsISupports)) + return this; + + throw Cr.NS_ERROR_NO_INTERFACE; + }, + + getClassObject: function M_getClassObject(aCompMgr, aCID, aIID) { + if (!aIID.equals(Ci.nsIFactory)) + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + if (aCID.equals(LS_CLASSID)) + return new GenericComponentFactory(LivemarkService); + + throw Cr.NS_ERROR_NO_INTERFACE; + }, + + registerSelf: function(aCompMgr, aFile, aLocation, aType) { + var cr = aCompMgr.QueryInterface(Ci.nsIComponentRegistrar); + + cr.registerFactoryLocation(LS_CLASSID, LS_CLASSNAME, + LS_CONTRACTID, aFile, aLocation, aType); + }, + + unregisterSelf: function M_unregisterSelf(aCompMgr, aLocation, aType) { + var cr = aCompMgr.QueryInterface(Ci.nsIComponentRegistrar); + cr.unregisterFactoryLocation(LS_CLASSID, aLocation); + }, + + canUnload: function M_canUnload(aCompMgr) { + return true; + } +}; + +function NSGetModule(aCompMgr, aFile) { + return Module; +} |