Sat, 03 Jan 2015 20:18:00 +0100
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]); |