toolkit/components/places/nsLivemarkService.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 const Cc = Components.classes;
michael@0 6 const Ci = Components.interfaces;
michael@0 7 const Cr = Components.results;
michael@0 8 const Cu = Components.utils;
michael@0 9
michael@0 10 ////////////////////////////////////////////////////////////////////////////////
michael@0 11 //// Modules
michael@0 12
michael@0 13 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 14 Cu.import("resource://gre/modules/Services.jsm");
michael@0 15 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
michael@0 16 "resource://gre/modules/PlacesUtils.jsm");
michael@0 17 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
michael@0 18 "resource://gre/modules/NetUtil.jsm");
michael@0 19 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
michael@0 20 "resource://gre/modules/Promise.jsm");
michael@0 21 XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
michael@0 22 "resource://gre/modules/Deprecated.jsm");
michael@0 23
michael@0 24 ////////////////////////////////////////////////////////////////////////////////
michael@0 25 //// Services
michael@0 26
michael@0 27 XPCOMUtils.defineLazyServiceGetter(this, "secMan",
michael@0 28 "@mozilla.org/scriptsecuritymanager;1",
michael@0 29 "nsIScriptSecurityManager");
michael@0 30 XPCOMUtils.defineLazyGetter(this, "asyncHistory", function () {
michael@0 31 // Lazily add an history observer when it's actually needed.
michael@0 32 PlacesUtils.history.addObserver(PlacesUtils.livemarks, true);
michael@0 33 return Cc["@mozilla.org/browser/history;1"].getService(Ci.mozIAsyncHistory);
michael@0 34 });
michael@0 35
michael@0 36 ////////////////////////////////////////////////////////////////////////////////
michael@0 37 //// Constants
michael@0 38
michael@0 39 // Security flags for checkLoadURIWithPrincipal.
michael@0 40 const SEC_FLAGS = Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL;
michael@0 41
michael@0 42 // Delay between reloads of consecute livemarks.
michael@0 43 const RELOAD_DELAY_MS = 500;
michael@0 44 // Expire livemarks after this time.
michael@0 45 const EXPIRE_TIME_MS = 3600000; // 1 hour.
michael@0 46 // Expire livemarks after this time on error.
michael@0 47 const ONERROR_EXPIRE_TIME_MS = 300000; // 5 minutes.
michael@0 48
michael@0 49 ////////////////////////////////////////////////////////////////////////////////
michael@0 50 //// LivemarkService
michael@0 51
michael@0 52 function LivemarkService()
michael@0 53 {
michael@0 54 // Cleanup on shutdown.
michael@0 55 Services.obs.addObserver(this, PlacesUtils.TOPIC_SHUTDOWN, true);
michael@0 56
michael@0 57 // Observe bookmarks and history, but don't init the services just for that.
michael@0 58 PlacesUtils.addLazyBookmarkObserver(this, true);
michael@0 59
michael@0 60 // Asynchronously build the livemarks cache.
michael@0 61 this._ensureAsynchronousCache();
michael@0 62 }
michael@0 63
michael@0 64 LivemarkService.prototype = {
michael@0 65 // Cache of Livemark objects, hashed by bookmarks folder ids.
michael@0 66 _livemarks: {},
michael@0 67 // Hash associating guids to bookmarks folder ids.
michael@0 68 _guids: {},
michael@0 69
michael@0 70 get _populateCacheSQL()
michael@0 71 {
michael@0 72 function getAnnoSQLFragment(aAnnoParam) {
michael@0 73 return "SELECT a.content "
michael@0 74 + "FROM moz_items_annos a "
michael@0 75 + "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
michael@0 76 + "WHERE a.item_id = b.id "
michael@0 77 + "AND n.name = " + aAnnoParam;
michael@0 78 }
michael@0 79
michael@0 80 return "SELECT b.id, b.title, b.parent, b.position, b.guid, b.lastModified, "
michael@0 81 + "(" + getAnnoSQLFragment(":feedURI_anno") + ") AS feedURI, "
michael@0 82 + "(" + getAnnoSQLFragment(":siteURI_anno") + ") AS siteURI "
michael@0 83 + "FROM moz_bookmarks b "
michael@0 84 + "JOIN moz_items_annos a ON a.item_id = b.id "
michael@0 85 + "JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id "
michael@0 86 + "WHERE b.type = :folder_type "
michael@0 87 + "AND n.name = :feedURI_anno ";
michael@0 88 },
michael@0 89
michael@0 90 _ensureAsynchronousCache: function LS__ensureAsynchronousCache()
michael@0 91 {
michael@0 92 let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
michael@0 93 .DBConnection;
michael@0 94 let stmt = db.createAsyncStatement(this._populateCacheSQL);
michael@0 95 stmt.params.folder_type = Ci.nsINavBookmarksService.TYPE_FOLDER;
michael@0 96 stmt.params.feedURI_anno = PlacesUtils.LMANNO_FEEDURI;
michael@0 97 stmt.params.siteURI_anno = PlacesUtils.LMANNO_SITEURI;
michael@0 98
michael@0 99 let livemarkSvc = this;
michael@0 100 this._pendingStmt = stmt.executeAsync({
michael@0 101 handleResult: function LS_handleResult(aResults)
michael@0 102 {
michael@0 103 for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
michael@0 104 let id = row.getResultByName("id");
michael@0 105 let siteURL = row.getResultByName("siteURI");
michael@0 106 let guid = row.getResultByName("guid");
michael@0 107 livemarkSvc._livemarks[id] =
michael@0 108 new Livemark({ id: id,
michael@0 109 guid: guid,
michael@0 110 title: row.getResultByName("title"),
michael@0 111 parentId: row.getResultByName("parent"),
michael@0 112 index: row.getResultByName("position"),
michael@0 113 lastModified: row.getResultByName("lastModified"),
michael@0 114 feedURI: NetUtil.newURI(row.getResultByName("feedURI")),
michael@0 115 siteURI: siteURL ? NetUtil.newURI(siteURL) : null,
michael@0 116 });
michael@0 117 livemarkSvc._guids[guid] = id;
michael@0 118 }
michael@0 119 },
michael@0 120 handleError: function LS_handleError(aErr)
michael@0 121 {
michael@0 122 Cu.reportError("AsyncStmt error (" + aErr.result + "): '" + aErr.message);
michael@0 123 },
michael@0 124 handleCompletion: function LS_handleCompletion() {
michael@0 125 livemarkSvc._pendingStmt = null;
michael@0 126 }
michael@0 127 });
michael@0 128 stmt.finalize();
michael@0 129 },
michael@0 130
michael@0 131 _onCacheReady: function LS__onCacheReady(aCallback)
michael@0 132 {
michael@0 133 if (this._pendingStmt) {
michael@0 134 // The cache is still being populated, so enqueue the job to the Storage
michael@0 135 // async thread. Ideally this should just dispatch a runnable to it,
michael@0 136 // that would call back on the main thread, but bug 608142 made that
michael@0 137 // impossible. Thus just enqueue the cheapest query possible.
michael@0 138 let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
michael@0 139 .DBConnection;
michael@0 140 let stmt = db.createAsyncStatement("PRAGMA encoding");
michael@0 141 stmt.executeAsync({
michael@0 142 handleError: function () {},
michael@0 143 handleResult: function () {},
michael@0 144 handleCompletion: function ETAT_handleCompletion()
michael@0 145 {
michael@0 146 aCallback();
michael@0 147 }
michael@0 148 });
michael@0 149 stmt.finalize();
michael@0 150 }
michael@0 151 else {
michael@0 152 // The callbacks should always be enqueued per the interface.
michael@0 153 // Just enque on the main thread.
michael@0 154 Services.tm.mainThread.dispatch(aCallback, Ci.nsIThread.DISPATCH_NORMAL);
michael@0 155 }
michael@0 156 },
michael@0 157
michael@0 158 _reloading: false,
michael@0 159 _startReloadTimer: function LS__startReloadTimer()
michael@0 160 {
michael@0 161 if (this._reloadTimer) {
michael@0 162 this._reloadTimer.cancel();
michael@0 163 }
michael@0 164 else {
michael@0 165 this._reloadTimer = Cc["@mozilla.org/timer;1"]
michael@0 166 .createInstance(Ci.nsITimer);
michael@0 167 }
michael@0 168 this._reloading = true;
michael@0 169 this._reloadTimer.initWithCallback(this._reloadNextLivemark.bind(this),
michael@0 170 RELOAD_DELAY_MS,
michael@0 171 Ci.nsITimer.TYPE_ONE_SHOT);
michael@0 172 },
michael@0 173
michael@0 174 //////////////////////////////////////////////////////////////////////////////
michael@0 175 //// nsIObserver
michael@0 176
michael@0 177 observe: function LS_observe(aSubject, aTopic, aData)
michael@0 178 {
michael@0 179 if (aTopic == PlacesUtils.TOPIC_SHUTDOWN) {
michael@0 180 if (this._pendingStmt) {
michael@0 181 this._pendingStmt.cancel();
michael@0 182 this._pendingStmt = null;
michael@0 183 // Initialization never finished, so just bail out.
michael@0 184 return;
michael@0 185 }
michael@0 186
michael@0 187 if (this._reloadTimer) {
michael@0 188 this._reloading = false;
michael@0 189 this._reloadTimer.cancel();
michael@0 190 delete this._reloadTimer;
michael@0 191 }
michael@0 192
michael@0 193 // Stop any ongoing update.
michael@0 194 for each (let livemark in this._livemarks) {
michael@0 195 livemark.terminate();
michael@0 196 }
michael@0 197 this._livemarks = {};
michael@0 198 }
michael@0 199 },
michael@0 200
michael@0 201 //////////////////////////////////////////////////////////////////////////////
michael@0 202 //// mozIAsyncLivemarks
michael@0 203
michael@0 204 addLivemark: function LS_addLivemark(aLivemarkInfo,
michael@0 205 aLivemarkCallback)
michael@0 206 {
michael@0 207 // Must provide at least non-null parentId, index and feedURI.
michael@0 208 if (!aLivemarkInfo ||
michael@0 209 ("parentId" in aLivemarkInfo && aLivemarkInfo.parentId < 1) ||
michael@0 210 !("index" in aLivemarkInfo) || aLivemarkInfo.index < Ci.nsINavBookmarksService.DEFAULT_INDEX ||
michael@0 211 !(aLivemarkInfo.feedURI instanceof Ci.nsIURI) ||
michael@0 212 (aLivemarkInfo.siteURI && !(aLivemarkInfo.siteURI instanceof Ci.nsIURI)) ||
michael@0 213 (aLivemarkInfo.guid && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid))) {
michael@0 214 throw Cr.NS_ERROR_INVALID_ARG;
michael@0 215 }
michael@0 216
michael@0 217 if (aLivemarkCallback) {
michael@0 218 Deprecated.warning("Passing a callback to Livermarks methods is deprecated. " +
michael@0 219 "Please use the returned promise instead.",
michael@0 220 "https://developer.mozilla.org/docs/Mozilla/JavaScript_code_modules/Promise.jsm");
michael@0 221 }
michael@0 222
michael@0 223 // The addition is done synchronously due to the fact importExport service
michael@0 224 // and JSON backups require that. The notification is async though.
michael@0 225 // Once bookmarks are async, this may be properly fixed.
michael@0 226 let deferred = Promise.defer();
michael@0 227 let addLivemarkEx = null;
michael@0 228 let livemark = null;
michael@0 229 try {
michael@0 230 // Disallow adding a livemark inside another livemark.
michael@0 231 if (aLivemarkInfo.parentId in this._livemarks) {
michael@0 232 throw new Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
michael@0 233 }
michael@0 234
michael@0 235 // Don't pass unexpected input data to the livemark constructor.
michael@0 236 livemark = new Livemark({ title: aLivemarkInfo.title
michael@0 237 , parentId: aLivemarkInfo.parentId
michael@0 238 , index: aLivemarkInfo.index
michael@0 239 , feedURI: aLivemarkInfo.feedURI
michael@0 240 , siteURI: aLivemarkInfo.siteURI
michael@0 241 , guid: aLivemarkInfo.guid
michael@0 242 , lastModified: aLivemarkInfo.lastModified
michael@0 243 });
michael@0 244 if (this._itemAdded && this._itemAdded.id == livemark.id) {
michael@0 245 livemark.index = this._itemAdded.index;
michael@0 246 livemark.guid = this._itemAdded.guid;
michael@0 247 if (!aLivemarkInfo.lastModified) {
michael@0 248 livemark.lastModified = this._itemAdded.lastModified;
michael@0 249 }
michael@0 250 }
michael@0 251
michael@0 252 // Updating the cache even if it has not yet been populated doesn't
michael@0 253 // matter since it will just be overwritten.
michael@0 254 this._livemarks[livemark.id] = livemark;
michael@0 255 this._guids[livemark.guid] = livemark.id;
michael@0 256 }
michael@0 257 catch (ex) {
michael@0 258 addLivemarkEx = ex;
michael@0 259 livemark = null;
michael@0 260 }
michael@0 261 finally {
michael@0 262 this._onCacheReady( () => {
michael@0 263 if (addLivemarkEx) {
michael@0 264 if (aLivemarkCallback) {
michael@0 265 try {
michael@0 266 aLivemarkCallback.onCompletion(addLivemarkEx.result, livemark);
michael@0 267 }
michael@0 268 catch(ex2) { }
michael@0 269 } else {
michael@0 270 deferred.reject(addLivemarkEx);
michael@0 271 }
michael@0 272 }
michael@0 273 else {
michael@0 274 if (aLivemarkCallback) {
michael@0 275 try {
michael@0 276 aLivemarkCallback.onCompletion(Cr.NS_OK, livemark);
michael@0 277 }
michael@0 278 catch(ex2) { }
michael@0 279 } else {
michael@0 280 deferred.resolve(livemark);
michael@0 281 }
michael@0 282 }
michael@0 283 });
michael@0 284 }
michael@0 285
michael@0 286 return aLivemarkCallback ? null : deferred.promise;
michael@0 287 },
michael@0 288
michael@0 289 removeLivemark: function LS_removeLivemark(aLivemarkInfo, aLivemarkCallback)
michael@0 290 {
michael@0 291 if (!aLivemarkInfo) {
michael@0 292 throw Cr.NS_ERROR_INVALID_ARG;
michael@0 293 }
michael@0 294
michael@0 295 // Accept either a guid or an id.
michael@0 296 let id = aLivemarkInfo.guid || aLivemarkInfo.id;
michael@0 297 if (("guid" in aLivemarkInfo && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid)) ||
michael@0 298 ("id" in aLivemarkInfo && aLivemarkInfo.id < 1) ||
michael@0 299 !id) {
michael@0 300 throw Cr.NS_ERROR_INVALID_ARG;
michael@0 301 }
michael@0 302
michael@0 303 if (aLivemarkCallback) {
michael@0 304 Deprecated.warning("Passing a callback to Livermarks methods is deprecated. " +
michael@0 305 "Please use the returned promise instead.",
michael@0 306 "https://developer.mozilla.org/docs/Mozilla/JavaScript_code_modules/Promise.jsm");
michael@0 307 }
michael@0 308
michael@0 309 // Convert the guid to an id.
michael@0 310 if (id in this._guids) {
michael@0 311 id = this._guids[id];
michael@0 312 }
michael@0 313
michael@0 314 let deferred = Promise.defer();
michael@0 315 let removeLivemarkEx = null;
michael@0 316 try {
michael@0 317 if (!(id in this._livemarks)) {
michael@0 318 throw new Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
michael@0 319 }
michael@0 320 this._livemarks[id].remove();
michael@0 321 }
michael@0 322 catch (ex) {
michael@0 323 removeLivemarkEx = ex;
michael@0 324 }
michael@0 325 finally {
michael@0 326 this._onCacheReady( () => {
michael@0 327 if (removeLivemarkEx) {
michael@0 328 if (aLivemarkCallback) {
michael@0 329 try {
michael@0 330 aLivemarkCallback.onCompletion(removeLivemarkEx.result, null);
michael@0 331 }
michael@0 332 catch(ex2) { }
michael@0 333 } else {
michael@0 334 deferred.reject(removeLivemarkEx);
michael@0 335 }
michael@0 336 }
michael@0 337 else {
michael@0 338 if (aLivemarkCallback) {
michael@0 339 try {
michael@0 340 aLivemarkCallback.onCompletion(Cr.NS_OK, null);
michael@0 341 }
michael@0 342 catch(ex2) { }
michael@0 343 } else {
michael@0 344 deferred.resolve();
michael@0 345 }
michael@0 346 }
michael@0 347 });
michael@0 348 }
michael@0 349
michael@0 350 return aLivemarkCallback ? null : deferred.promise;
michael@0 351 },
michael@0 352
michael@0 353 _reloaded: [],
michael@0 354 _reloadNextLivemark: function LS__reloadNextLivemark()
michael@0 355 {
michael@0 356 this._reloading = false;
michael@0 357 // Find first livemark to be reloaded.
michael@0 358 for (let id in this._livemarks) {
michael@0 359 if (this._reloaded.indexOf(id) == -1) {
michael@0 360 this._reloaded.push(id);
michael@0 361 this._livemarks[id].reload(this._forceUpdate);
michael@0 362 this._startReloadTimer();
michael@0 363 break;
michael@0 364 }
michael@0 365 }
michael@0 366 },
michael@0 367
michael@0 368 reloadLivemarks: function LS_reloadLivemarks(aForceUpdate)
michael@0 369 {
michael@0 370 // Check if there's a currently running reload, to save some useless work.
michael@0 371 let notWorthRestarting =
michael@0 372 this._forceUpdate || // We're already forceUpdating.
michael@0 373 !aForceUpdate; // The caller didn't request a forced update.
michael@0 374 if (this._reloading && notWorthRestarting) {
michael@0 375 // Ignore this call.
michael@0 376 return;
michael@0 377 }
michael@0 378
michael@0 379 this._onCacheReady( () => {
michael@0 380 this._forceUpdate = !!aForceUpdate;
michael@0 381 this._reloaded = [];
michael@0 382 // Livemarks reloads happen on a timer, and are delayed for performance
michael@0 383 // reasons.
michael@0 384 this._startReloadTimer();
michael@0 385 });
michael@0 386 },
michael@0 387
michael@0 388 getLivemark: function LS_getLivemark(aLivemarkInfo, aLivemarkCallback)
michael@0 389 {
michael@0 390 if (!aLivemarkInfo) {
michael@0 391 throw Cr.NS_ERROR_INVALID_ARG;
michael@0 392 }
michael@0 393 // Accept either a guid or an id.
michael@0 394 let id = aLivemarkInfo.guid || aLivemarkInfo.id;
michael@0 395 if (("guid" in aLivemarkInfo && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid)) ||
michael@0 396 ("id" in aLivemarkInfo && aLivemarkInfo.id < 1) ||
michael@0 397 !id) {
michael@0 398 throw Cr.NS_ERROR_INVALID_ARG;
michael@0 399 }
michael@0 400
michael@0 401 if (aLivemarkCallback) {
michael@0 402 Deprecated.warning("Passing a callback to Livermarks methods is deprecated. " +
michael@0 403 "Please use the returned promise instead.",
michael@0 404 "https://developer.mozilla.org/docs/Mozilla/JavaScript_code_modules/Promise.jsm");
michael@0 405 }
michael@0 406
michael@0 407 let deferred = Promise.defer();
michael@0 408 this._onCacheReady( () => {
michael@0 409 // Convert the guid to an id.
michael@0 410 if (id in this._guids) {
michael@0 411 id = this._guids[id];
michael@0 412 }
michael@0 413 if (id in this._livemarks) {
michael@0 414 if (aLivemarkCallback) {
michael@0 415 try {
michael@0 416 aLivemarkCallback.onCompletion(Cr.NS_OK, this._livemarks[id]);
michael@0 417 } catch (ex) {}
michael@0 418 } else {
michael@0 419 deferred.resolve(this._livemarks[id]);
michael@0 420 }
michael@0 421 }
michael@0 422 else {
michael@0 423 if (aLivemarkCallback) {
michael@0 424 try {
michael@0 425 aLivemarkCallback.onCompletion(Cr.NS_ERROR_INVALID_ARG, null);
michael@0 426 } catch (ex) { }
michael@0 427 } else {
michael@0 428 deferred.reject(Components.Exception("", Cr.NS_ERROR_INVALID_ARG));
michael@0 429 }
michael@0 430 }
michael@0 431 });
michael@0 432
michael@0 433 return aLivemarkCallback ? null : deferred.promise;
michael@0 434 },
michael@0 435
michael@0 436 //////////////////////////////////////////////////////////////////////////////
michael@0 437 //// nsINavBookmarkObserver
michael@0 438
michael@0 439 onBeginUpdateBatch: function () {},
michael@0 440 onEndUpdateBatch: function () {},
michael@0 441 onItemVisited: function () {},
michael@0 442
michael@0 443 _itemAdded: null,
michael@0 444 onItemAdded: function LS_onItemAdded(aItemId, aParentId, aIndex, aItemType,
michael@0 445 aURI, aTitle, aDateAdded, aGUID)
michael@0 446 {
michael@0 447 if (aItemType == Ci.nsINavBookmarksService.TYPE_FOLDER) {
michael@0 448 this._itemAdded = { id: aItemId
michael@0 449 , guid: aGUID
michael@0 450 , index: aIndex
michael@0 451 , lastModified: aDateAdded
michael@0 452 };
michael@0 453 }
michael@0 454 },
michael@0 455
michael@0 456 onItemChanged: function LS_onItemChanged(aItemId, aProperty, aIsAnno, aValue,
michael@0 457 aLastModified, aItemType)
michael@0 458 {
michael@0 459 if (aItemType == Ci.nsINavBookmarksService.TYPE_FOLDER) {
michael@0 460 if (this._itemAdded && this._itemAdded.id == aItemId) {
michael@0 461 this._itemAdded.lastModified = aLastModified;
michael@0 462 }
michael@0 463 if (aItemId in this._livemarks) {
michael@0 464 if (aProperty == "title") {
michael@0 465 this._livemarks[aItemId].title = aValue;
michael@0 466 }
michael@0 467 this._livemarks[aItemId].lastModified = aLastModified;
michael@0 468 }
michael@0 469 }
michael@0 470 },
michael@0 471
michael@0 472 onItemMoved: function LS_onItemMoved(aItemId, aOldParentId, aOldIndex,
michael@0 473 aNewParentId, aNewIndex, aItemType)
michael@0 474 {
michael@0 475 if (aItemType == Ci.nsINavBookmarksService.TYPE_FOLDER &&
michael@0 476 aItemId in this._livemarks) {
michael@0 477 this._livemarks[aItemId].parentId = aNewParentId;
michael@0 478 this._livemarks[aItemId].index = aNewIndex;
michael@0 479 }
michael@0 480 },
michael@0 481
michael@0 482 onItemRemoved: function LS_onItemRemoved(aItemId, aParentId, aIndex,
michael@0 483 aItemType, aURI, aGUID)
michael@0 484 {
michael@0 485 if (aItemType == Ci.nsINavBookmarksService.TYPE_FOLDER &&
michael@0 486 aItemId in this._livemarks) {
michael@0 487 this._livemarks[aItemId].terminate();
michael@0 488 delete this._livemarks[aItemId];
michael@0 489 delete this._guids[aGUID];
michael@0 490 }
michael@0 491 },
michael@0 492
michael@0 493 //////////////////////////////////////////////////////////////////////////////
michael@0 494 //// nsINavHistoryObserver
michael@0 495
michael@0 496 onBeginUpdateBatch: function () {},
michael@0 497 onEndUpdateBatch: function () {},
michael@0 498 onPageChanged: function () {},
michael@0 499 onTitleChanged: function () {},
michael@0 500 onDeleteVisits: function () {},
michael@0 501 onClearHistory: function () {},
michael@0 502
michael@0 503 onDeleteURI: function PS_onDeleteURI(aURI) {
michael@0 504 for each (let livemark in this._livemarks) {
michael@0 505 livemark.updateURIVisitedStatus(aURI, false);
michael@0 506 }
michael@0 507 },
michael@0 508
michael@0 509 onVisit: function PS_onVisit(aURI) {
michael@0 510 for each (let livemark in this._livemarks) {
michael@0 511 livemark.updateURIVisitedStatus(aURI, true);
michael@0 512 }
michael@0 513 },
michael@0 514
michael@0 515 //////////////////////////////////////////////////////////////////////////////
michael@0 516 //// nsISupports
michael@0 517
michael@0 518 classID: Components.ID("{dca61eb5-c7cd-4df1-b0fb-d0722baba251}"),
michael@0 519
michael@0 520 _xpcom_factory: XPCOMUtils.generateSingletonFactory(LivemarkService),
michael@0 521
michael@0 522 QueryInterface: XPCOMUtils.generateQI([
michael@0 523 Ci.mozIAsyncLivemarks
michael@0 524 , Ci.nsINavBookmarkObserver
michael@0 525 , Ci.nsINavHistoryObserver
michael@0 526 , Ci.nsIObserver
michael@0 527 , Ci.nsISupportsWeakReference
michael@0 528 ])
michael@0 529 };
michael@0 530
michael@0 531 ////////////////////////////////////////////////////////////////////////////////
michael@0 532 //// Livemark
michael@0 533
michael@0 534 /**
michael@0 535 * Object used internally to represent a livemark.
michael@0 536 *
michael@0 537 * @param aLivemarkInfo
michael@0 538 * Object containing information on the livemark. If the livemark is
michael@0 539 * not included in the object, a new livemark will be created.
michael@0 540 *
michael@0 541 * @note terminate() must be invoked before getting rid of this object.
michael@0 542 */
michael@0 543 function Livemark(aLivemarkInfo)
michael@0 544 {
michael@0 545 this.title = aLivemarkInfo.title;
michael@0 546 this.parentId = aLivemarkInfo.parentId;
michael@0 547 this.index = aLivemarkInfo.index;
michael@0 548
michael@0 549 this._status = Ci.mozILivemark.STATUS_READY;
michael@0 550
michael@0 551 // Hash of resultObservers, hashed by container.
michael@0 552 this._resultObservers = new Map();
michael@0 553 // This keeps a list of the containers used as keys in the map, since
michael@0 554 // it's not iterable. In future may use an iterable Map.
michael@0 555 this._resultObserversList = [];
michael@0 556
michael@0 557 // Sorted array of objects representing livemark children in the form
michael@0 558 // { uri, title, visited }.
michael@0 559 this._children = [];
michael@0 560
michael@0 561 // Keeps a separate array of nodes for each requesting container, hashed by
michael@0 562 // the container itself.
michael@0 563 this._nodes = new Map();
michael@0 564
michael@0 565 this._guid = "";
michael@0 566 this._lastModified = 0;
michael@0 567
michael@0 568 this.loadGroup = null;
michael@0 569 this.feedURI = null;
michael@0 570 this.siteURI = null;
michael@0 571 this.expireTime = 0;
michael@0 572
michael@0 573 if (aLivemarkInfo.id) {
michael@0 574 // This request comes from the cache.
michael@0 575 this.id = aLivemarkInfo.id;
michael@0 576 this.guid = aLivemarkInfo.guid;
michael@0 577 this.feedURI = aLivemarkInfo.feedURI;
michael@0 578 this.siteURI = aLivemarkInfo.siteURI;
michael@0 579 this.lastModified = aLivemarkInfo.lastModified;
michael@0 580 }
michael@0 581 else {
michael@0 582 // Create a new livemark.
michael@0 583 this.id = PlacesUtils.bookmarks.createFolder(aLivemarkInfo.parentId,
michael@0 584 aLivemarkInfo.title,
michael@0 585 aLivemarkInfo.index,
michael@0 586 aLivemarkInfo.guid);
michael@0 587 PlacesUtils.bookmarks.setFolderReadonly(this.id, true);
michael@0 588 this.writeFeedURI(aLivemarkInfo.feedURI);
michael@0 589 if (aLivemarkInfo.siteURI) {
michael@0 590 this.writeSiteURI(aLivemarkInfo.siteURI);
michael@0 591 }
michael@0 592 // Last modified time must be the last change.
michael@0 593 if (aLivemarkInfo.lastModified) {
michael@0 594 this.lastModified = aLivemarkInfo.lastModified;
michael@0 595 PlacesUtils.bookmarks.setItemLastModified(this.id, this.lastModified);
michael@0 596 }
michael@0 597 }
michael@0 598 }
michael@0 599
michael@0 600 Livemark.prototype = {
michael@0 601 get status() this._status,
michael@0 602 set status(val) {
michael@0 603 if (this._status != val) {
michael@0 604 this._status = val;
michael@0 605 this._invalidateRegisteredContainers();
michael@0 606 }
michael@0 607 return this._status;
michael@0 608 },
michael@0 609
michael@0 610 /**
michael@0 611 * Sets an annotation on the bookmarks folder id representing the livemark.
michael@0 612 *
michael@0 613 * @param aAnnoName
michael@0 614 * Name of the annotation.
michael@0 615 * @param aValue
michael@0 616 * Value of the annotation.
michael@0 617 * @return The annotation value.
michael@0 618 * @throws If the folder is invalid.
michael@0 619 */
michael@0 620 _setAnno: function LM__setAnno(aAnnoName, aValue)
michael@0 621 {
michael@0 622 PlacesUtils.annotations
michael@0 623 .setItemAnnotation(this.id, aAnnoName, aValue, 0,
michael@0 624 PlacesUtils.annotations.EXPIRE_NEVER);
michael@0 625 },
michael@0 626
michael@0 627 writeFeedURI: function LM_writeFeedURI(aFeedURI)
michael@0 628 {
michael@0 629 this._setAnno(PlacesUtils.LMANNO_FEEDURI, aFeedURI.spec);
michael@0 630 this.feedURI = aFeedURI;
michael@0 631 },
michael@0 632
michael@0 633 writeSiteURI: function LM_writeSiteURI(aSiteURI)
michael@0 634 {
michael@0 635 if (!aSiteURI) {
michael@0 636 PlacesUtils.annotations.removeItemAnnotation(this.id,
michael@0 637 PlacesUtils.LMANNO_SITEURI)
michael@0 638 this.siteURI = null;
michael@0 639 return;
michael@0 640 }
michael@0 641
michael@0 642 // Security check the site URI against the feed URI principal.
michael@0 643 let feedPrincipal = secMan.getSimpleCodebasePrincipal(this.feedURI);
michael@0 644 try {
michael@0 645 secMan.checkLoadURIWithPrincipal(feedPrincipal, aSiteURI, SEC_FLAGS);
michael@0 646 }
michael@0 647 catch (ex) {
michael@0 648 return;
michael@0 649 }
michael@0 650
michael@0 651 this._setAnno(PlacesUtils.LMANNO_SITEURI, aSiteURI.spec)
michael@0 652 this.siteURI = aSiteURI;
michael@0 653 },
michael@0 654
michael@0 655 set guid(aGUID) {
michael@0 656 this._guid = aGUID;
michael@0 657 return aGUID;
michael@0 658 },
michael@0 659 get guid() this._guid,
michael@0 660
michael@0 661 set lastModified(aLastModified) {
michael@0 662 this._lastModified = aLastModified;
michael@0 663 return aLastModified;
michael@0 664 },
michael@0 665 get lastModified() this._lastModified,
michael@0 666
michael@0 667 /**
michael@0 668 * Tries to updates the livemark if needed.
michael@0 669 * The update process is asynchronous.
michael@0 670 *
michael@0 671 * @param [optional] aForceUpdate
michael@0 672 * If true will try to update the livemark even if its contents have
michael@0 673 * not yet expired.
michael@0 674 */
michael@0 675 updateChildren: function LM_updateChildren(aForceUpdate)
michael@0 676 {
michael@0 677 // Check if the livemark is already updating.
michael@0 678 if (this.status == Ci.mozILivemark.STATUS_LOADING)
michael@0 679 return;
michael@0 680
michael@0 681 // Check the TTL/expiration on this, to check if there is no need to update
michael@0 682 // this livemark.
michael@0 683 if (!aForceUpdate && this.children.length && this.expireTime > Date.now())
michael@0 684 return;
michael@0 685
michael@0 686 this.status = Ci.mozILivemark.STATUS_LOADING;
michael@0 687
michael@0 688 // Setting the status notifies observers that may remove the livemark.
michael@0 689 if (this._terminated)
michael@0 690 return;
michael@0 691
michael@0 692 try {
michael@0 693 // Create a load group for the request. This will allow us to
michael@0 694 // automatically keep track of redirects, so we can always
michael@0 695 // cancel the channel.
michael@0 696 let loadgroup = Cc["@mozilla.org/network/load-group;1"].
michael@0 697 createInstance(Ci.nsILoadGroup);
michael@0 698 let channel = NetUtil.newChannel(this.feedURI.spec).
michael@0 699 QueryInterface(Ci.nsIHttpChannel);
michael@0 700 channel.loadGroup = loadgroup;
michael@0 701 channel.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND |
michael@0 702 Ci.nsIRequest.LOAD_BYPASS_CACHE;
michael@0 703 channel.requestMethod = "GET";
michael@0 704 channel.setRequestHeader("X-Moz", "livebookmarks", false);
michael@0 705
michael@0 706 // Stream the result to the feed parser with this listener
michael@0 707 let listener = new LivemarkLoadListener(this);
michael@0 708 channel.notificationCallbacks = listener;
michael@0 709 channel.asyncOpen(listener, null);
michael@0 710
michael@0 711 this.loadGroup = loadgroup;
michael@0 712 }
michael@0 713 catch (ex) {
michael@0 714 this.status = Ci.mozILivemark.STATUS_FAILED;
michael@0 715 }
michael@0 716 },
michael@0 717
michael@0 718 reload: function LM_reload(aForceUpdate)
michael@0 719 {
michael@0 720 this.updateChildren(aForceUpdate);
michael@0 721 },
michael@0 722
michael@0 723 remove: function LM_remove() {
michael@0 724 PlacesUtils.bookmarks.removeItem(this.id);
michael@0 725 },
michael@0 726
michael@0 727 get children() this._children,
michael@0 728 set children(val) {
michael@0 729 this._children = val;
michael@0 730
michael@0 731 // Discard the previous cached nodes, new ones should be generated.
michael@0 732 for (let i = 0; i < this._resultObserversList.length; i++) {
michael@0 733 let container = this._resultObserversList[i];
michael@0 734 this._nodes.delete(container);
michael@0 735 }
michael@0 736
michael@0 737 // Update visited status for each entry.
michael@0 738 for (let i = 0; i < this._children.length; i++) {
michael@0 739 let child = this._children[i];
michael@0 740 asyncHistory.isURIVisited(child.uri,
michael@0 741 (function(aURI, aIsVisited) {
michael@0 742 this.updateURIVisitedStatus(aURI, aIsVisited);
michael@0 743 }).bind(this));
michael@0 744 }
michael@0 745
michael@0 746 return this._children;
michael@0 747 },
michael@0 748
michael@0 749 _isURIVisited: function LM__isURIVisited(aURI) {
michael@0 750 for (let i = 0; i < this.children.length; i++) {
michael@0 751 if (this.children[i].uri.equals(aURI)) {
michael@0 752 return this.children[i].visited;
michael@0 753 }
michael@0 754 }
michael@0 755 },
michael@0 756
michael@0 757 getNodesForContainer: function LM_getNodesForContainer(aContainerNode)
michael@0 758 {
michael@0 759 if (this._nodes.has(aContainerNode)) {
michael@0 760 return this._nodes.get(aContainerNode);
michael@0 761 }
michael@0 762
michael@0 763 let livemark = this;
michael@0 764 let nodes = [];
michael@0 765 let now = Date.now() * 1000;
michael@0 766 for (let i = 0; i < this._children.length; i++) {
michael@0 767 let child = this._children[i];
michael@0 768 let node = {
michael@0 769 // The QueryInterface is needed cause aContainerNode is a jsval.
michael@0 770 // This is required to avoid issues with scriptable wrappers that would
michael@0 771 // not allow the view to correctly set expandos.
michael@0 772 get parent()
michael@0 773 aContainerNode.QueryInterface(Ci.nsINavHistoryContainerResultNode),
michael@0 774 get parentResult() this.parent.parentResult,
michael@0 775 get uri() child.uri.spec,
michael@0 776 get type() Ci.nsINavHistoryResultNode.RESULT_TYPE_URI,
michael@0 777 get title() child.title,
michael@0 778 get accessCount()
michael@0 779 Number(livemark._isURIVisited(NetUtil.newURI(this.uri))),
michael@0 780 get time() 0,
michael@0 781 get icon() "",
michael@0 782 get indentLevel() this.parent.indentLevel + 1,
michael@0 783 get bookmarkIndex() -1,
michael@0 784 get itemId() -1,
michael@0 785 get dateAdded() now + i,
michael@0 786 get lastModified() now + i,
michael@0 787 get tags()
michael@0 788 PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(this.uri)).join(", "),
michael@0 789 QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryResultNode])
michael@0 790 };
michael@0 791 nodes.push(node);
michael@0 792 }
michael@0 793 this._nodes.set(aContainerNode, nodes);
michael@0 794 return nodes;
michael@0 795 },
michael@0 796
michael@0 797 registerForUpdates: function LM_registerForUpdates(aContainerNode,
michael@0 798 aResultObserver)
michael@0 799 {
michael@0 800 this._resultObservers.set(aContainerNode, aResultObserver);
michael@0 801 this._resultObserversList.push(aContainerNode);
michael@0 802 },
michael@0 803
michael@0 804 unregisterForUpdates: function LM_unregisterForUpdates(aContainerNode)
michael@0 805 {
michael@0 806 this._resultObservers.delete(aContainerNode);
michael@0 807 let index = this._resultObserversList.indexOf(aContainerNode);
michael@0 808 this._resultObserversList.splice(index, 1);
michael@0 809
michael@0 810 this._nodes.delete(aContainerNode);
michael@0 811 },
michael@0 812
michael@0 813 _invalidateRegisteredContainers: function LM__invalidateRegisteredContainers()
michael@0 814 {
michael@0 815 for (let i = 0; i < this._resultObserversList.length; i++) {
michael@0 816 let container = this._resultObserversList[i];
michael@0 817 let observer = this._resultObservers.get(container);
michael@0 818 observer.invalidateContainer(container);
michael@0 819 }
michael@0 820 },
michael@0 821
michael@0 822 updateURIVisitedStatus:
michael@0 823 function LM_updateURIVisitedStatus(aURI, aVisitedStatus)
michael@0 824 {
michael@0 825 for (let i = 0; i < this.children.length; i++) {
michael@0 826 if (this.children[i].uri.equals(aURI)) {
michael@0 827 this.children[i].visited = aVisitedStatus;
michael@0 828 }
michael@0 829 }
michael@0 830
michael@0 831 for (let i = 0; i < this._resultObserversList.length; i++) {
michael@0 832 let container = this._resultObserversList[i];
michael@0 833 let observer = this._resultObservers.get(container);
michael@0 834 if (this._nodes.has(container)) {
michael@0 835 let nodes = this._nodes.get(container);
michael@0 836 for (let j = 0; j < nodes.length; j++) {
michael@0 837 let node = nodes[j];
michael@0 838 if (node.uri == aURI.spec) {
michael@0 839 Services.tm.mainThread.dispatch((function () {
michael@0 840 observer.nodeHistoryDetailsChanged(node, 0, aVisitedStatus);
michael@0 841 }).bind(this), Ci.nsIThread.DISPATCH_NORMAL);
michael@0 842 }
michael@0 843 }
michael@0 844 }
michael@0 845 }
michael@0 846 },
michael@0 847
michael@0 848 /**
michael@0 849 * Terminates the livemark entry, cancelling any ongoing load.
michael@0 850 * Must be invoked before destroying the entry.
michael@0 851 */
michael@0 852 terminate: function LM_terminate()
michael@0 853 {
michael@0 854 // Avoid handling any updateChildren request from now on.
michael@0 855 this._terminated = true;
michael@0 856 // Clear the list before aborting, since abort() would try to set the
michael@0 857 // status and notify about it, but that's not really useful at this point.
michael@0 858 this._resultObserversList = [];
michael@0 859 this.abort();
michael@0 860 },
michael@0 861
michael@0 862 /**
michael@0 863 * Aborts the livemark loading if needed.
michael@0 864 */
michael@0 865 abort: function LM_abort()
michael@0 866 {
michael@0 867 this.status = Ci.mozILivemark.STATUS_FAILED;
michael@0 868 if (this.loadGroup) {
michael@0 869 this.loadGroup.cancel(Cr.NS_BINDING_ABORTED);
michael@0 870 this.loadGroup = null;
michael@0 871 }
michael@0 872 },
michael@0 873
michael@0 874 QueryInterface: XPCOMUtils.generateQI([
michael@0 875 Ci.mozILivemark
michael@0 876 ])
michael@0 877 }
michael@0 878
michael@0 879 ////////////////////////////////////////////////////////////////////////////////
michael@0 880 //// LivemarkLoadListener
michael@0 881
michael@0 882 /**
michael@0 883 * Object used internally to handle loading a livemark's contents.
michael@0 884 *
michael@0 885 * @param aLivemark
michael@0 886 * The Livemark that is loading.
michael@0 887 */
michael@0 888 function LivemarkLoadListener(aLivemark)
michael@0 889 {
michael@0 890 this._livemark = aLivemark;
michael@0 891 this._processor = null;
michael@0 892 this._isAborted = false;
michael@0 893 this._ttl = EXPIRE_TIME_MS;
michael@0 894 }
michael@0 895
michael@0 896 LivemarkLoadListener.prototype = {
michael@0 897 abort: function LLL_abort(aException)
michael@0 898 {
michael@0 899 if (!this._isAborted) {
michael@0 900 this._isAborted = true;
michael@0 901 this._livemark.abort();
michael@0 902 this._setResourceTTL(ONERROR_EXPIRE_TIME_MS);
michael@0 903 }
michael@0 904 },
michael@0 905
michael@0 906 // nsIFeedResultListener
michael@0 907 handleResult: function LLL_handleResult(aResult)
michael@0 908 {
michael@0 909 if (this._isAborted) {
michael@0 910 return;
michael@0 911 }
michael@0 912
michael@0 913 try {
michael@0 914 // We need this to make sure the item links are safe
michael@0 915 let feedPrincipal =
michael@0 916 secMan.getSimpleCodebasePrincipal(this._livemark.feedURI);
michael@0 917
michael@0 918 // Enforce well-formedness because the existing code does
michael@0 919 if (!aResult || !aResult.doc || aResult.bozo) {
michael@0 920 throw new Components.Exception("", Cr.NS_ERROR_FAILURE);
michael@0 921 }
michael@0 922
michael@0 923 let feed = aResult.doc.QueryInterface(Ci.nsIFeed);
michael@0 924 let siteURI = this._livemark.siteURI;
michael@0 925 if (feed.link && (!siteURI || !feed.link.equals(siteURI))) {
michael@0 926 siteURI = feed.link;
michael@0 927 this._livemark.writeSiteURI(siteURI);
michael@0 928 }
michael@0 929
michael@0 930 // Insert feed items.
michael@0 931 let livemarkChildren = [];
michael@0 932 for (let i = 0; i < feed.items.length; ++i) {
michael@0 933 let entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
michael@0 934 let uri = entry.link || siteURI;
michael@0 935 if (!uri) {
michael@0 936 continue;
michael@0 937 }
michael@0 938
michael@0 939 try {
michael@0 940 secMan.checkLoadURIWithPrincipal(feedPrincipal, uri, SEC_FLAGS);
michael@0 941 }
michael@0 942 catch(ex) {
michael@0 943 continue;
michael@0 944 }
michael@0 945
michael@0 946 let title = entry.title ? entry.title.plainText() : "";
michael@0 947 livemarkChildren.push({ uri: uri, title: title, visited: false });
michael@0 948 }
michael@0 949
michael@0 950 this._livemark.children = livemarkChildren;
michael@0 951 }
michael@0 952 catch (ex) {
michael@0 953 this.abort(ex);
michael@0 954 }
michael@0 955 finally {
michael@0 956 this._processor.listener = null;
michael@0 957 this._processor = null;
michael@0 958 }
michael@0 959 },
michael@0 960
michael@0 961 onDataAvailable: function LLL_onDataAvailable(aRequest, aContext,
michael@0 962 aInputStream, aSourceOffset,
michael@0 963 aCount)
michael@0 964 {
michael@0 965 if (this._processor) {
michael@0 966 this._processor.onDataAvailable(aRequest, aContext, aInputStream,
michael@0 967 aSourceOffset, aCount);
michael@0 968 }
michael@0 969 },
michael@0 970
michael@0 971 onStartRequest: function LLL_onStartRequest(aRequest, aContext)
michael@0 972 {
michael@0 973 if (this._isAborted) {
michael@0 974 throw Cr.NS_ERROR_UNEXPECTED;
michael@0 975 }
michael@0 976
michael@0 977 let channel = aRequest.QueryInterface(Ci.nsIChannel);
michael@0 978 try {
michael@0 979 // Parse feed data as it comes in
michael@0 980 this._processor = Cc["@mozilla.org/feed-processor;1"].
michael@0 981 createInstance(Ci.nsIFeedProcessor);
michael@0 982 this._processor.listener = this;
michael@0 983 this._processor.parseAsync(null, channel.URI);
michael@0 984 this._processor.onStartRequest(aRequest, aContext);
michael@0 985 }
michael@0 986 catch (ex) {
michael@0 987 Components.utils.reportError("Livemark Service: feed processor received an invalid channel for " + channel.URI.spec);
michael@0 988 this.abort(ex);
michael@0 989 }
michael@0 990 },
michael@0 991
michael@0 992 onStopRequest: function LLL_onStopRequest(aRequest, aContext, aStatus)
michael@0 993 {
michael@0 994 if (!Components.isSuccessCode(aStatus)) {
michael@0 995 this.abort();
michael@0 996 return;
michael@0 997 }
michael@0 998
michael@0 999 // Set an expiration on the livemark, to reloading the data in future.
michael@0 1000 try {
michael@0 1001 if (this._processor) {
michael@0 1002 this._processor.onStopRequest(aRequest, aContext, aStatus);
michael@0 1003 }
michael@0 1004
michael@0 1005 // Calculate a new ttl
michael@0 1006 let channel = aRequest.QueryInterface(Ci.nsICachingChannel);
michael@0 1007 if (channel) {
michael@0 1008 let entryInfo = channel.cacheToken.QueryInterface(Ci.nsICacheEntry);
michael@0 1009 if (entryInfo) {
michael@0 1010 // nsICacheEntry returns value as seconds.
michael@0 1011 let expireTime = entryInfo.expirationTime * 1000;
michael@0 1012 let nowTime = Date.now();
michael@0 1013 // Note, expireTime can be 0, see bug 383538.
michael@0 1014 if (expireTime > nowTime) {
michael@0 1015 this._setResourceTTL(Math.max((expireTime - nowTime),
michael@0 1016 EXPIRE_TIME_MS));
michael@0 1017 return;
michael@0 1018 }
michael@0 1019 }
michael@0 1020 }
michael@0 1021 this._setResourceTTL(EXPIRE_TIME_MS);
michael@0 1022 }
michael@0 1023 catch (ex) {
michael@0 1024 this.abort(ex);
michael@0 1025 }
michael@0 1026 finally {
michael@0 1027 if (this._livemark.status == Ci.mozILivemark.STATUS_LOADING) {
michael@0 1028 this._livemark.status = Ci.mozILivemark.STATUS_READY;
michael@0 1029 }
michael@0 1030 this._livemark.locked = false;
michael@0 1031 this._livemark.loadGroup = null;
michael@0 1032 }
michael@0 1033 },
michael@0 1034
michael@0 1035 _setResourceTTL: function LLL__setResourceTTL(aMilliseconds)
michael@0 1036 {
michael@0 1037 this._livemark.expireTime = Date.now() + aMilliseconds;
michael@0 1038 },
michael@0 1039
michael@0 1040 // nsIInterfaceRequestor
michael@0 1041 getInterface: function LLL_getInterface(aIID)
michael@0 1042 {
michael@0 1043 return this.QueryInterface(aIID);
michael@0 1044 },
michael@0 1045
michael@0 1046 // nsISupports
michael@0 1047 QueryInterface: XPCOMUtils.generateQI([
michael@0 1048 Ci.nsIFeedResultListener
michael@0 1049 , Ci.nsIStreamListener
michael@0 1050 , Ci.nsIRequestObserver
michael@0 1051 , Ci.nsIInterfaceRequestor
michael@0 1052 ])
michael@0 1053 }
michael@0 1054
michael@0 1055 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([LivemarkService]);

mercurial